launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #05434
[Merge] lp:~wallyworld/launchpad/no-open-pillar-owners-879103 into lp:launchpad
Ian Booth has proposed merging lp:~wallyworld/launchpad/no-open-pillar-owners-879103 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~wallyworld/launchpad/no-open-pillar-owners-879103/+merge/81228
Pillars (products, projects, distros) cannot be created with an open team as the maintainer. The maintainer must be a person or restricted/moderated team.
== Implementation ==
1. Add new storm validator: validate_person_or_closed_team
Implementation based on existing person validators.
Change pillar owner and security_contact model properties to use new validator, replacing validate_public_person
2. Add new helper method: is_person_or_closed_team
Implementation based on is_public_person
3. Add new vocab: ValidPillarOwner
Implementation based on ValidPersonOrTeam vocab.
extra_clause property returns: Person.subscriptionpolicy.is_in(CLOSED_TEAM_POLICY)
4. Change nominated vocab on owner and security property interface fields to use the new vocab , replacing ValidOwner
The changes mean that when existing products and distros are edited (via +review), if they currently have an open team for owner etc, then the form cannot be saved. So this change cannot land until all 233 products and 1 distro with open teams as owner/security contact have been updated.
The next branch will prevent teams which are pillar owners or security contacts from having their subscription policy changed to open.
== Demo and QA ==
Register a new project. The owner picker should say "Search for a restricted team, a moderated team, or a person" and only people and closed teams should be available in the search results.
== Tests ==
Update lib/registry/doc/vocabularies.txt
Add new tests to TestDistribution, TestProduct, TestProjectGroup:
- test_owner_cannot_be_open_team
- test_owner_can_be_closed_team
- test_security_contact_cannot_be_open_team
- test_security_contact_can_be_closed_team
Add new test case for is_person_or_closed_team helper method:
Test_is_person_or_closed_team
Add new test case: TestValidPersonOrClosedTeamVocabulary
- refactor TestValidPersonOrTeamVocabulary, factor out common tests to TestValidPersonOrTeamVocabularyMixin
- add test_team_filter
== Lint ==
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/bugs/interfaces/securitycontact.py
lib/lp/registry/errors.py
lib/lp/registry/vocabularies.py
lib/lp/registry/vocabularies.zcml
lib/lp/registry/doc/vocabularies.txt
lib/lp/registry/interfaces/distribution.py
lib/lp/registry/interfaces/person.py
lib/lp/registry/interfaces/product.py
lib/lp/registry/interfaces/projectgroup.py
lib/lp/registry/model/distribution.py
lib/lp/registry/model/product.py
lib/lp/registry/model/projectgroup.py
lib/lp/registry/tests/test_distribution.py
lib/lp/registry/tests/test_person_vocabularies.py
lib/lp/registry/tests/test_product.py
lib/lp/registry/tests/test_projectgroup.py
lib/lp/services/fields/__init__.py
lib/lp/services/fields/tests/test_fields.py
--
https://code.launchpad.net/~wallyworld/launchpad/no-open-pillar-owners-879103/+merge/81228
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~wallyworld/launchpad/no-open-pillar-owners-879103 into lp:launchpad.
=== modified file 'lib/lp/bugs/interfaces/securitycontact.py'
--- lib/lp/bugs/interfaces/securitycontact.py 2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/interfaces/securitycontact.py 2011-11-07 00:42:24 +0000
@@ -28,4 +28,4 @@
"security-related bug reports. The security contact will be "
"subscribed to all bugs marked as a security vulnerability and "
"will receive email about all activity on all security bugs."),
- required=False, vocabulary='ValidPersonOrTeam'))
+ required=False, vocabulary='ValidPillarOwner'))
=== modified file 'lib/lp/registry/doc/vocabularies.txt'
--- lib/lp/registry/doc/vocabularies.txt 2011-10-21 00:06:01 +0000
+++ lib/lp/registry/doc/vocabularies.txt 2011-11-07 00:42:24 +0000
@@ -975,6 +975,28 @@
u'simple-team', u'testing-spanish-team', u'ubuntu-security',
u'ubuntu-team', u'warty-gnome']
+ValidPillarOwner
+..........
+
+Valid persons and closed teams are valid pillar owners.
+
+ >>> login(ANONYMOUS)
+ >>> vocab = get_naked_vocab(None, "ValidPillarOwner")
+ >>> vocab.step_title
+ 'Search for a restricted team, a moderated team, or a person'
+
+ >>> list(vocab.search(None))
+ []
+
+Almost all teams have the word 'team' as part of their names, so a
+search for 'team' should give us some of them. Only restricted or moderated
+teams will be returned in the search results. There's also the ircperson
+created earlier with an icr nickname of 'team':
+
+ >>> sorted(person.name for person in vocab.search('team'))
+ [u'hwdb-team', u'ircperson', u'name18', u'name20', u'name21',
+ u'no-team-memberships', u'otherteam',
+ u'simple-team', u'testing-spanish-team', u'ubuntu-team', u'warty-gnome']
ValidTeam
.........
=== modified file 'lib/lp/registry/errors.py'
--- lib/lp/registry/errors.py 2011-08-11 20:37:16 +0000
+++ lib/lp/registry/errors.py 2011-11-07 00:42:24 +0000
@@ -16,6 +16,7 @@
'NameAlreadyTaken',
'NoSuchDistroSeries',
'NoSuchSourcePackageName',
+ 'OpenTeamLinkageError',
'PPACreationError',
'PrivatePersonLinkageError',
'TeamMembershipTransitionError',
@@ -38,6 +39,11 @@
"""An attempt was made to link a private person/team to something."""
+@error_status(httplib.FORBIDDEN)
+class OpenTeamLinkageError(ValueError):
+ """An attempt was made to link an open team to something."""
+
+
@error_status(httplib.CONFLICT)
class NameAlreadyTaken(Exception):
"""The name given for a person is already in use by other person."""
=== modified file 'lib/lp/registry/interfaces/distribution.py'
--- lib/lp/registry/interfaces/distribution.py 2011-08-29 00:13:22 +0000
+++ lib/lp/registry/interfaces/distribution.py 2011-11-07 00:42:24 +0000
@@ -211,7 +211,7 @@
exported_as='domain_name')
owner = exported(
PublicPersonChoice(
- title=_("Owner"), vocabulary='ValidOwner',
+ title=_("Owner"), vocabulary='ValidPillarOwner',
description=_("The distro's owner."), required=True))
registrant = exported(
PublicPersonChoice(
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2011-10-31 07:59:32 +0000
+++ lib/lp/registry/interfaces/person.py 2011-11-07 00:42:24 +0000
@@ -37,6 +37,7 @@
'TeamMembershipRenewalPolicy',
'TeamSubscriptionPolicy',
'validate_person',
+ 'validate_person_or_closed_team',
'validate_public_person',
]
@@ -126,6 +127,7 @@
)
from lp.code.interfaces.hasrecipes import IHasRecipes
from lp.registry.errors import (
+ OpenTeamLinkageError,
PrivatePersonLinkageError,
TeamSubscriptionPolicyError,
)
@@ -151,6 +153,7 @@
from lp.services.fields import (
BlacklistableContentNameField,
IconImageUpload,
+ is_public_person_or_closed_team,
is_public_person,
LogoImageUpload,
MugshotImageUpload,
@@ -170,7 +173,8 @@
@block_implicit_flushes
-def validate_person_common(obj, attr, value, validate_func):
+def validate_person_common(obj, attr, value, validate_func,
+ error_class=PrivatePersonLinkageError):
"""Validate the person using the supplied function."""
if value is None:
return None
@@ -181,7 +185,7 @@
from lp.registry.model.person import Person
person = Person.get(value)
if not validate_func(person):
- raise PrivatePersonLinkageError(
+ raise error_class(
"Cannot link person (name=%s, visibility=%s) to %s (name=%s)"
% (person.name, person.visibility.name,
obj, getattr(obj, 'name', None)))
@@ -206,6 +210,15 @@
return validate_person_common(obj, attr, value, validate)
+def validate_person_or_closed_team(obj, attr, value):
+
+ def validate(person):
+ return is_public_person_or_closed_team(person)
+
+ return validate_person_common(
+ obj, attr, value, validate, error_class=OpenTeamLinkageError)
+
+
class PersonalStanding(DBEnumeratedType):
"""A person's standing.
=== modified file 'lib/lp/registry/interfaces/product.py'
--- lib/lp/registry/interfaces/product.py 2011-10-29 18:23:06 +0000
+++ lib/lp/registry/interfaces/product.py 2011-11-07 00:42:24 +0000
@@ -440,9 +440,9 @@
PersonChoice(
title=_('Maintainer'),
required=True,
- vocabulary='ValidOwner',
- description=_("The person or team who maintains the project "
- "information in Launchpad.")))
+ vocabulary='ValidPillarOwner',
+ description=_("The person or closed team who maintains the "
+ "project information in Launchpad.")))
registrant = exported(
PublicPersonChoice(
=== modified file 'lib/lp/registry/interfaces/projectgroup.py'
--- lib/lp/registry/interfaces/projectgroup.py 2011-10-07 16:08:17 +0000
+++ lib/lp/registry/interfaces/projectgroup.py 2011-11-07 00:42:24 +0000
@@ -131,9 +131,9 @@
PublicPersonChoice(
title=_('Maintainer'),
required=True,
- vocabulary='ValidOwner',
+ vocabulary='ValidPillarOwner',
description=_("Project group owner. Must be either a "
- "Launchpad Person or Team.")))
+ "Launchpad Person or closed Team.")))
registrant = exported(
PublicPersonChoice(
=== modified file 'lib/lp/registry/model/distribution.py'
--- lib/lp/registry/model/distribution.py 2011-10-07 16:20:20 +0000
+++ lib/lp/registry/model/distribution.py 2011-11-07 00:42:24 +0000
@@ -131,6 +131,7 @@
)
from lp.registry.interfaces.packaging import PackagingType
from lp.registry.interfaces.person import (
+ validate_person_or_closed_team,
validate_person,
validate_public_person,
)
@@ -229,7 +230,7 @@
domainname = StringCol(notNull=True)
owner = ForeignKey(
dbName='owner', foreignKey='Person',
- storm_validator=validate_public_person, notNull=True)
+ storm_validator=validate_person_or_closed_team, notNull=True)
registrant = ForeignKey(
dbName='registrant', foreignKey='Person',
storm_validator=validate_public_person, notNull=True)
@@ -242,7 +243,7 @@
bug_reported_acknowledgement = StringCol(default=None)
security_contact = ForeignKey(
dbName='security_contact', foreignKey='Person',
- storm_validator=validate_public_person, notNull=False,
+ storm_validator=validate_person_or_closed_team, notNull=False,
default=None)
driver = ForeignKey(
dbName="driver", foreignKey="Person",
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2011-10-29 18:23:06 +0000
+++ lib/lp/registry/model/product.py 2011-11-07 00:42:24 +0000
@@ -141,6 +141,7 @@
from lp.registry.interfaces.person import (
IPersonSet,
validate_person,
+ validate_person_or_closed_team,
validate_public_person,
)
from lp.registry.interfaces.pillar import IPillarNameSet
@@ -318,7 +319,7 @@
default=None)
_owner = ForeignKey(
dbName="owner", foreignKey="Person",
- storm_validator=validate_person,
+ storm_validator=validate_person_or_closed_team,
notNull=True)
registrant = ForeignKey(
dbName="registrant", foreignKey="Person",
@@ -331,7 +332,7 @@
default=None)
security_contact = ForeignKey(
dbName='security_contact', foreignKey='Person',
- storm_validator=validate_public_person, notNull=False,
+ storm_validator=validate_person_or_closed_team, notNull=False,
default=None)
driver = ForeignKey(
dbName="driver", foreignKey="Person",
=== modified file 'lib/lp/registry/model/projectgroup.py'
--- lib/lp/registry/model/projectgroup.py 2011-10-07 16:20:20 +0000
+++ lib/lp/registry/model/projectgroup.py 2011-11-07 00:42:24 +0000
@@ -84,7 +84,10 @@
HasBranchesMixin,
HasMergeProposalsMixin,
)
-from lp.registry.interfaces.person import validate_public_person
+from lp.registry.interfaces.person import (
+ validate_person_or_closed_team,
+ validate_public_person,
+ )
from lp.registry.interfaces.pillar import IPillarNameSet
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.projectgroup import (
@@ -127,7 +130,7 @@
# db field names
owner = ForeignKey(
dbName='owner', foreignKey='Person',
- storm_validator=validate_public_person, notNull=True)
+ storm_validator=validate_person_or_closed_team, notNull=True)
registrant = ForeignKey(
dbName='registrant', foreignKey='Person',
storm_validator=validate_public_person, notNull=True)
=== modified file 'lib/lp/registry/tests/test_distribution.py'
--- lib/lp/registry/tests/test_distribution.py 2011-10-12 11:10:39 +0000
+++ lib/lp/registry/tests/test_distribution.py 2011-11-07 00:42:24 +0000
@@ -27,7 +27,14 @@
from lp.app.enums import ServiceUsage
from lp.app.errors import NotFoundError
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
-from lp.registry.errors import NoSuchDistroSeries
+from lp.registry.errors import (
+ NoSuchDistroSeries,
+ OpenTeamLinkageError,
+ )
+from lp.registry.interfaces.person import (
+ CLOSED_TEAM_POLICY,
+ OPEN_TEAM_POLICY,
+ )
from lp.registry.interfaces.distribution import (
IDistribution,
IDistributionSet,
@@ -61,6 +68,34 @@
distro = self.factory.makeDistribution()
self.assertEqual("Distribution", distro.pillar_category)
+ def test_owner_cannot_be_open_team(self):
+ """Distro owners cannot be open teams."""
+ for policy in OPEN_TEAM_POLICY:
+ open_team = self.factory.makeTeam(subscription_policy=policy)
+ self.assertRaises(
+ OpenTeamLinkageError, self.factory.makeDistribution,
+ owner=open_team)
+
+ def test_owner_can_be_closed_team(self):
+ """Distro owners can be closed teams."""
+ for policy in CLOSED_TEAM_POLICY:
+ closed_team = self.factory.makeTeam(subscription_policy=policy)
+ self.factory.makeDistribution(owner=closed_team)
+
+ def test_security_contact_cannot_be_open_team(self):
+ """Distro security contacts cannot be open teams."""
+ for policy in OPEN_TEAM_POLICY:
+ open_team = self.factory.makeTeam(subscription_policy=policy)
+ self.assertRaises(
+ OpenTeamLinkageError, self.factory.makeDistribution,
+ security_contact=open_team)
+
+ def test_security_contact_can_be_closed_team(self):
+ """Distro security contacts can be closed teams."""
+ for policy in CLOSED_TEAM_POLICY:
+ closed_team = self.factory.makeTeam(subscription_policy=policy)
+ self.factory.makeDistribution(security_contact=closed_team)
+
def test_distribution_repr_ansii(self):
# Verify that ANSI displayname is ascii safe.
distro = self.factory.makeDistribution(
=== modified file 'lib/lp/registry/tests/test_person_vocabularies.py'
--- lib/lp/registry/tests/test_person_vocabularies.py 2011-10-20 00:53:01 +0000
+++ lib/lp/registry/tests/test_person_vocabularies.py 2011-11-07 00:42:24 +0000
@@ -21,6 +21,8 @@
from lp.registry.interfaces.person import (
PersonVisibility,
TeamSubscriptionPolicy,
+ CLOSED_TEAM_POLICY,
+ OPEN_TEAM_POLICY,
)
from lp.registry.interfaces.karma import IKarmaCacheManager
from lp.registry.vocabularies import ValidPersonOrTeamVocabulary
@@ -51,15 +53,8 @@
return removeSecurityProxy(vocabulary).search(text, vocab_filter)
-class TestValidPersonOrTeamVocabulary(VocabularyTestBase,
- TestCaseWithFactory):
- """Test that the ValidPersonOrTeamVocabulary behaves as expected.
-
- Most tests are in lib/lp/registry/doc/vocabularies.txt.
- """
-
- layer = LaunchpadZopelessLayer
- vocabulary_name = 'ValidPersonOrTeam'
+class ValidPersonOrTeamVocabularyMixin(VocabularyTestBase):
+ """Common tests for the ValidPersonOrTeam vocabulary derivatives."""
def test_supported_filters(self):
# The vocab supports the correct filters.
@@ -156,12 +151,23 @@
name="fredteam", email="fredteam@xxxxxxx")
self._person_filter_tests(person)
- def _team_filter_tests(self, team):
+ def _team_filter_tests(self, teams):
results = self.searchVocabulary(None, '', 'TEAM')
for personorteam in results:
self.assertTrue(personorteam.is_team)
results = self.searchVocabulary(None, u'fred', 'TEAM')
- self.assertEqual([team], list(results))
+ self.assertContentEqual(teams, list(results))
+
+
+class TestValidPersonOrTeamVocabulary(ValidPersonOrTeamVocabularyMixin,
+ TestCaseWithFactory):
+ """Test that the ValidPersonOrTeamVocabulary behaves as expected.
+
+ Most tests are in lib/lp/registry/doc/vocabularies.txt.
+ """
+
+ layer = LaunchpadZopelessLayer
+ vocabulary_name = 'ValidPersonOrTeam'
def test_team_filter(self):
# Test that the team filter only returns teams.
@@ -169,8 +175,7 @@
name="fredperson", email="fredperson@xxxxxxx")
team = self.factory.makeTeam(
name="fredteam", email="fredteam@xxxxxxx")
- self._team_filter_tests(team)
- self._team_filter_tests(team)
+ self._team_filter_tests([team])
class TestValidPersonOrTeamPreloading(VocabularyTestBase,
@@ -209,6 +214,31 @@
self.assertThat(recorder, HasQueryCount(Equals(0)))
+class TestValidPersonOrClosedTeamVocabulary(ValidPersonOrTeamVocabularyMixin,
+ TestCaseWithFactory):
+ """Test that the ValidPersonOrClosedTeamVocabulary behaves as expected."""
+
+ layer = LaunchpadZopelessLayer
+ vocabulary_name = 'ValidPillarOwner'
+
+ def test_team_filter(self):
+ # Test that the team filter only returns closed teams.
+ self.factory.makePerson(
+ name="fredperson", email="fredperson@xxxxxxx")
+ for policy in OPEN_TEAM_POLICY:
+ self.factory.makeTeam(
+ name="fred%s" % policy.name.lower(),
+ email="team_%s@xxxxxxx" % policy.name,
+ subscription_policy=policy)
+ closed_teams = []
+ for policy in CLOSED_TEAM_POLICY:
+ closed_teams.append(self.factory.makeTeam(
+ name="fred%s" % policy.name.lower(),
+ email="team_%s@xxxxxxx" % policy.name,
+ subscription_policy=policy))
+ self._team_filter_tests(closed_teams)
+
+
class TeamMemberVocabularyTestBase(VocabularyTestBase):
def test_open_team_cannot_be_a_member_of_a_closed_team(self):
=== modified file 'lib/lp/registry/tests/test_product.py'
--- lib/lp/registry/tests/test_product.py 2011-10-18 12:57:33 +0000
+++ lib/lp/registry/tests/test_product.py 2011-11-07 00:42:24 +0000
@@ -34,10 +34,15 @@
from lp.bugs.interfaces.bugsummary import IBugSummaryDimension
from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
from lp.bugs.interfaces.bugtarget import IHasBugHeat
+from lp.registry.errors import OpenTeamLinkageError
from lp.registry.interfaces.product import (
IProduct,
License,
)
+from lp.registry.interfaces.person import (
+ CLOSED_TEAM_POLICY,
+ OPEN_TEAM_POLICY,
+ )
from lp.registry.interfaces.series import SeriesStatus
from lp.registry.model.commercialsubscription import CommercialSubscription
from lp.registry.model.product import (
@@ -222,6 +227,34 @@
[u'trunk', u'active-series'],
[series.name for series in active_series])
+ def test_owner_cannot_be_open_team(self):
+ """Product owners cannot be open teams."""
+ for policy in OPEN_TEAM_POLICY:
+ open_team = self.factory.makeTeam(subscription_policy=policy)
+ self.assertRaises(
+ OpenTeamLinkageError, self.factory.makeProduct,
+ owner=open_team)
+
+ def test_owner_can_be_closed_team(self):
+ """Product owners can be closed teams."""
+ for policy in CLOSED_TEAM_POLICY:
+ closed_team = self.factory.makeTeam(subscription_policy=policy)
+ self.factory.makeProduct(owner=closed_team)
+
+ def test_security_contact_cannot_be_open_team(self):
+ """Product security contacts cannot be open teams."""
+ for policy in OPEN_TEAM_POLICY:
+ open_team = self.factory.makeTeam(subscription_policy=policy)
+ self.assertRaises(
+ OpenTeamLinkageError, self.factory.makeProduct,
+ security_contact=open_team)
+
+ def test_security_contact_can_be_closed_team(self):
+ """Product security contacts can be closed teams."""
+ for policy in CLOSED_TEAM_POLICY:
+ closed_team = self.factory.makeTeam(subscription_policy=policy)
+ self.factory.makeProduct(security_contact=closed_team)
+
class TestProductFiles(TestCase):
"""Tests for downloadable product files."""
=== modified file 'lib/lp/registry/tests/test_projectgroup.py'
--- lib/lp/registry/tests/test_projectgroup.py 2011-10-25 04:43:25 +0000
+++ lib/lp/registry/tests/test_projectgroup.py 2011-11-07 00:42:24 +0000
@@ -11,6 +11,11 @@
DatabaseFunctionalLayer,
LaunchpadFunctionalLayer,
)
+from lp.registry.errors import OpenTeamLinkageError
+from lp.registry.interfaces.person import (
+ CLOSED_TEAM_POLICY,
+ OPEN_TEAM_POLICY,
+ )
from lp.registry.interfaces.projectgroup import IProjectGroupSet
from lp.testing import (
launchpadlib_for,
@@ -30,6 +35,20 @@
pg = self.factory.makeProject()
self.assertEqual("Project Group", pg.pillar_category)
+ def test_owner_cannot_be_open_team(self):
+ """Project group owners cannot be open teams."""
+ for policy in OPEN_TEAM_POLICY:
+ open_team = self.factory.makeTeam(subscription_policy=policy)
+ self.assertRaises(
+ OpenTeamLinkageError, self.factory.makeProject,
+ owner=open_team)
+
+ def test_owner_can_be_closed_team(self):
+ """Project group owners can be closed teams."""
+ for policy in CLOSED_TEAM_POLICY:
+ closed_team = self.factory.makeTeam(subscription_policy=policy)
+ self.factory.makeProject(owner=closed_team)
+
class ProjectGroupSearchTestCase(TestCaseWithFactory):
=== modified file 'lib/lp/registry/vocabularies.py'
--- lib/lp/registry/vocabularies.py 2011-10-20 00:53:01 +0000
+++ lib/lp/registry/vocabularies.py 2011-11-07 00:42:24 +0000
@@ -886,6 +886,23 @@
return 'Search'
+class ValidPersonOrClosedTeamVocabulary(TeamVocabularyMixin,
+ ValidPersonOrTeamVocabulary):
+ """The set of people and closed teams in Launchpad.
+
+ A closed team is one for which the subscription policy is either
+ RESTRICTED or MODERATED.
+ """
+
+ @property
+ def is_closed_team(self):
+ return True
+
+ @property
+ def extra_clause(self):
+ return Person.subscriptionpolicy.is_in(CLOSED_TEAM_POLICY)
+
+
class ValidTeamMemberVocabulary(TeamVocabularyMixin,
ValidPersonOrTeamVocabulary):
"""The set of valid members of a given team.
=== modified file 'lib/lp/registry/vocabularies.zcml'
--- lib/lp/registry/vocabularies.zcml 2011-08-22 13:01:14 +0000
+++ lib/lp/registry/vocabularies.zcml 2011-11-07 00:42:24 +0000
@@ -355,6 +355,17 @@
<allow interface="zope.schema.interfaces.IVocabularyFactory"/>
</securedutility>
+ <securedutility
+ name="ValidPillarOwner"
+ component="lp.registry.vocabularies.ValidPersonOrClosedTeamVocabulary"
+ provides="zope.schema.interfaces.IVocabularyFactory"
+ >
+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
+ </securedutility>
+
+ <class class="lp.registry.vocabularies.ValidPersonOrClosedTeamVocabulary">
+ <allow interface="canonical.launchpad.webapp.vocabulary.IHugeVocabulary"/>
+ </class>
<securedutility
name="ValidTeam"
=== modified file 'lib/lp/services/fields/__init__.py'
--- lib/lp/services/fields/__init__.py 2011-02-24 15:30:54 +0000
+++ lib/lp/services/fields/__init__.py 2011-11-07 00:42:24 +0000
@@ -55,6 +55,7 @@
'URIField',
'UniqueField',
'Whiteboard',
+ 'is_public_person_or_closed_team',
'is_public_person',
]
@@ -243,6 +244,7 @@
class NoneableTextLine(StrippedTextLine):
implements(INoneableTextLine)
+
# Title
# A field to capture a launchpad object title
@@ -278,7 +280,6 @@
return super(StrippableText, self).validate(value)
-
# Summary
# A field capture a Launchpad object summary
@@ -770,19 +771,19 @@
class IconImageUpload(BaseImageUpload):
dimensions = (14, 14)
- max_size = 5*1024
+ max_size = 5 * 1024
class LogoImageUpload(BaseImageUpload):
dimensions = (64, 64)
- max_size = 50*1024
+ max_size = 50 * 1024
class MugshotImageUpload(BaseImageUpload):
dimensions = (192, 192)
- max_size = 100*1024
+ max_size = 100 * 1024
class LocationField(Field):
@@ -830,6 +831,20 @@
return person.visibility == PersonVisibility.PUBLIC
+def is_public_person_or_closed_team(person):
+ """Return True if person is a Person or not an open or delegated team."""
+ from lp.registry.interfaces.person import (
+ IPerson,
+ PersonVisibility,
+ CLOSED_TEAM_POLICY,
+ )
+ if not IPerson.providedBy(person):
+ return False
+ if not person.is_team:
+ return person.visibility == PersonVisibility.PUBLIC
+ return person.subscriptionpolicy in CLOSED_TEAM_POLICY
+
+
class PrivateTeamNotAllowed(ConstraintNotSatisfied):
__doc__ = _("A private team is not allowed.")
=== modified file 'lib/lp/services/fields/tests/test_fields.py'
--- lib/lp/services/fields/tests/test_fields.py 2011-02-24 15:30:54 +0000
+++ lib/lp/services/fields/tests/test_fields.py 2011-11-07 00:42:24 +0000
@@ -17,12 +17,16 @@
from canonical.testing.layers import DatabaseFunctionalLayer
from lp.app.validators import LaunchpadValidationError
from lp.registry.interfaces.nameblacklist import INameBlacklistSet
+from lp.registry.interfaces.person import (
+ CLOSED_TEAM_POLICY,
+ OPEN_TEAM_POLICY,
+ )
from lp.services.fields import (
BaseImageUpload,
BlacklistableContentNameField,
FormattableDate,
StrippableText,
- )
+ is_public_person_or_closed_team)
from lp.testing import (
login_person,
TestCase,
@@ -159,7 +163,7 @@
class ExampleImageUpload(BaseImageUpload):
dimensions = (192, 192)
- max_size = 100*1024
+ max_size = 100 * 1024
def test_validation_corrupt_image(self):
# ValueErrors raised by PIL become LaunchpadValidationErrors.
@@ -182,3 +186,30 @@
image.filename = 'foo.jpg'
self.assertRaises(
LaunchpadValidationError, field.validate, image)
+
+
+class Test_is_person_or_closed_team(TestCaseWithFactory):
+ """ Tests for is_person_or_closed_team()."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_non_person(self):
+ self.assertFalse(is_public_person_or_closed_team(0))
+
+ def test_person(self):
+ person = self.factory.makePerson()
+ self.assertTrue(is_public_person_or_closed_team(person))
+
+ def test_open_team(self):
+ for policy in OPEN_TEAM_POLICY:
+ open_team = self.factory.makeTeam(subscription_policy=policy)
+ self.assertFalse(
+ is_public_person_or_closed_team(open_team),
+ "%s is not open" % policy)
+
+ def test_closed_team(self):
+ for policy in CLOSED_TEAM_POLICY:
+ closed_team = self.factory.makeTeam(subscription_policy=policy)
+ self.assertTrue(
+ is_public_person_or_closed_team(closed_team),
+ "%s is not closed" % policy)