Merge lp:~thumper/launchpad/branch-subscription-subscribed-by into lp:launchpad/db-devel

Proposed by Tim Penhey
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 9417
Proposed branch: lp:~thumper/launchpad/branch-subscription-subscribed-by
Merge into: lp:launchpad/db-devel
Diff against target: 1649 lines (+395/-226)
50 files modified
database/sampledata/current-dev.sql (+4/-4)
database/sampledata/current.sql (+4/-4)
database/schema/patch-2207-60-0.sql (+18/-0)
lib/canonical/launchpad/mail/commands.py (+3/-1)
lib/canonical/launchpad/scripts/tests/test_garbo.py (+0/-2)
lib/canonical/launchpad/security.py (+1/-23)
lib/lp/app/errors.py (+18/-0)
lib/lp/bugs/interfaces/bug.py (+0/-7)
lib/lp/bugs/model/bug.py (+2/-1)
lib/lp/code/browser/branch.py (+0/-12)
lib/lp/code/browser/branchsubscription.py (+7/-24)
lib/lp/code/browser/codeimport.py (+2/-1)
lib/lp/code/configure.zcml (+1/-0)
lib/lp/code/doc/branch-merge-proposal-notifications.txt (+4/-4)
lib/lp/code/doc/branch-notifications.txt (+23/-39)
lib/lp/code/doc/branch-visibility.txt (+1/-1)
lib/lp/code/doc/branch.txt (+4/-4)
lib/lp/code/doc/codeimport.txt (+1/-1)
lib/lp/code/doc/codereviewcomment.txt (+1/-1)
lib/lp/code/interfaces/branch.py (+11/-3)
lib/lp/code/interfaces/branchsubscription.py (+12/-1)
lib/lp/code/mail/tests/test_branch.py (+1/-1)
lib/lp/code/mail/tests/test_branchmergeproposal.py (+3/-3)
lib/lp/code/mail/tests/test_codehandler.py (+1/-1)
lib/lp/code/mail/tests/test_codereviewcomment.py (+2/-2)
lib/lp/code/model/branch.py (+14/-5)
lib/lp/code/model/branchnamespace.py (+4/-2)
lib/lp/code/model/branchsubscription.py (+12/-1)
lib/lp/code/model/tests/test_branch.py (+14/-9)
lib/lp/code/model/tests/test_branchcollection.py (+9/-5)
lib/lp/code/model/tests/test_branchjob.py (+8/-4)
lib/lp/code/model/tests/test_branchmergeproposals.py (+20/-16)
lib/lp/code/model/tests/test_branchsubscription.py (+115/-0)
lib/lp/code/scripts/tests/test_scan_branches.py (+2/-1)
lib/lp/code/scripts/tests/test_sendbranchmail.py (+4/-2)
lib/lp/code/security.py (+37/-0)
lib/lp/code/stories/branches/xx-branch-edit.txt (+1/-1)
lib/lp/code/stories/branches/xx-branchmergeproposals.txt (+2/-2)
lib/lp/code/stories/branches/xx-person-branches.txt (+1/-1)
lib/lp/code/stories/branches/xx-subscribing-branches.txt (+7/-24)
lib/lp/code/stories/webservice/xx-branchsubscription.txt (+2/-0)
lib/lp/code/tests/test_branch.py (+1/-1)
lib/lp/codehosting/scanner/tests/test_bzrsync.py (+1/-1)
lib/lp/codehosting/scanner/tests/test_email.py (+6/-3)
lib/lp/codehosting/tests/test_jobs.py (+1/-1)
lib/lp/registry/browser/tests/packaging-views.txt (+1/-1)
lib/lp/registry/browser/tests/private-team-creation-views.txt (+1/-1)
lib/lp/registry/doc/private-team-roles.txt (+2/-2)
lib/lp/registry/model/distroseries.py (+1/-1)
lib/lp/testing/factory.py (+5/-2)
To merge this branch: bzr merge lp:~thumper/launchpad/branch-subscription-subscribed-by
Reviewer Review Type Date Requested Status
Stuart Bishop (community) db Approve
Curtis Hovey (community) release-critical code Approve
Björn Tillenius db Pending
Review via email: mp+25937@code.launchpad.net

Commit message

Allow people to subscribe teams they are not a member of, and record them as the user who subscribed the team.

Description of the change

Add a subscribed_by to the BranchSubscription table.

This work also changes the permissions and abilities for subscribing and unsubscribing from branches.

Work in progress for now.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :
Download full text (3.2 KiB)

> === modified file 'lib/lp/code/browser/branch.py'
> --- lib/lp/code/browser/branch.py 2010-05-20 04:01:34 +0000
> +++ lib/lp/code/browser/branch.py 2010-05-27 04:47:25 +0000
> @@ -354,6 +354,7 @@
>
> def initialize(self):
> self.notices = []
> + # TODO: FIXME - this is almost certainly not what we want.
> self._add_subscription_notice()

What do you want to do here?

> === modified file 'lib/lp/code/browser/branchsubscription.py'
> --- lib/lp/code/browser/branchsubscription.py 2010-04-20 01:21:10 +0000
> +++ lib/lp/code/browser/branchsubscription.py 2010-05-27 04:47:25 +0000
> ...
>
> @@ -209,7 +210,7 @@
> subscription = self.context.getSubscription(person)
> if subscription is None:
> self.context.subscribe(
> - person, notification_level, max_diff_lines, review_level)
> + person, notification_level, max_diff_lines, review_level, self.user)

Wrap the code at 78 characters.

> === modified file 'lib/lp/code/model/branchsubscription.py'
> --- lib/lp/code/model/branchsubscription.py 2009-06-25 04:06:00 +0000
> +++ lib/lp/code/model/branchsubscription.py 2010-05-27 04:47:25 +0000
> @@ -41,6 +41,9 @@
> notNull=False, default=DEFAULT)
> review_level = EnumCol(enum=CodeReviewNotificationLevel,
> notNull=True, default=DEFAULT)
> + subscribed_by = ForeignKey(
> + dbName='subscribed_by', foreignKey='Person',
> + storm_validator=validate_person_not_private_membership, notNull=True)

I was suggested in a recent review to drop the dbName since it is identical
to the column name. I did so, but now I ponder explicit is better than
implicit again.

> === modified file 'lib/lp/registry/browser/tests/packaging-views.txt'
> --- lib/lp/registry/browser/tests/packaging-views.txt 2010-04-16 18:00:31 +0000
> +++ lib/lp/registry/browser/tests/packaging-views.txt 2010-05-27 04:47:25 +0000
> @@ -350,5 +350,5 @@
> cnews
> libstdc++
> linux-source-2.6.15
> + hot
> thunderbird
> - hot

What caused this? This look like me from last week?

> === modified file 'lib/lp/registry/model/distroseries.py'
> --- lib/lp/registry/model/distroseries.py 2010-05-12 23:23:19 +0000
> +++ lib/lp/registry/model/distroseries.py 2010-05-27 04:47:25 +0000
> @@ -332,7 +332,7 @@
> origin = SQL(joins)
> condition = SQL(conditions + "AND packaging.id IS NULL")
> results = IStore(self).using(origin).find(find_spec, condition)
> - results = results.order_by('score DESC')
> + results = results.order_by('score DESC', SourcePackageName.name)
> return [{
> 'package': SourcePackage(
> sourcepackagename=spn, distroseries=self),

This is me from last week.

> === modified file 'lib/lp/testing/factory.py'
> --- lib/lp/testing/factory.py 2010-05-25 13:13:02 +0000
> +++ lib/lp/testing/factory.py 2010-05-27 04:47:25 +0000
> @@ -986,7 +986,7 @@
>
> return proposal
>
> - def makeBranchSubscription(self, branch=None, person=None):
> + def makeBranchSubscription(self, branch=None, person=None, subscri...

Read more...

review: Needs Information (release-critical)
Revision history for this message
Tim Penhey (thumper) wrote :

On Fri, 28 May 2010 13:58:22 you wrote:
> Review: Needs Information release-critical

Thanks Curtis,

> > === modified file 'lib/lp/code/browser/branch.py'
> > --- lib/lp/code/browser/branch.py 2010-05-20 04:01:34 +0000
> > +++ lib/lp/code/browser/branch.py 2010-05-27 04:47:25 +0000
> > @@ -354,6 +354,7 @@
> >
> > def initialize(self):
> > self.notices = []
> >
> > + # TODO: FIXME - this is almost certainly not what we want.
> >
> > self._add_subscription_notice()
>
> What do you want to do here?

Haha, that was my own personal reminder in that I *think* that the following
function call isn't needed, nor called, nor would do what is really wanted,
and it was a reminder to check.

> > === modified file 'lib/lp/code/model/branchsubscription.py'
> > --- lib/lp/code/model/branchsubscription.py 2009-06-25 04:06:00 +0000
> > +++ lib/lp/code/model/branchsubscription.py 2010-05-27 04:47:25 +0000
> > @@ -41,6 +41,9 @@
> >
> > notNull=False, default=DEFAULT)
> >
> > review_level = EnumCol(enum=CodeReviewNotificationLevel,
> >
> > notNull=True, default=DEFAULT)
> >
> > + subscribed_by = ForeignKey(
> > + dbName='subscribed_by', foreignKey='Person',
> > + storm_validator=validate_person_not_private_membership,
> > notNull=True)
>
> I was suggested in a recent review to drop the dbName since it is identical
> to the column name. I did so, but now I ponder explicit is better than
> implicit again.

I'm relatively unconcerned about this point. I think at one stage the dbName
was required for ForeignKey but now may not be needed. I'm happy to remove
the dbName param if you think it best.

> > === modified file 'lib/lp/registry/browser/tests/packaging-views.txt'
> > --- lib/lp/registry/browser/tests/packaging-views.txt 2010-04-16 18:00:31
> > +0000 +++ lib/lp/registry/browser/tests/packaging-views.txt 2010-05-27
> > 04:47:25 +0000 @@ -350,5 +350,5 @@
> >
> > cnews
> > libstdc++
> > linux-source-2.6.15
> >
> > + hot
> >
> > thunderbird
> >
> > - hot
>
> What caused this? This look like me from last week?

Caused by someone.

> > === modified file 'lib/lp/registry/model/distroseries.py'
> > --- lib/lp/registry/model/distroseries.py 2010-05-12 23:23:19 +0000
> > +++ lib/lp/registry/model/distroseries.py 2010-05-27 04:47:25 +0000
> > @@ -332,7 +332,7 @@
> >
> > origin = SQL(joins)
> > condition = SQL(conditions + "AND packaging.id IS NULL")
> > results = IStore(self).using(origin).find(find_spec, condition)
> >
> > - results = results.order_by('score DESC')
> > + results = results.order_by('score DESC', SourcePackageName.name)
> >
> > return [{
> >
> > 'package': SourcePackage(
> >
> > sourcepackagename=spn, distroseries=self),
>
> This is me from last week.

Yes this was the source of it. It was failing locally for me consistently.

I'm back on to this branch now to add the extra tests and model validation.

Revision history for this message
Tim Penhey (thumper) wrote :

On Fri, 28 May 2010 13:58:22 you wrote:
> Review: Needs Information release-critical
> > === modified file 'lib/lp/code/browser/branchsubscription.py'
> Wrap the code at 78 characters.

Done.

> > === modified file 'lib/lp/testing/factory.py'
> Wrap the code at 78 characters.

Done.

Also added the checking.

=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-05-27 04:44:39 +0000
+++ lib/canonical/launchpad/security.py 2010-05-28 04:18:17 +0000
@@ -24,8 +24,6 @@
24 IBranch, user_has_special_branch_access)24 IBranch, user_has_special_branch_access)
25from lp.code.interfaces.branchmergeproposal import (25from lp.code.interfaces.branchmergeproposal import (
26 IBranchMergeProposal)26 IBranchMergeProposal)
27from lp.code.interfaces.branchsubscription import (
28 IBranchSubscription)
29from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe27from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
30from lp.code.interfaces.sourcepackagerecipebuild import (28from lp.code.interfaces.sourcepackagerecipebuild import (
31 ISourcePackageRecipeBuild)29 ISourcePackageRecipeBuild)
@@ -61,8 +59,7 @@
61from lp.services.worlddata.interfaces.language import ILanguage, ILanguageSet59from lp.services.worlddata.interfaces.language import ILanguage, ILanguageSet
62from lp.translations.interfaces.languagepack import ILanguagePack60from lp.translations.interfaces.languagepack import ILanguagePack
63from canonical.launchpad.interfaces.launchpad import (61from canonical.launchpad.interfaces.launchpad import (
64 IBazaarApplication, IHasBug, IHasDrivers, ILaunchpadCelebrities,62 IHasBug, IHasDrivers, ILaunchpadCelebrities, IPersonRoles)
65 IPersonRoles)
66from lp.registry.interfaces.role import IHasOwner63from lp.registry.interfaces.role import IHasOwner
67from lp.registry.interfaces.location import IPersonLocation64from lp.registry.interfaces.location import IPersonLocation
68from lp.registry.interfaces.mailinglist import IMailingListSet65from lp.registry.interfaces.mailinglist import IMailingListSet
@@ -1733,26 +1730,6 @@
1733 self.obj.distribution).checkAuthenticated(user))1730 self.obj.distribution).checkAuthenticated(user))
17341731
17351732
1736class BranchSubscriptionEdit(AuthorizationBase):
1737 permission = 'launchpad.Edit'
1738 usedfor = IBranchSubscription
1739
1740 def checkAuthenticated(self, user):
1741 """Is the user able to edit a branch subscription?
1742
1743 Any team member can edit a branch subscription for their team.
1744 Launchpad Admins can also edit any branch subscription.
1745 """
1746 return (user.inTeam(self.obj.person) or
1747 user.inTeam(self.obj.subscribed_by) or
1748 user.in_admin or
1749 user.in_bazaar_experts)
1750
1751
1752class BranchSubscriptionView(BranchSubscriptionEdit):
1753 permission = 'launchpad.View'
1754
1755
1756class BranchMergeProposalView(AuthorizationBase):1733class BranchMergeProposalView(AuthorizationBase):
1757 permission = 'launchpad.View'1734 permission = 'launchpad.View'
1758 usedfor = IBranchMergeProposal1735 usedfor = IBranchMergeProposal
17591736
=== modified file 'lib/lp/code/browser/branchsubscription.py'
--- lib/lp/code/browser/branchsubscription.py 2010-05-25 03:13:25 +0000
+++ lib/lp/code/browser/branchsubscription.py 2010-05-28 09:31:03 +0000
@@ -12,10 +12,9 @@
12 'BranchSubscriptionPrimaryContext',12 'BranchSubscriptionPrimaryContext',
13 ]13 ]
1414
15from zope.component import getUtility15
16from zope.interface import implements16from zope.interface import implements
1717
18from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
19from canonical.launchpad.webapp import (18from canonical.launchpad.webapp import (
20 action, canonical_url, LaunchpadEditFormView, LaunchpadFormView,19 action, canonical_url, LaunchpadEditFormView, LaunchpadFormView,
21 LaunchpadView)20 LaunchpadView)
@@ -210,7 +209,8 @@
210 subscription = self.context.getSubscription(person)209 subscription = self.context.getSubscription(person)
211 if subscription is None:210 if subscription is None:
212 self.context.subscribe(211 self.context.subscribe(
213 person, notification_level, max_diff_lines, review_level, self.user)212 person, notification_level, max_diff_lines, review_level,
213 self.user)
214214
215 self.add_notification_message(215 self.add_notification_message(
216 '%s has been subscribed to this branch with: '216 '%s has been subscribed to this branch with: '
217217
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2010-05-07 04:53:47 +0000
+++ lib/lp/code/configure.zcml 2010-05-28 04:18:17 +0000
@@ -11,6 +11,7 @@
11 xmlns:webservice="http://namespaces.canonical.com/webservice"11 xmlns:webservice="http://namespaces.canonical.com/webservice"
12 i18n_domain="launchpad">12 i18n_domain="launchpad">
13 <include package=".browser"/>13 <include package=".browser"/>
14 <authorizations module="lp.code.security" />
1415
15 <!-- Branch Merge Queue -->16 <!-- Branch Merge Queue -->
1617
1718
=== modified file 'lib/lp/code/interfaces/branchsubscription.py'
--- lib/lp/code/interfaces/branchsubscription.py 2010-05-25 01:24:43 +0000
+++ lib/lp/code/interfaces/branchsubscription.py 2010-05-28 04:18:17 +0000
@@ -21,7 +21,8 @@
21from lp.code.interfaces.branch import IBranch21from lp.code.interfaces.branch import IBranch
22from canonical.launchpad.fields import ParticipatingPersonChoice22from canonical.launchpad.fields import ParticipatingPersonChoice
23from lazr.restful.declarations import (23from lazr.restful.declarations import (
24 export_as_webservice_entry, exported)24 REQUEST_USER, call_with, export_as_webservice_entry,
25 export_read_operation, exported)
25from lazr.restful.fields import Reference26from lazr.restful.fields import Reference
2627
2728
@@ -78,3 +79,8 @@
78 title=_('Subscribed by'), required=True,79 title=_('Subscribed by'), required=True,
79 vocabulary='ValidPersonOrTeam', readonly=True,80 vocabulary='ValidPersonOrTeam', readonly=True,
80 description=_("The person who created this subscription.")))81 description=_("The person who created this subscription.")))
82
83 @call_with(user=REQUEST_USER)
84 @export_read_operation()
85 def canBeUnsubscribedByUser(user):
86 """Can the user unsubscribe the subscriber from the branch?"""
8187
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2010-05-25 04:37:11 +0000
+++ lib/lp/code/model/branch.py 2010-05-28 09:04:16 +0000
@@ -45,6 +45,7 @@
45from canonical.launchpad.webapp.interfaces import (45from canonical.launchpad.webapp.interfaces import (
46 IStoreSelector, MAIN_STORE, SLAVE_FLAVOR)46 IStoreSelector, MAIN_STORE, SLAVE_FLAVOR)
4747
48from lp.app.errors import UserCannotUnsubscribePerson
48from lp.code.bzr import (49from lp.code.bzr import (
49 BranchFormat, ControlFormat, CURRENT_BRANCH_FORMATS,50 BranchFormat, ControlFormat, CURRENT_BRANCH_FORMATS,
50 CURRENT_REPOSITORY_FORMATS, RepositoryFormat)51 CURRENT_REPOSITORY_FORMATS, RepositoryFormat)
@@ -705,9 +706,16 @@
705 def unsubscribe(self, person, unsubscribed_by):706 def unsubscribe(self, person, unsubscribed_by):
706 """See `IBranch`."""707 """See `IBranch`."""
707 subscription = self.getSubscription(person)708 subscription = self.getSubscription(person)
709 if subscription is None:
710 # Silent success seems order of the day (like bugs).
711 return
712 if not subscription.canBeUnsubscribedByUser(unsubscribed_by):
713 raise UserCannotUnsubscribePerson(
714 '%s does not have permission to unsubscribe %s.' % (
715 unsubscribed_by.displayname,
716 person.displayname))
708 store = Store.of(subscription)717 store = Store.of(subscription)
709 assert subscription is not None, "User is not subscribed."718 store.remove(subscription)
710 BranchSubscription.delete(subscription.id)
711 store.flush()719 store.flush()
712720
713 def getBranchRevision(self, sequence=None, revision=None,721 def getBranchRevision(self, sequence=None, revision=None,
714722
=== modified file 'lib/lp/code/model/branchsubscription.py'
--- lib/lp/code/model/branchsubscription.py 2010-05-25 04:37:11 +0000
+++ lib/lp/code/model/branchsubscription.py 2010-05-28 04:18:17 +0000
@@ -13,13 +13,14 @@
13from canonical.database.constants import DEFAULT13from canonical.database.constants import DEFAULT
14from canonical.database.sqlbase import SQLBase14from canonical.database.sqlbase import SQLBase
15from canonical.database.enumcol import EnumCol15from canonical.database.enumcol import EnumCol
1616from canonical.launchpad.interfaces.launchpad import IPersonRoles
17from lp.code.enums import (17from lp.code.enums import (
18 BranchSubscriptionDiffSize, BranchSubscriptionNotificationLevel,18 BranchSubscriptionDiffSize, BranchSubscriptionNotificationLevel,
19 CodeReviewNotificationLevel)19 CodeReviewNotificationLevel)
20from lp.code.interfaces.branchsubscription import IBranchSubscription20from lp.code.interfaces.branchsubscription import IBranchSubscription
21from lp.code.interfaces.branch import IBranchNavigationMenu21from lp.code.interfaces.branch import IBranchNavigationMenu
22from lp.code.interfaces.branchtarget import IHasBranchTarget22from lp.code.interfaces.branchtarget import IHasBranchTarget
23from lp.code.security import BranchSubscriptionEdit
23from lp.registry.interfaces.person import (24from lp.registry.interfaces.person import (
24 validate_person_not_private_membership)25 validate_person_not_private_membership)
2526
@@ -49,3 +50,10 @@
49 def target(self):50 def target(self):
50 """See `IHasBranchTarget`."""51 """See `IHasBranchTarget`."""
51 return self.branch.target52 return self.branch.target
53
54 def canBeUnsubscribedByUser(self, user):
55 """See `IBranchSubscription`."""
56 if user is None:
57 return False
58 permission_check = BranchSubscriptionEdit(self)
59 return permission_check.checkAuthenticated(IPersonRoles(user))
5260
=== added file 'lib/lp/code/model/tests/test_branchsubscription.py'
--- lib/lp/code/model/tests/test_branchsubscription.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/model/tests/test_branchsubscription.py 2010-05-28 09:04:16 +0000
@@ -0,0 +1,115 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the BranchSubscrptions model object.."""
5
6from __future__ import with_statement
7
8__metaclass__ = type
9
10import unittest
11
12from canonical.testing.layers import DatabaseFunctionalLayer
13from lp.app.errors import UserCannotUnsubscribePerson
14from lp.code.enums import (
15 BranchSubscriptionNotificationLevel, CodeReviewNotificationLevel)
16from lp.testing import TestCaseWithFactory, person_logged_in
17
18
19class TestBranchSubscriptions(TestCaseWithFactory):
20 """Tests relating to branch subscriptions in general."""
21
22 layer = DatabaseFunctionalLayer
23
24 def test_subscribed_by_set(self):
25 """The user subscribing is recorded along the subscriber."""
26 subscriber = self.factory.makePerson()
27 subscribed_by = self.factory.makePerson()
28 branch = self.factory.makeAnyBranch()
29 subscription = branch.subscribe(
30 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,
31 CodeReviewNotificationLevel.NOEMAIL, subscribed_by)
32 self.assertEqual(subscriber, subscription.person)
33 self.assertEqual(subscribed_by, subscription.subscribed_by)
34
35 def test_unsubscribe(self):
36 """Test unsubscribing by the subscriber."""
37 subscription = self.factory.makeBranchSubscription()
38 subscriber = subscription.person
39 branch = subscription.branch
40 branch.unsubscribe(subscriber, subscriber)
41 self.assertFalse(branch.hasSubscription(subscriber))
42
43 def test_unsubscribe_by_subscriber(self):
44 """Test unsubscribing by the person who subscribed the user."""
45 subscribed_by = self.factory.makePerson()
46 subscription = self.factory.makeBranchSubscription(
47 subscribed_by=subscribed_by)
48 subscriber = subscription.person
49 branch = subscription.branch
50 branch.unsubscribe(subscriber, subscribed_by)
51 self.assertFalse(branch.hasSubscription(subscriber))
52
53 def test_unsubscribe_by_unauthorized(self):
54 """Test unsubscribing someone you shouldn't be able to."""
55 subscription = self.factory.makeBranchSubscription()
56 branch = subscription.branch
57 self.assertRaises(
58 UserCannotUnsubscribePerson,
59 branch.unsubscribe,
60 subscription.person,
61 self.factory.makePerson())
62
63
64class TestBranchSubscriptionCanBeUnsubscribedbyUser(TestCaseWithFactory):
65 """Tests for BranchSubscription.canBeUnsubscribedByUser."""
66
67 layer = DatabaseFunctionalLayer
68
69 def test_none(self):
70 """None for a user always returns False."""
71 subscription = self.factory.makeBranchSubscription()
72 self.assertFalse(subscription.canBeUnsubscribedByUser(None))
73
74 def test_self_subscriber(self):
75 """The subscriber has permission to unsubscribe."""
76 subscription = self.factory.makeBranchSubscription()
77 self.assertTrue(
78 subscription.canBeUnsubscribedByUser(subscription.person))
79
80 def test_non_subscriber_fails(self):
81 """An unrelated person can't unsubscribe a user."""
82 subscription = self.factory.makeBranchSubscription()
83 editor = self.factory.makePerson()
84 self.assertFalse(subscription.canBeUnsubscribedByUser(editor))
85
86 def test_subscribed_by(self):
87 """If a user subscribes someone else, the user can unsubscribe."""
88 subscribed_by = self.factory.makePerson()
89 subscriber = self.factory.makePerson()
90 subscription = self.factory.makeBranchSubscription(
91 person=subscriber, subscribed_by=subscribed_by)
92 self.assertTrue(subscription.canBeUnsubscribedByUser(subscribed_by))
93
94 def test_team_member_can_unsubscribe(self):
95 """Any team member can unsubscribe the team from a branch."""
96 team = self.factory.makeTeam()
97 member = self.factory.makePerson()
98 with person_logged_in(team.teamowner):
99 team.addMember(member, team.teamowner)
100 subscription = self.factory.makeBranchSubscription(
101 person=team, subscribed_by=team.teamowner)
102 self.assertTrue(subscription.canBeUnsubscribedByUser(member))
103
104 def test_team_subscriber_can_unsubscribe(self):
105 """A team can be unsubscribed by the subscriber even if they are not a
106 member."""
107 team = self.factory.makeTeam()
108 subscribed_by = self.factory.makePerson()
109 subscription = self.factory.makeBranchSubscription(
110 person=team, subscribed_by=subscribed_by)
111 self.assertTrue(subscription.canBeUnsubscribedByUser(subscribed_by))
112
113
114def test_suite():
115 return unittest.TestLoader().loadTestsFromName(__name__)
0116
=== added file 'lib/lp/code/security.py'
--- lib/lp/code/security.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/security.py 2010-05-28 04:18:17 +0000
@@ -0,0 +1,37 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Security adapters for the code module."""
5
6__metaclass__ = type
7__all__ = [
8 'BranchSubscriptionEdit',
9 'BranchSubscriptionView',
10 ]
11
12from canonical.launchpad.security import AuthorizationBase
13
14from lp.code.interfaces.branchsubscription import IBranchSubscription
15
16
17
18class BranchSubscriptionEdit(AuthorizationBase):
19 permission = 'launchpad.Edit'
20 usedfor = IBranchSubscription
21
22 def checkAuthenticated(self, user):
23 """Is the user able to edit a branch subscription?
24
25 Any team member can edit a branch subscription for their team.
26 Launchpad Admins can also edit any branch subscription.
27 """
28 return (user.inTeam(self.obj.person) or
29 user.inTeam(self.obj.subscribed_by) or
30 user.in_admin or
31 user.in_bazaar_experts)
32
33
34class BranchSubscriptionView(BranchSubscriptionEdit):
35 permission = 'launchpad.View'
36
37
038
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-05-25 01:24:43 +0000
+++ lib/lp/testing/factory.py 2010-05-28 09:31:03 +0000
@@ -986,7 +986,8 @@
986986
987 return proposal987 return proposal
988988
989 def makeBranchSubscription(self, branch=None, person=None, subscribed_by=None):989 def makeBranchSubscription(self, branch=None, person=None,
990 subscribed_by=None):
990 """Create a BranchSubscription.991 """Create a BranchSubscription.
991992
992 :param branch_title: The title to use for the created Branch993 :param branch_title: The title to use for the created Branch
Revision history for this message
Tim Penhey (thumper) wrote :

On Fri, 28 May 2010 13:58:22 you wrote:
> Review: Needs Information release-critical
>
> > === modified file 'lib/lp/code/browser/branch.py'
> > --- lib/lp/code/browser/branch.py 2010-05-20 04:01:34 +0000
> > +++ lib/lp/code/browser/branch.py 2010-05-27 04:47:25 +0000
> > @@ -354,6 +354,7 @@
> >
> > def initialize(self):
> > self.notices = []
> >
> > + # TODO: FIXME - this is almost certainly not what we want.
> >
> > self._add_subscription_notice()
>
> What do you want to do here?

Looks like it isn't used:

=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2010-05-25 03:13:25 +0000
+++ lib/lp/code/browser/branch.py 2010-05-28 09:43:31 +0000
@@ -354,19 +354,6 @@

     def initialize(self):
         self.notices = []
- # TODO: FIXME - this is almost certainly not what we want.
- self._add_subscription_notice()
-
- def _add_subscription_notice(self):
- """Add the appropriate notice after posting the subscription form."""
- if self.user and self.request.method == 'POST':
- newsub = self.request.form.get('subscribe', None)
- if newsub == 'Subscribe':
- self.context.subscribe(self.user, self.user)
- self.notices.append("You have subscribed to this branch.")
- elif newsub == 'Unsubscribe':
- self.context.unsubscribe(self.user, self.user)
- self.notices.append("You have unsubscribed from this
branch.")

     def user_is_subscribed(self):
         """Is the current user subscribed to this branch?"""

Revision history for this message
Curtis Hovey (sinzui) wrote :

Thanks for addressing this issue Tim. This branch is good to land.

review: Approve (release-critical code)
Revision history for this message
Stuart Bishop (stub) wrote :

Looks good. patch-2207-60-0.sql

review: Approve (db)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql 2010-05-27 22:18:16 +0000
+++ database/sampledata/current-dev.sql 2010-05-29 09:01:05 +0000
@@ -3131,10 +3131,10 @@
31313131
3132ALTER TABLE branchsubscription DISABLE TRIGGER ALL;3132ALTER TABLE branchsubscription DISABLE TRIGGER ALL;
31333133
3134INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (1, 12, 20, '2006-10-16 18:31:43.079375', 1, NULL, 0);3134INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (1, 12, 20, '2006-10-16 18:31:43.079375', 1, NULL, 0, 12);
3135INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (2, 12, 24, '2006-10-16 18:31:43.080236', 1, NULL, 0);3135INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (2, 12, 24, '2006-10-16 18:31:43.080236', 1, NULL, 0, 12);
3136INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (4, 64, 29, '2007-05-28 02:41:07.938677', 1, NULL, 0);3136INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (4, 64, 29, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64);
3137INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (5, 64, 30, '2007-05-28 02:41:07.938677', 1, NULL, 0);3137INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (5, 64, 30, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64);
31383138
31393139
3140ALTER TABLE branchsubscription ENABLE TRIGGER ALL;3140ALTER TABLE branchsubscription ENABLE TRIGGER ALL;
31413141
=== modified file 'database/sampledata/current.sql'
--- database/sampledata/current.sql 2010-05-27 22:18:16 +0000
+++ database/sampledata/current.sql 2010-05-29 09:01:05 +0000
@@ -3089,10 +3089,10 @@
30893089
3090ALTER TABLE branchsubscription DISABLE TRIGGER ALL;3090ALTER TABLE branchsubscription DISABLE TRIGGER ALL;
30913091
3092INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (1, 12, 20, '2006-10-16 18:31:43.079375', 1, NULL, 0);3092INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (1, 12, 20, '2006-10-16 18:31:43.079375', 1, NULL, 0, 12);
3093INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (2, 12, 24, '2006-10-16 18:31:43.080236', 1, NULL, 0);3093INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (2, 12, 24, '2006-10-16 18:31:43.080236', 1, NULL, 0, 12);
3094INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (4, 64, 29, '2007-05-28 02:41:07.938677', 1, NULL, 0);3094INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (4, 64, 29, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64);
3095INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level) VALUES (5, 64, 30, '2007-05-28 02:41:07.938677', 1, NULL, 0);3095INSERT INTO branchsubscription (id, person, branch, date_created, notification_level, max_diff_lines, review_level, subscribed_by) VALUES (5, 64, 30, '2007-05-28 02:41:07.938677', 1, NULL, 0, 64);
30963096
30973097
3098ALTER TABLE branchsubscription ENABLE TRIGGER ALL;3098ALTER TABLE branchsubscription ENABLE TRIGGER ALL;
30993099
=== added file 'database/schema/patch-2207-60-0.sql'
--- database/schema/patch-2207-60-0.sql 1970-01-01 00:00:00 +0000
+++ database/schema/patch-2207-60-0.sql 2010-05-29 09:01:05 +0000
@@ -0,0 +1,18 @@
1-- Copyright 2010 Canonical Ltd. This software is licensed under the
2-- GNU Affero General Public License version 3 (see the file LICENSE).
3
4SET client_min_messages=ERROR;
5
6ALTER TABLE BranchSubscription
7 ADD COLUMN subscribed_by integer REFERENCES Person;
8
9UPDATE BranchSubscription
10SET subscribed_by = person;
11
12ALTER TABLE BranchSubscription ALTER COLUMN subscribed_by SET NOT NULL;
13
14-- Index needed for person merging.
15CREATE INDEX branchsubscription__subscribed_by__idx
16 ON BranchSubscription(subscribed_by);
17
18INSERT INTO LaunchpadDatabaseRevision VALUES (2207, 60, 0);
019
=== modified file 'lib/canonical/launchpad/mail/commands.py'
--- lib/canonical/launchpad/mail/commands.py 2010-04-16 15:06:55 +0000
+++ lib/canonical/launchpad/mail/commands.py 2010-05-29 09:01:05 +0000
@@ -23,7 +23,7 @@
23 IDistributionSourcePackage, EmailProcessingError,23 IDistributionSourcePackage, EmailProcessingError,
24 NotFoundError, CreateBugParams, IPillarNameSet,24 NotFoundError, CreateBugParams, IPillarNameSet,
25 BugTargetNotFound, IProjectGroup, ISourcePackage, IProductSeries,25 BugTargetNotFound, IProjectGroup, ISourcePackage, IProductSeries,
26 BugTaskStatus, UserCannotUnsubscribePerson)26 BugTaskStatus)
27from lazr.lifecycle.event import (27from lazr.lifecycle.event import (
28 ObjectModifiedEvent, ObjectCreatedEvent)28 ObjectModifiedEvent, ObjectCreatedEvent)
29from lazr.lifecycle.interfaces import (29from lazr.lifecycle.interfaces import (
@@ -34,6 +34,8 @@
34from canonical.launchpad.validators.name import valid_name34from canonical.launchpad.validators.name import valid_name
35from canonical.launchpad.webapp.authorization import check_permission35from canonical.launchpad.webapp.authorization import check_permission
3636
37from lp.app.errors import UserCannotUnsubscribePerson
38
3739
38def normalize_arguments(string_args):40def normalize_arguments(string_args):
39 """Normalizes the string arguments.41 """Normalizes the string arguments.
4042
=== modified file 'lib/canonical/launchpad/scripts/tests/test_garbo.py'
--- lib/canonical/launchpad/scripts/tests/test_garbo.py 2010-04-23 08:33:14 +0000
+++ lib/canonical/launchpad/scripts/tests/test_garbo.py 2010-05-29 09:01:05 +0000
@@ -19,7 +19,6 @@
1919
20from canonical.config import config20from canonical.config import config
21from canonical.database.constants import THIRTY_DAYS_AGO, UTC_NOW21from canonical.database.constants import THIRTY_DAYS_AGO, UTC_NOW
22from canonical.launchpad.database.emailaddress import EmailAddress
23from canonical.launchpad.database.message import Message22from canonical.launchpad.database.message import Message
24from canonical.launchpad.database.oauth import OAuthNonce23from canonical.launchpad.database.oauth import OAuthNonce
25from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce24from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce
@@ -42,7 +41,6 @@
42from lp.code.model.branchjob import BranchJob, BranchUpgradeJob41from lp.code.model.branchjob import BranchJob, BranchUpgradeJob
43from lp.code.model.codeimportresult import CodeImportResult42from lp.code.model.codeimportresult import CodeImportResult
44from lp.registry.interfaces.person import IPersonSet, PersonCreationRationale43from lp.registry.interfaces.person import IPersonSet, PersonCreationRationale
45from lp.registry.model.person import Person
46from lp.services.job.model.job import Job44from lp.services.job.model.job import Job
4745
4846
4947
=== modified file 'lib/canonical/launchpad/security.py'
--- lib/canonical/launchpad/security.py 2010-05-26 08:54:27 +0000
+++ lib/canonical/launchpad/security.py 2010-05-29 09:01:05 +0000
@@ -24,8 +24,6 @@
24 IBranch, user_has_special_branch_access)24 IBranch, user_has_special_branch_access)
25from lp.code.interfaces.branchmergeproposal import (25from lp.code.interfaces.branchmergeproposal import (
26 IBranchMergeProposal)26 IBranchMergeProposal)
27from lp.code.interfaces.branchsubscription import (
28 IBranchSubscription)
29from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe27from lp.code.interfaces.sourcepackagerecipe import ISourcePackageRecipe
30from lp.code.interfaces.sourcepackagerecipebuild import (28from lp.code.interfaces.sourcepackagerecipebuild import (
31 ISourcePackageRecipeBuild)29 ISourcePackageRecipeBuild)
@@ -63,8 +61,7 @@
63from lp.services.worlddata.interfaces.language import ILanguage, ILanguageSet61from lp.services.worlddata.interfaces.language import ILanguage, ILanguageSet
64from lp.translations.interfaces.languagepack import ILanguagePack62from lp.translations.interfaces.languagepack import ILanguagePack
65from canonical.launchpad.interfaces.launchpad import (63from canonical.launchpad.interfaces.launchpad import (
66 IBazaarApplication, IHasBug, IHasDrivers, ILaunchpadCelebrities,64 IHasBug, IHasDrivers, ILaunchpadCelebrities, IPersonRoles)
67 IPersonRoles)
68from lp.registry.interfaces.role import IHasOwner65from lp.registry.interfaces.role import IHasOwner
69from lp.registry.interfaces.location import IPersonLocation66from lp.registry.interfaces.location import IPersonLocation
70from lp.registry.interfaces.mailinglist import IMailingListSet67from lp.registry.interfaces.mailinglist import IMailingListSet
@@ -1749,25 +1746,6 @@
1749 self.obj.distribution).checkAuthenticated(user))1746 self.obj.distribution).checkAuthenticated(user))
17501747
17511748
1752class BranchSubscriptionEdit(AuthorizationBase):
1753 permission = 'launchpad.Edit'
1754 usedfor = IBranchSubscription
1755
1756 def checkAuthenticated(self, user):
1757 """Is the user able to edit a branch subscription?
1758
1759 Any team member can edit a branch subscription for their team.
1760 Launchpad Admins can also edit any branch subscription.
1761 """
1762 return (user.inTeam(self.obj.person) or
1763 user.in_admin or
1764 user.in_bazaar_experts)
1765
1766
1767class BranchSubscriptionView(BranchSubscriptionEdit):
1768 permission = 'launchpad.View'
1769
1770
1771class BranchMergeProposalView(AuthorizationBase):1749class BranchMergeProposalView(AuthorizationBase):
1772 permission = 'launchpad.View'1750 permission = 'launchpad.View'
1773 usedfor = IBranchMergeProposal1751 usedfor = IBranchMergeProposal
17741752
=== added file 'lib/lp/app/errors.py'
--- lib/lp/app/errors.py 1970-01-01 00:00:00 +0000
+++ lib/lp/app/errors.py 2010-05-29 09:01:05 +0000
@@ -0,0 +1,18 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Cross application type errors for launchpad."""
5
6__metaclass__ = type
7__all__ = [
8 'UserCannotUnsubscribePerson',
9 ]
10
11from zope.security.interfaces import Unauthorized
12
13from lazr.restful.declarations import webservice_error
14
15
16class UserCannotUnsubscribePerson(Unauthorized):
17 """User does not have persmisson to unsubscribe person or team."""
18 webservice_error(401)
019
=== modified file 'lib/lp/bugs/interfaces/bug.py'
--- lib/lp/bugs/interfaces/bug.py 2010-04-29 17:49:19 +0000
+++ lib/lp/bugs/interfaces/bug.py 2010-05-29 09:01:05 +0000
@@ -20,7 +20,6 @@
20 'IProjectGroupBugAddForm',20 'IProjectGroupBugAddForm',
21 'InvalidBugTargetType',21 'InvalidBugTargetType',
22 'InvalidDuplicateValue',22 'InvalidDuplicateValue',
23 'UserCannotUnsubscribePerson',
24 ]23 ]
2524
26from zope.component import getUtility25from zope.component import getUtility
@@ -28,7 +27,6 @@
28from zope.schema import (27from zope.schema import (
29 Bool, Bytes, Choice, Datetime, Int, List, Object, Text, TextLine)28 Bool, Bytes, Choice, Datetime, Int, List, Object, Text, TextLine)
30from zope.schema.vocabulary import SimpleVocabulary29from zope.schema.vocabulary import SimpleVocabulary
31from zope.security.interfaces import Unauthorized
3230
33from canonical.launchpad import _31from canonical.launchpad import _
34from canonical.launchpad.fields import (32from canonical.launchpad.fields import (
@@ -805,11 +803,6 @@
805 webservice_error(417)803 webservice_error(417)
806804
807805
808class UserCannotUnsubscribePerson(Unauthorized):
809 """User does not have persmisson to unsubscribe person or team."""
810 webservice_error(401)
811
812
813# We are forced to define these now to avoid circular import problems.806# We are forced to define these now to avoid circular import problems.
814IBugAttachment['bug'].schema = IBug807IBugAttachment['bug'].schema = IBug
815IBugWatch['bug'].schema = IBug808IBugWatch['bug'].schema = IBug
816809
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2010-05-25 15:48:47 +0000
+++ lib/lp/bugs/model/bug.py 2010-05-29 09:01:05 +0000
@@ -64,12 +64,13 @@
64 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)64 IStoreSelector, DEFAULT_FLAVOR, MAIN_STORE, NotFoundError)
6565
66from lp.answers.interfaces.questiontarget import IQuestionTarget66from lp.answers.interfaces.questiontarget import IQuestionTarget
67from lp.app.errors import UserCannotUnsubscribePerson
67from lp.bugs.adapters.bugchange import (68from lp.bugs.adapters.bugchange import (
68 BranchLinkedToBug, BranchUnlinkedFromBug, BugConvertedToQuestion,69 BranchLinkedToBug, BranchUnlinkedFromBug, BugConvertedToQuestion,
69 BugWatchAdded, BugWatchRemoved, SeriesNominated, UnsubscribedFromBug)70 BugWatchAdded, BugWatchRemoved, SeriesNominated, UnsubscribedFromBug)
70from lp.bugs.interfaces.bug import (71from lp.bugs.interfaces.bug import (
71 IBug, IBugBecameQuestionEvent, IBugSet, IFileBugData,72 IBug, IBugBecameQuestionEvent, IBugSet, IFileBugData,
72 InvalidDuplicateValue, UserCannotUnsubscribePerson)73 InvalidDuplicateValue)
73from lp.bugs.interfaces.bugactivity import IBugActivitySet74from lp.bugs.interfaces.bugactivity import IBugActivitySet
74from lp.bugs.interfaces.bugattachment import (75from lp.bugs.interfaces.bugattachment import (
75 BugAttachmentType, IBugAttachmentSet)76 BugAttachmentType, IBugAttachmentSet)
7677
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2010-05-27 01:46:06 +0000
+++ lib/lp/code/browser/branch.py 2010-05-29 09:01:05 +0000
@@ -356,18 +356,6 @@
356356
357 def initialize(self):357 def initialize(self):
358 self.notices = []358 self.notices = []
359 self._add_subscription_notice()
360
361 def _add_subscription_notice(self):
362 """Add the appropriate notice after posting the subscription form."""
363 if self.user and self.request.method == 'POST':
364 newsub = self.request.form.get('subscribe', None)
365 if newsub == 'Subscribe':
366 self.context.subscribe(self.user)
367 self.notices.append("You have subscribed to this branch.")
368 elif newsub == 'Unsubscribe':
369 self.context.unsubscribe(self.user)
370 self.notices.append("You have unsubscribed from this branch.")
371359
372 def user_is_subscribed(self):360 def user_is_subscribed(self):
373 """Is the current user subscribed to this branch?"""361 """Is the current user subscribed to this branch?"""
374362
=== modified file 'lib/lp/code/browser/branchsubscription.py'
--- lib/lp/code/browser/branchsubscription.py 2010-04-20 01:21:10 +0000
+++ lib/lp/code/browser/branchsubscription.py 2010-05-29 09:01:05 +0000
@@ -12,10 +12,9 @@
12 'BranchSubscriptionPrimaryContext',12 'BranchSubscriptionPrimaryContext',
13 ]13 ]
1414
15from zope.component import getUtility15
16from zope.interface import implements16from zope.interface import implements
1717
18from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
19from canonical.launchpad.webapp import (18from canonical.launchpad.webapp import (
20 action, canonical_url, LaunchpadEditFormView, LaunchpadFormView,19 action, canonical_url, LaunchpadEditFormView, LaunchpadFormView,
21 LaunchpadView)20 LaunchpadView)
@@ -118,7 +117,8 @@
118 review_level = data['review_level']117 review_level = data['review_level']
119118
120 self.context.subscribe(119 self.context.subscribe(
121 self.user, notification_level, max_diff_lines, review_level)120 self.user, notification_level, max_diff_lines, review_level,
121 self.user)
122122
123 self.add_notification_message(123 self.add_notification_message(
124 'You have subscribed to this branch with: ',124 'You have subscribed to this branch with: ',
@@ -171,7 +171,7 @@
171 def unsubscribe(self, action, data):171 def unsubscribe(self, action, data):
172 # Be proactive in the checking to catch the stale post problem.172 # Be proactive in the checking to catch the stale post problem.
173 if self.context.hasSubscription(self.user):173 if self.context.hasSubscription(self.user):
174 self.context.unsubscribe(self.user)174 self.context.unsubscribe(self.user, self.user)
175 self.request.response.addNotification(175 self.request.response.addNotification(
176 "You have unsubscribed from this branch.")176 "You have unsubscribed from this branch.")
177 else:177 else:
@@ -209,7 +209,8 @@
209 subscription = self.context.getSubscription(person)209 subscription = self.context.getSubscription(person)
210 if subscription is None:210 if subscription is None:
211 self.context.subscribe(211 self.context.subscribe(
212 person, notification_level, max_diff_lines, review_level)212 person, notification_level, max_diff_lines, review_level,
213 self.user)
213214
214 self.add_notification_message(215 self.add_notification_message(
215 '%s has been subscribed to this branch with: '216 '%s has been subscribed to this branch with: '
@@ -222,24 +223,6 @@
222 subscription.notification_level, subscription.max_diff_lines,223 subscription.notification_level, subscription.max_diff_lines,
223 review_level)224 review_level)
224225
225 def validate(self, data):
226 """Ensure that when a team is subscribed, the user is a member."""
227 celebs = getUtility(ILaunchpadCelebrities)
228 # An admin or bzr expert can subscribe anyone.
229 if self.user.inTeam(celebs.admin) or (
230 self.user.inTeam(celebs.bazaar_experts)):
231 return
232
233 person = data.get('person')
234 if (person is not None and
235 person.isTeam() and
236 not self.user.inTeam(person)):
237 # A person can only subscribe a team if they are members
238 # of that team.
239 self.setFieldError(
240 'person',
241 "You can only subscribe teams that you are a member of.")
242
243226
244class BranchSubscriptionEditView(LaunchpadEditFormView):227class BranchSubscriptionEditView(LaunchpadEditFormView):
245 """The view for editing branch subscriptions.228 """The view for editing branch subscriptions.
@@ -273,7 +256,7 @@
273 @action("Unsubscribe", name="unsubscribe")256 @action("Unsubscribe", name="unsubscribe")
274 def unsubscribe_action(self, action, data):257 def unsubscribe_action(self, action, data):
275 """Unsubscribe the team from the branch."""258 """Unsubscribe the team from the branch."""
276 self.branch.unsubscribe(self.person)259 self.branch.unsubscribe(self.person, self.user)
277 self.request.response.addNotification(260 self.request.response.addNotification(
278 "%s has been unsubscribed from this branch."261 "%s has been unsubscribed from this branch."
279 % self.person.displayname)262 % self.person.displayname)
280263
=== modified file 'lib/lp/code/browser/codeimport.py'
--- lib/lp/code/browser/codeimport.py 2010-04-28 02:49:58 +0000
+++ lib/lp/code/browser/codeimport.py 2010-05-29 09:01:05 +0000
@@ -392,7 +392,8 @@
392 self.user,392 self.user,
393 BranchSubscriptionNotificationLevel.FULL,393 BranchSubscriptionNotificationLevel.FULL,
394 BranchSubscriptionDiffSize.NODIFF,394 BranchSubscriptionDiffSize.NODIFF,
395 CodeReviewNotificationLevel.NOEMAIL)395 CodeReviewNotificationLevel.NOEMAIL,
396 self.user)
396397
397 self.next_url = canonical_url(code_import.branch)398 self.next_url = canonical_url(code_import.branch)
398399
399400
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2010-05-19 15:39:52 +0000
+++ lib/lp/code/configure.zcml 2010-05-29 09:01:05 +0000
@@ -11,6 +11,7 @@
11 xmlns:webservice="http://namespaces.canonical.com/webservice"11 xmlns:webservice="http://namespaces.canonical.com/webservice"
12 i18n_domain="launchpad">12 i18n_domain="launchpad">
13 <include package=".browser"/>13 <include package=".browser"/>
14 <authorizations module="lp.code.security" />
1415
15 <!-- Branch Merge Queue -->16 <!-- Branch Merge Queue -->
1617
1718
=== modified file 'lib/lp/code/doc/branch-merge-proposal-notifications.txt'
--- lib/lp/code/doc/branch-merge-proposal-notifications.txt 2010-04-06 01:21:33 +0000
+++ lib/lp/code/doc/branch-merge-proposal-notifications.txt 2010-05-29 09:01:05 +0000
@@ -30,13 +30,13 @@
30 >>> _unused = bmp.source_branch.subscribe(source_subscriber,30 >>> _unused = bmp.source_branch.subscribe(source_subscriber,
31 ... BranchSubscriptionNotificationLevel.NOEMAIL,31 ... BranchSubscriptionNotificationLevel.NOEMAIL,
32 ... BranchSubscriptionDiffSize.NODIFF,32 ... BranchSubscriptionDiffSize.NODIFF,
33 ... CodeReviewNotificationLevel.STATUS)33 ... CodeReviewNotificationLevel.STATUS, source_subscriber)
34 >>> target_subscriber = factory.makePerson(34 >>> target_subscriber = factory.makePerson(
35 ... email='target@example.com', displayname='Target Subscriber')35 ... email='target@example.com', displayname='Target Subscriber')
36 >>> target_subscription = bmp.target_branch.subscribe(target_subscriber,36 >>> target_subscription = bmp.target_branch.subscribe(target_subscriber,
37 ... BranchSubscriptionNotificationLevel.NOEMAIL,37 ... BranchSubscriptionNotificationLevel.NOEMAIL,
38 ... BranchSubscriptionDiffSize.NODIFF,38 ... BranchSubscriptionDiffSize.NODIFF,
39 ... CodeReviewNotificationLevel.FULL)39 ... CodeReviewNotificationLevel.FULL, target_subscriber)
4040
41The owners of the branches are subscribed when the branches are created.41The owners of the branches are subscribed when the branches are created.
4242
@@ -69,8 +69,8 @@
6969
70Now we will unsubscribe the branch owners to simplify the rest of the test.70Now we will unsubscribe the branch owners to simplify the rest of the test.
7171
72 >>> bmp.source_branch.unsubscribe(source_owner)72 >>> bmp.source_branch.unsubscribe(source_owner, source_owner)
73 >>> bmp.target_branch.unsubscribe(target_owner)73 >>> bmp.target_branch.unsubscribe(target_owner, target_owner)
74 >>> recipients = bmp.getNotificationRecipients(74 >>> recipients = bmp.getNotificationRecipients(
75 ... CodeReviewNotificationLevel.FULL)75 ... CodeReviewNotificationLevel.FULL)
7676
7777
=== modified file 'lib/lp/code/doc/branch-notifications.txt'
--- lib/lp/code/doc/branch-notifications.txt 2010-03-10 05:49:00 +0000
+++ lib/lp/code/doc/branch-notifications.txt 2010-05-29 09:01:05 +0000
@@ -40,7 +40,7 @@
40 ... branch.owner,40 ... branch.owner,
41 ... BranchSubscriptionNotificationLevel.FULL,41 ... BranchSubscriptionNotificationLevel.FULL,
42 ... BranchSubscriptionDiffSize.WHOLEDIFF,42 ... BranchSubscriptionDiffSize.WHOLEDIFF,
43 ... CodeReviewNotificationLevel.NOEMAIL)43 ... CodeReviewNotificationLevel.NOEMAIL, branch.owner)
44 >>> BranchMailer.forRevision(branch, 1, 'foo@canonical.com',44 >>> BranchMailer.forRevision(branch, 1, 'foo@canonical.com',
45 ... 'The contents.', None, 'Subject line').sendAll()45 ... 'The contents.', None, 'Subject line').sendAll()
4646
@@ -71,7 +71,7 @@
71 You are subscribed to branch lp://dev/~name12/firefox/main.71 You are subscribed to branch lp://dev/~name12/firefox/main.
72 To unsubscribe from this branch go to http://code.launchpad.dev/~name12/firefox/main/+edit-subscription72 To unsubscribe from this branch go to http://code.launchpad.dev/~name12/firefox/main/+edit-subscription
73 <BLANKLINE>73 <BLANKLINE>
74 >>> branch.unsubscribe(branch.owner)74 >>> branch.unsubscribe(branch.owner, branch.owner)
7575
7676
77== Subscriptions ==77== Subscriptions ==
@@ -98,63 +98,55 @@
98 >>> from lp.code.interfaces.branch import IBranch, IBranchSet98 >>> from lp.code.interfaces.branch import IBranch, IBranchSet
99 >>> personset = getUtility(IPersonSet)99 >>> personset = getUtility(IPersonSet)
100100
101 >>> branch.subscribe(101 >>> def subscribe_user_by_email(branch, email, level, size, level2):
102 ... personset.getByEmail('no-priv@canonical.com'),102 ... subscriber = personset.getByEmail(email)
103 ... branch.subscribe(subscriber, level, size, level2, subscriber)
104
105 >>> subscribe_user_by_email(branch, 'no-priv@canonical.com',
103 ... BranchSubscriptionNotificationLevel.NOEMAIL,106 ... BranchSubscriptionNotificationLevel.NOEMAIL,
104 ... BranchSubscriptionDiffSize.NODIFF,107 ... BranchSubscriptionDiffSize.NODIFF,
105 ... CodeReviewNotificationLevel.NOEMAIL)108 ... CodeReviewNotificationLevel.NOEMAIL)
106 <BranchSubscription ...
107109
108 >>> branch.subscribe(110 >>> subscribe_user_by_email(branch, 'test@canonical.com',
109 ... personset.getByEmail('test@canonical.com'),
110 ... BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,111 ... BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,
111 ... BranchSubscriptionDiffSize.NODIFF,112 ... BranchSubscriptionDiffSize.NODIFF,
112 ... CodeReviewNotificationLevel.NOEMAIL)113 ... CodeReviewNotificationLevel.NOEMAIL)
113 <BranchSubscription ...
114114
115 >>> branch.subscribe(115 >>> subscribe_user_by_email(branch, 'carlos@canonical.com',
116 ... personset.getByEmail('carlos@canonical.com'),
117 ... BranchSubscriptionNotificationLevel.DIFFSONLY,116 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
118 ... BranchSubscriptionDiffSize.NODIFF,117 ... BranchSubscriptionDiffSize.NODIFF,
119 ... CodeReviewNotificationLevel.NOEMAIL)118 ... CodeReviewNotificationLevel.NOEMAIL)
120 <BranchSubscription ...
121119
122 >>> branch.subscribe(120 >>> subscribe_user_by_email(branch, 'jeff.waugh@ubuntulinux.com',
123 ... personset.getByEmail('jeff.waugh@ubuntulinux.com'),
124 ... BranchSubscriptionNotificationLevel.DIFFSONLY,121 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
125 ... BranchSubscriptionDiffSize.HALFKLINES,122 ... BranchSubscriptionDiffSize.HALFKLINES,
126 ... CodeReviewNotificationLevel.NOEMAIL)123 ... CodeReviewNotificationLevel.NOEMAIL)
127 <BranchSubscription ...
128124
129 >>> branch.subscribe(125 >>> subscribe_user_by_email(branch, 'celso.providelo@canonical.com',
130 ... personset.getByEmail('celso.providelo@canonical.com'),
131 ... BranchSubscriptionNotificationLevel.DIFFSONLY,126 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
132 ... BranchSubscriptionDiffSize.ONEKLINES,127 ... BranchSubscriptionDiffSize.ONEKLINES,
133 ... CodeReviewNotificationLevel.NOEMAIL)128 ... CodeReviewNotificationLevel.NOEMAIL)
134 <BranchSubscription ...
135129
136 >>> branch.subscribe(130 >>> subscribe_user_by_email(branch, 'daf@canonical.com',
137 ... personset.getByEmail('daf@canonical.com'),
138 ... BranchSubscriptionNotificationLevel.DIFFSONLY,131 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
139 ... BranchSubscriptionDiffSize.FIVEKLINES,132 ... BranchSubscriptionDiffSize.FIVEKLINES,
140 ... CodeReviewNotificationLevel.NOEMAIL)133 ... CodeReviewNotificationLevel.NOEMAIL)
141 <BranchSubscription ...
142134
143 >>> branch.subscribe(135 >>> subscribe_user_by_email(branch, 'mark@example.com',
144 ... personset.getByEmail('mark@example.com'),
145 ... BranchSubscriptionNotificationLevel.FULL,136 ... BranchSubscriptionNotificationLevel.FULL,
146 ... BranchSubscriptionDiffSize.WHOLEDIFF,137 ... BranchSubscriptionDiffSize.WHOLEDIFF,
147 ... CodeReviewNotificationLevel.NOEMAIL)138 ... CodeReviewNotificationLevel.NOEMAIL)
148 <BranchSubscription ...
149139
150Team are subscribed in the same way.140Team are subscribed in the same way.
151141
152 >>> branch.subscribe(142 >>> def subscribe_team_by_name(branch, name, level, size, level2):
153 ... personset.getByName('launchpad'),143 ... team = personset.getByName(name)
144 ... branch.subscribe(team, level, size, level2, team.teamowner)
145
146 >>> subscribe_team_by_name(branch, 'launchpad',
154 ... BranchSubscriptionNotificationLevel.FULL,147 ... BranchSubscriptionNotificationLevel.FULL,
155 ... BranchSubscriptionDiffSize.WHOLEDIFF,148 ... BranchSubscriptionDiffSize.WHOLEDIFF,
156 ... CodeReviewNotificationLevel.NOEMAIL)149 ... CodeReviewNotificationLevel.NOEMAIL)
157 <BranchSubscription ...
158150
159And to make sure we have them:151And to make sure we have them:
160152
@@ -358,7 +350,7 @@
358Unsubscribe everybody.350Unsubscribe everybody.
359351
360 >>> for subscription in branch.subscriptions:352 >>> for subscription in branch.subscriptions:
361 ... branch.unsubscribe(subscription.person)353 ... branch.unsubscribe(subscription.person, subscription.person)
362 >>> len(list(branch.subscriptions))354 >>> len(list(branch.subscriptions))
363 0355 0
364356
@@ -373,29 +365,23 @@
373If a team is registered, and that team has an email address assigned,365If a team is registered, and that team has an email address assigned,
374then that email address is used for the notifications.366then that email address is used for the notifications.
375367
376 >>> branch.subscribe(368 >>> subscribe_user_by_email(branch, 'david.allouche@canonical.com',
377 ... personset.getByEmail('david.allouche@canonical.com'),
378 ... BranchSubscriptionNotificationLevel.DIFFSONLY,369 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
379 ... BranchSubscriptionDiffSize.HALFKLINES,370 ... BranchSubscriptionDiffSize.HALFKLINES,
380 ... CodeReviewNotificationLevel.NOEMAIL)371 ... CodeReviewNotificationLevel.NOEMAIL)
381 <BranchSubscription ...
382372
383 >>> branch.subscribe(373 >>> subscribe_team_by_name(branch, 'vcs-imports',
384 ... personset.getByName('vcs-imports'),
385 ... BranchSubscriptionNotificationLevel.DIFFSONLY,374 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
386 ... BranchSubscriptionDiffSize.FIVEKLINES,375 ... BranchSubscriptionDiffSize.FIVEKLINES,
387 ... CodeReviewNotificationLevel.NOEMAIL)376 ... CodeReviewNotificationLevel.NOEMAIL)
388 <BranchSubscription ...
389377
390The ubuntu-team has an email address supplied (support@ubuntu.com), so378The ubuntu-team has an email address supplied (support@ubuntu.com), so
391that is used rather than the email addresses of the seven members.379that is used rather than the email addresses of the seven members.
392380
393 >>> branch.subscribe(381 >>> subscribe_team_by_name(branch, 'ubuntu-team',
394 ... personset.getByName('ubuntu-team'),
395 ... BranchSubscriptionNotificationLevel.DIFFSONLY,382 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
396 ... BranchSubscriptionDiffSize.ONEKLINES,383 ... BranchSubscriptionDiffSize.ONEKLINES,
397 ... CodeReviewNotificationLevel.NOEMAIL)384 ... CodeReviewNotificationLevel.NOEMAIL)
398 <BranchSubscription ...
399385
400 >>> recipients = branch.getNotificationRecipients()386 >>> recipients = branch.getNotificationRecipients()
401 >>> for email in recipients.getEmails():387 >>> for email in recipients.getEmails():
@@ -427,12 +413,10 @@
427413
428Resubscribe our test user.414Resubscribe our test user.
429415
430 >>> branch.subscribe(416 >>> subscribe_user_by_email(branch, 'test@canonical.com',
431 ... personset.getByEmail('test@canonical.com'),
432 ... BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,417 ... BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,
433 ... BranchSubscriptionDiffSize.NODIFF,418 ... BranchSubscriptionDiffSize.NODIFF,
434 ... CodeReviewNotificationLevel.NOEMAIL)419 ... CodeReviewNotificationLevel.NOEMAIL)
435 <BranchSubscription ...
436420
437 >>> from zope.event import notify421 >>> from zope.event import notify
438 >>> from lazr.lifecycle.event import ObjectModifiedEvent422 >>> from lazr.lifecycle.event import ObjectModifiedEvent
439423
=== modified file 'lib/lp/code/doc/branch-visibility.txt'
--- lib/lp/code/doc/branch-visibility.txt 2010-01-12 14:52:41 +0000
+++ lib/lp/code/doc/branch-visibility.txt 2010-05-29 09:01:05 +0000
@@ -138,7 +138,7 @@
138 >>> branch.subscribe(ubuntu_team,138 >>> branch.subscribe(ubuntu_team,
139 ... BranchSubscriptionNotificationLevel.NOEMAIL,139 ... BranchSubscriptionNotificationLevel.NOEMAIL,
140 ... BranchSubscriptionDiffSize.NODIFF,140 ... BranchSubscriptionDiffSize.NODIFF,
141 ... CodeReviewNotificationLevel.NOEMAIL)141 ... CodeReviewNotificationLevel.NOEMAIL, ubuntu_team.teamowner)
142 <BranchSubscription ...>142 <BranchSubscription ...>
143143
144 >>> access.checkAccountAuthenticated(jdub.account)144 >>> access.checkAccountAuthenticated(jdub.account)
145145
=== modified file 'lib/lp/code/doc/branch.txt'
--- lib/lp/code/doc/branch.txt 2010-01-12 21:37:34 +0000
+++ lib/lp/code/doc/branch.txt 2010-05-29 09:01:05 +0000
@@ -310,7 +310,7 @@
310 ... subscriber,310 ... subscriber,
311 ... BranchSubscriptionNotificationLevel.FULL,311 ... BranchSubscriptionNotificationLevel.FULL,
312 ... BranchSubscriptionDiffSize.FIVEKLINES,312 ... BranchSubscriptionDiffSize.FIVEKLINES,
313 ... CodeReviewNotificationLevel.FULL)313 ... CodeReviewNotificationLevel.FULL, subscriber)
314 >>> verifyObject(IBranchSubscription, subscription)314 >>> verifyObject(IBranchSubscription, subscription)
315 True315 True
316 >>> subscription.branch == branch and subscription.person == subscriber316 >>> subscription.branch == branch and subscription.person == subscriber
@@ -338,7 +338,7 @@
338 ... subscriber,338 ... subscriber,
339 ... BranchSubscriptionNotificationLevel.FULL,339 ... BranchSubscriptionNotificationLevel.FULL,
340 ... BranchSubscriptionDiffSize.FIVEKLINES,340 ... BranchSubscriptionDiffSize.FIVEKLINES,
341 ... CodeReviewNotificationLevel.NOEMAIL)341 ... CodeReviewNotificationLevel.NOEMAIL, subscriber)
342 >>> subscription == subscription2342 >>> subscription == subscription2
343 True343 True
344 >>> subscription2.review_level == CodeReviewNotificationLevel.NOEMAIL344 >>> subscription2.review_level == CodeReviewNotificationLevel.NOEMAIL
@@ -346,7 +346,7 @@
346346
347 Unsubscribing is also supported.347 Unsubscribing is also supported.
348348
349 >>> branch.unsubscribe(subscriber)349 >>> branch.unsubscribe(subscriber, subscriber)
350 >>> branch.subscribers.count()350 >>> branch.subscribers.count()
351 1351 1
352352
@@ -365,7 +365,7 @@
365 ... subscriber,365 ... subscriber,
366 ... BranchSubscriptionNotificationLevel.FULL,366 ... BranchSubscriptionNotificationLevel.FULL,
367 ... BranchSubscriptionDiffSize.FIVEKLINES,367 ... BranchSubscriptionDiffSize.FIVEKLINES,
368 ... CodeReviewNotificationLevel.NOEMAIL)368 ... CodeReviewNotificationLevel.NOEMAIL, subscriber)
369369
370 >>> print_names(branch2.getSubscriptionsByLevel([370 >>> print_names(branch2.getSubscriptionsByLevel([
371 ... BranchSubscriptionNotificationLevel.FULL]))371 ... BranchSubscriptionNotificationLevel.FULL]))
372372
=== modified file 'lib/lp/code/doc/codeimport.txt'
--- lib/lp/code/doc/codeimport.txt 2010-03-18 22:18:49 +0000
+++ lib/lp/code/doc/codeimport.txt 2010-05-29 09:01:05 +0000
@@ -258,7 +258,7 @@
258 ... nopriv,258 ... nopriv,
259 ... BranchSubscriptionNotificationLevel.FULL,259 ... BranchSubscriptionNotificationLevel.FULL,
260 ... BranchSubscriptionDiffSize.NODIFF,260 ... BranchSubscriptionDiffSize.NODIFF,
261 ... CodeReviewNotificationLevel.FULL)261 ... CodeReviewNotificationLevel.FULL, nopriv)
262262
263 >>> from lp.testing.mail_helpers import (263 >>> from lp.testing.mail_helpers import (
264 ... pop_notifications, print_emails)264 ... pop_notifications, print_emails)
265265
=== modified file 'lib/lp/code/doc/codereviewcomment.txt'
--- lib/lp/code/doc/codereviewcomment.txt 2010-04-06 01:13:28 +0000
+++ lib/lp/code/doc/codereviewcomment.txt 2010-05-29 09:01:05 +0000
@@ -65,7 +65,7 @@
65 >>> _unused = merge_proposal.source_branch.subscribe(source_subscriber,65 >>> _unused = merge_proposal.source_branch.subscribe(source_subscriber,
66 ... BranchSubscriptionNotificationLevel.NOEMAIL,66 ... BranchSubscriptionNotificationLevel.NOEMAIL,
67 ... BranchSubscriptionDiffSize.NODIFF,67 ... BranchSubscriptionDiffSize.NODIFF,
68 ... CodeReviewNotificationLevel.FULL)68 ... CodeReviewNotificationLevel.FULL, source_subscriber)
69 >>> from lp.testing.mail_helpers import (69 >>> from lp.testing.mail_helpers import (
70 ... pop_notifications, print_emails)70 ... pop_notifications, print_emails)
71 >>> _unused = pop_notifications()71 >>> _unused = pop_notifications()
7272
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2010-04-26 00:24:24 +0000
+++ lib/lp/code/interfaces/branch.py 2010-05-29 09:01:05 +0000
@@ -965,9 +965,10 @@
965 title=_("The level of code review notification emails."),965 title=_("The level of code review notification emails."),
966 vocabulary=CodeReviewNotificationLevel))966 vocabulary=CodeReviewNotificationLevel))
967 @operation_returns_entry(Interface) # Really IBranchSubscription967 @operation_returns_entry(Interface) # Really IBranchSubscription
968 @call_with(subscribed_by=REQUEST_USER)
968 @export_write_operation()969 @export_write_operation()
969 def subscribe(person, notification_level, max_diff_lines,970 def subscribe(person, notification_level, max_diff_lines,
970 code_review_level):971 code_review_level, subscribed_by):
971 """Subscribe this person to the branch.972 """Subscribe this person to the branch.
972973
973 :param person: The `Person` to subscribe.974 :param person: The `Person` to subscribe.
@@ -977,6 +978,8 @@
977 appear in a notification.978 appear in a notification.
978 :param code_review_level: The kinds of code review activity that cause979 :param code_review_level: The kinds of code review activity that cause
979 notification.980 notification.
981 :param subscribed_by: The person who is subscribing the subscriber.
982 Most often the subscriber themselves.
980 :return: new or existing BranchSubscription."""983 :return: new or existing BranchSubscription."""
981984
982 @operation_parameters(985 @operation_parameters(
@@ -995,9 +998,14 @@
995 person=Reference(998 person=Reference(
996 title=_("The person to unsubscribe"),999 title=_("The person to unsubscribe"),
997 schema=IPerson))1000 schema=IPerson))
1001 @call_with(unsubscribed_by=REQUEST_USER)
998 @export_write_operation()1002 @export_write_operation()
999 def unsubscribe(person):1003 def unsubscribe(person, unsubscribed_by):
1000 """Remove the person's subscription to this branch."""1004 """Remove the person's subscription to this branch.
1005
1006 :param person: The person or team to unsubscribe from the branch.
1007 :param unsubscribed_by: The person doing the unsubscribing.
1008 """
10011009
1002 def getSubscriptionsByLevel(notification_levels):1010 def getSubscriptionsByLevel(notification_levels):
1003 """Return the subscriptions that are at the given notification levels.1011 """Return the subscriptions that are at the given notification levels.
10041012
=== modified file 'lib/lp/code/interfaces/branchsubscription.py'
--- lib/lp/code/interfaces/branchsubscription.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/interfaces/branchsubscription.py 2010-05-29 09:01:05 +0000
@@ -21,7 +21,8 @@
21from lp.code.interfaces.branch import IBranch21from lp.code.interfaces.branch import IBranch
22from canonical.launchpad.fields import ParticipatingPersonChoice22from canonical.launchpad.fields import ParticipatingPersonChoice
23from lazr.restful.declarations import (23from lazr.restful.declarations import (
24 export_as_webservice_entry, exported)24 REQUEST_USER, call_with, export_as_webservice_entry,
25 export_read_operation, exported)
25from lazr.restful.fields import Reference26from lazr.restful.fields import Reference
2627
2728
@@ -73,3 +74,13 @@
73 'Control the kind of review activity that triggers '74 'Control the kind of review activity that triggers '
74 'notifications.'75 'notifications.'
75 )))76 )))
77
78 subscribed_by = exported(ParticipatingPersonChoice(
79 title=_('Subscribed by'), required=True,
80 vocabulary='ValidPersonOrTeam', readonly=True,
81 description=_("The person who created this subscription.")))
82
83 @call_with(user=REQUEST_USER)
84 @export_read_operation()
85 def canBeUnsubscribedByUser(user):
86 """Can the user unsubscribe the subscriber from the branch?"""
7687
=== modified file 'lib/lp/code/mail/tests/test_branch.py'
--- lib/lp/code/mail/tests/test_branch.py 2010-04-26 00:22:16 +0000
+++ lib/lp/code/mail/tests/test_branch.py 2010-05-29 09:01:05 +0000
@@ -35,7 +35,7 @@
35 source_branch.owner, target_branch)35 source_branch.owner, target_branch)
36 subscription = merge_proposal.source_branch.subscribe(36 subscription = merge_proposal.source_branch.subscribe(
37 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,37 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,
38 CodeReviewNotificationLevel.FULL)38 CodeReviewNotificationLevel.FULL, subscriber)
39 return merge_proposal, subscription39 return merge_proposal, subscription
4040
41 def test_forBranchSubscriber(self):41 def test_forBranchSubscriber(self):
4242
=== modified file 'lib/lp/code/mail/tests/test_branchmergeproposal.py'
--- lib/lp/code/mail/tests/test_branchmergeproposal.py 2010-05-18 13:14:40 +0000
+++ lib/lp/code/mail/tests/test_branchmergeproposal.py 2010-05-29 09:01:05 +0000
@@ -67,7 +67,7 @@
67 email='baz.quxx@example.com')67 email='baz.quxx@example.com')
68 bmp.source_branch.subscribe(subscriber,68 bmp.source_branch.subscribe(subscriber,
69 BranchSubscriptionNotificationLevel.NOEMAIL, None,69 BranchSubscriptionNotificationLevel.NOEMAIL, None,
70 CodeReviewNotificationLevel.FULL)70 CodeReviewNotificationLevel.FULL, subscriber)
71 bmp.source_branch.owner.name = 'bob'71 bmp.source_branch.owner.name = 'bob'
72 bmp.source_branch.name = 'fix-foo-for-bar'72 bmp.source_branch.name = 'fix-foo-for-bar'
73 bmp.target_branch.owner.name = 'mary'73 bmp.target_branch.owner.name = 'mary'
@@ -176,7 +176,7 @@
176 bmp = request.merge_proposal176 bmp = request.merge_proposal
177 bmp.source_branch.subscribe(177 bmp.source_branch.subscribe(
178 bmp.registrant, BranchSubscriptionNotificationLevel.NOEMAIL, None,178 bmp.registrant, BranchSubscriptionNotificationLevel.NOEMAIL, None,
179 CodeReviewNotificationLevel.FULL)179 CodeReviewNotificationLevel.FULL, bmp.registrant)
180 mailer = BMPMailer.forCreation(bmp, bmp.registrant)180 mailer = BMPMailer.forCreation(bmp, bmp.registrant)
181 ctrl = mailer.generateEmail(bmp.registrant,181 ctrl = mailer.generateEmail(bmp.registrant,
182 bmp.registrant.preferredemail.email)182 bmp.registrant.preferredemail.email)
@@ -259,7 +259,7 @@
259 diff_text=diff_text)259 diff_text=diff_text)
260 bmp.source_branch.subscribe(subscriber,260 bmp.source_branch.subscribe(subscriber,
261 BranchSubscriptionNotificationLevel.NOEMAIL, None,261 BranchSubscriptionNotificationLevel.NOEMAIL, None,
262 CodeReviewNotificationLevel.STATUS)262 CodeReviewNotificationLevel.STATUS, subscriber)
263 mailer = BMPMailer.forCreation(bmp, bmp.registrant)263 mailer = BMPMailer.forCreation(bmp, bmp.registrant)
264 ctrl = mailer.generateEmail('baz.quxx@example.com', subscriber)264 ctrl = mailer.generateEmail('baz.quxx@example.com', subscriber)
265 self.assertEqual(0, len(ctrl.attachments))265 self.assertEqual(0, len(ctrl.attachments))
266266
=== modified file 'lib/lp/code/mail/tests/test_codehandler.py'
--- lib/lp/code/mail/tests/test_codehandler.py 2010-04-29 07:47:47 +0000
+++ lib/lp/code/mail/tests/test_codehandler.py 2010-05-29 09:01:05 +0000
@@ -346,7 +346,7 @@
346 subscriber = self.factory.makePerson()346 subscriber = self.factory.makePerson()
347 bmp.source_branch.subscribe(347 bmp.source_branch.subscribe(
348 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,348 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,
349 CodeReviewNotificationLevel.FULL)349 CodeReviewNotificationLevel.FULL, subscriber)
350 email_addr = bmp.address350 email_addr = bmp.address
351 self.switchDbUser(config.processmail.dbuser)351 self.switchDbUser(config.processmail.dbuser)
352 self.code_handler.process(mail, email_addr, None)352 self.code_handler.process(mail, email_addr, None)
353353
=== modified file 'lib/lp/code/mail/tests/test_codereviewcomment.py'
--- lib/lp/code/mail/tests/test_codereviewcomment.py 2010-05-07 17:16:44 +0000
+++ lib/lp/code/mail/tests/test_codereviewcomment.py 2010-05-29 09:01:05 +0000
@@ -48,7 +48,7 @@
48 notification_level = CodeReviewNotificationLevel.FULL48 notification_level = CodeReviewNotificationLevel.FULL
49 comment.branch_merge_proposal.source_branch.subscribe(49 comment.branch_merge_proposal.source_branch.subscribe(
50 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,50 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,
51 notification_level)51 notification_level, subscriber)
52 # Email is not sent on construction, so fake a root message id on the52 # Email is not sent on construction, so fake a root message id on the
53 # merge proposal.53 # merge proposal.
54 login_person(comment.branch_merge_proposal.registrant)54 login_person(comment.branch_merge_proposal.registrant)
@@ -265,7 +265,7 @@
265 email='commenter@email.com', displayname='Commenter')265 email='commenter@email.com', displayname='Commenter')
266 bmp.source_branch.subscribe(commenter,266 bmp.source_branch.subscribe(commenter,
267 BranchSubscriptionNotificationLevel.NOEMAIL, None,267 BranchSubscriptionNotificationLevel.NOEMAIL, None,
268 CodeReviewNotificationLevel.FULL)268 CodeReviewNotificationLevel.FULL, commenter)
269 comment = bmp.createComment(commenter, 'hello')269 comment = bmp.createComment(commenter, 'hello')
270 return comment270 return comment
271271
272272
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2010-05-15 17:43:59 +0000
+++ lib/lp/code/model/branch.py 2010-05-29 09:01:05 +0000
@@ -45,6 +45,7 @@
45from canonical.launchpad.webapp.interfaces import (45from canonical.launchpad.webapp.interfaces import (
46 IStoreSelector, MAIN_STORE, SLAVE_FLAVOR)46 IStoreSelector, MAIN_STORE, SLAVE_FLAVOR)
4747
48from lp.app.errors import UserCannotUnsubscribePerson
48from lp.code.bzr import (49from lp.code.bzr import (
49 BranchFormat, ControlFormat, CURRENT_BRANCH_FORMATS,50 BranchFormat, ControlFormat, CURRENT_BRANCH_FORMATS,
50 CURRENT_REPOSITORY_FORMATS, RepositoryFormat)51 CURRENT_REPOSITORY_FORMATS, RepositoryFormat)
@@ -659,7 +660,7 @@
659660
660 # subscriptions661 # subscriptions
661 def subscribe(self, person, notification_level, max_diff_lines,662 def subscribe(self, person, notification_level, max_diff_lines,
662 code_review_level):663 code_review_level, subscribed_by):
663 """See `IBranch`."""664 """See `IBranch`."""
664 # If the person is already subscribed, update the subscription with665 # If the person is already subscribed, update the subscription with
665 # the specified notification details.666 # the specified notification details.
@@ -668,7 +669,8 @@
668 subscription = BranchSubscription(669 subscription = BranchSubscription(
669 branch=self, person=person,670 branch=self, person=person,
670 notification_level=notification_level,671 notification_level=notification_level,
671 max_diff_lines=max_diff_lines, review_level=code_review_level)672 max_diff_lines=max_diff_lines, review_level=code_review_level,
673 subscribed_by=subscribed_by)
672 Store.of(subscription).flush()674 Store.of(subscription).flush()
673 else:675 else:
674 subscription.notification_level = notification_level676 subscription.notification_level = notification_level
@@ -701,12 +703,19 @@
701 """See `IBranch`."""703 """See `IBranch`."""
702 return self.getSubscription(person) is not None704 return self.getSubscription(person) is not None
703705
704 def unsubscribe(self, person):706 def unsubscribe(self, person, unsubscribed_by):
705 """See `IBranch`."""707 """See `IBranch`."""
706 subscription = self.getSubscription(person)708 subscription = self.getSubscription(person)
709 if subscription is None:
710 # Silent success seems order of the day (like bugs).
711 return
712 if not subscription.canBeUnsubscribedByUser(unsubscribed_by):
713 raise UserCannotUnsubscribePerson(
714 '%s does not have permission to unsubscribe %s.' % (
715 unsubscribed_by.displayname,
716 person.displayname))
707 store = Store.of(subscription)717 store = Store.of(subscription)
708 assert subscription is not None, "User is not subscribed."718 store.remove(subscription)
709 BranchSubscription.delete(subscription.id)
710 store.flush()719 store.flush()
711720
712 def getBranchRevision(self, sequence=None, revision=None,721 def getBranchRevision(self, sequence=None, revision=None,
713722
=== modified file 'lib/lp/code/model/branchnamespace.py'
--- lib/lp/code/model/branchnamespace.py 2010-02-17 11:19:42 +0000
+++ lib/lp/code/model/branchnamespace.py 2010-05-29 09:01:05 +0000
@@ -106,7 +106,8 @@
106 implicit_subscription,106 implicit_subscription,
107 BranchSubscriptionNotificationLevel.NOEMAIL,107 BranchSubscriptionNotificationLevel.NOEMAIL,
108 BranchSubscriptionDiffSize.NODIFF,108 BranchSubscriptionDiffSize.NODIFF,
109 CodeReviewNotificationLevel.NOEMAIL)109 CodeReviewNotificationLevel.NOEMAIL,
110 registrant)
110111
111 # The registrant of the branch should also be automatically subscribed112 # The registrant of the branch should also be automatically subscribed
112 # in order for them to get code review notifications. The implicit113 # in order for them to get code review notifications. The implicit
@@ -116,7 +117,8 @@
116 registrant,117 registrant,
117 BranchSubscriptionNotificationLevel.NOEMAIL,118 BranchSubscriptionNotificationLevel.NOEMAIL,
118 BranchSubscriptionDiffSize.NODIFF,119 BranchSubscriptionDiffSize.NODIFF,
119 CodeReviewNotificationLevel.FULL)120 CodeReviewNotificationLevel.FULL,
121 registrant)
120122
121 notify(ObjectCreatedEvent(branch))123 notify(ObjectCreatedEvent(branch))
122 return branch124 return branch
123125
=== modified file 'lib/lp/code/model/branchsubscription.py'
--- lib/lp/code/model/branchsubscription.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/model/branchsubscription.py 2010-05-29 09:01:05 +0000
@@ -13,13 +13,14 @@
13from canonical.database.constants import DEFAULT13from canonical.database.constants import DEFAULT
14from canonical.database.sqlbase import SQLBase14from canonical.database.sqlbase import SQLBase
15from canonical.database.enumcol import EnumCol15from canonical.database.enumcol import EnumCol
1616from canonical.launchpad.interfaces.launchpad import IPersonRoles
17from lp.code.enums import (17from lp.code.enums import (
18 BranchSubscriptionDiffSize, BranchSubscriptionNotificationLevel,18 BranchSubscriptionDiffSize, BranchSubscriptionNotificationLevel,
19 CodeReviewNotificationLevel)19 CodeReviewNotificationLevel)
20from lp.code.interfaces.branchsubscription import IBranchSubscription20from lp.code.interfaces.branchsubscription import IBranchSubscription
21from lp.code.interfaces.branch import IBranchNavigationMenu21from lp.code.interfaces.branch import IBranchNavigationMenu
22from lp.code.interfaces.branchtarget import IHasBranchTarget22from lp.code.interfaces.branchtarget import IHasBranchTarget
23from lp.code.security import BranchSubscriptionEdit
23from lp.registry.interfaces.person import (24from lp.registry.interfaces.person import (
24 validate_person_not_private_membership)25 validate_person_not_private_membership)
2526
@@ -41,8 +42,18 @@
41 notNull=False, default=DEFAULT)42 notNull=False, default=DEFAULT)
42 review_level = EnumCol(enum=CodeReviewNotificationLevel,43 review_level = EnumCol(enum=CodeReviewNotificationLevel,
43 notNull=True, default=DEFAULT)44 notNull=True, default=DEFAULT)
45 subscribed_by = ForeignKey(
46 dbName='subscribed_by', foreignKey='Person',
47 storm_validator=validate_person_not_private_membership, notNull=True)
4448
45 @property49 @property
46 def target(self):50 def target(self):
47 """See `IHasBranchTarget`."""51 """See `IHasBranchTarget`."""
48 return self.branch.target52 return self.branch.target
53
54 def canBeUnsubscribedByUser(self, user):
55 """See `IBranchSubscription`."""
56 if user is None:
57 return False
58 permission_check = BranchSubscriptionEdit(self)
59 return permission_check.checkAuthenticated(IPersonRoles(user))
4960
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2010-04-23 04:11:12 +0000
+++ lib/lp/code/model/tests/test_branch.py 2010-05-29 09:01:05 +0000
@@ -908,7 +908,7 @@
908 # The owner of the branch is subscribed to the branch when it is908 # The owner of the branch is subscribed to the branch when it is
909 # created. The tests here assume no initial connections, so909 # created. The tests here assume no initial connections, so
910 # unsubscribe the branch owner here.910 # unsubscribe the branch owner here.
911 self.branch.unsubscribe(self.branch.owner)911 self.branch.unsubscribe(self.branch.owner, self.branch.owner)
912912
913 def test_deletable(self):913 def test_deletable(self):
914 """A newly created branch can be deleted without any problems."""914 """A newly created branch can be deleted without any problems."""
@@ -930,7 +930,7 @@
930 """A branch that has a subscription can be deleted."""930 """A branch that has a subscription can be deleted."""
931 self.branch.subscribe(931 self.branch.subscribe(
932 self.user, BranchSubscriptionNotificationLevel.NOEMAIL, None,932 self.user, BranchSubscriptionNotificationLevel.NOEMAIL, None,
933 CodeReviewNotificationLevel.NOEMAIL)933 CodeReviewNotificationLevel.NOEMAIL, self.user)
934 self.assertEqual(True, self.branch.canBeDeleted())934 self.assertEqual(True, self.branch.canBeDeleted())
935935
936 def test_codeImportCanStillBeDeleted(self):936 def test_codeImportCanStillBeDeleted(self):
@@ -1068,7 +1068,7 @@
1068 # The owner of the branch is subscribed to the branch when it is1068 # The owner of the branch is subscribed to the branch when it is
1069 # created. The tests here assume no initial connections, so1069 # created. The tests here assume no initial connections, so
1070 # unsubscribe the branch owner here.1070 # unsubscribe the branch owner here.
1071 self.branch.unsubscribe(self.branch.owner)1071 self.branch.unsubscribe(self.branch.owner, self.branch.owner)
10721072
1073 def test_plainBranch(self):1073 def test_plainBranch(self):
1074 """Ensure that a fresh branch has no deletion requirements."""1074 """Ensure that a fresh branch has no deletion requirements."""
@@ -1081,8 +1081,9 @@
1081 prerequisite_branch = self.factory.makeProductBranch(1081 prerequisite_branch = self.factory.makeProductBranch(
1082 product=self.branch.product)1082 product=self.branch.product)
1083 # Remove the implicit subscriptions.1083 # Remove the implicit subscriptions.
1084 target_branch.unsubscribe(target_branch.owner)1084 target_branch.unsubscribe(target_branch.owner, target_branch.owner)
1085 prerequisite_branch.unsubscribe(prerequisite_branch.owner)1085 prerequisite_branch.unsubscribe(
1086 prerequisite_branch.owner, prerequisite_branch.owner)
1086 merge_proposal1 = self.branch.addLandingTarget(1087 merge_proposal1 = self.branch.addLandingTarget(
1087 self.branch.owner, target_branch, prerequisite_branch)1088 self.branch.owner, target_branch, prerequisite_branch)
1088 # Disable this merge proposal, to allow creating a new identical one1089 # Disable this merge proposal, to allow creating a new identical one
@@ -1261,7 +1262,8 @@
1261 """Deletion requirements for a code import branch are right"""1262 """Deletion requirements for a code import branch are right"""
1262 code_import = self.factory.makeCodeImport()1263 code_import = self.factory.makeCodeImport()
1263 # Remove the implicit branch subscription first.1264 # Remove the implicit branch subscription first.
1264 code_import.branch.unsubscribe(code_import.branch.owner)1265 code_import.branch.unsubscribe(
1266 code_import.branch.owner, code_import.branch.owner)
1265 self.assertEqual({}, code_import.branch.deletionRequirements())1267 self.assertEqual({}, code_import.branch.deletionRequirements())
12661268
1267 def test_branchWithCodeImportDeletion(self):1269 def test_branchWithCodeImportDeletion(self):
@@ -2303,7 +2305,8 @@
2303 # Revisions created before the start date are not returned.2305 # Revisions created before the start date are not returned.
2304 branch = self.factory.makeAnyBranch()2306 branch = self.factory.makeAnyBranch()
2305 epoch = datetime(2009, 9, 10, tzinfo=UTC)2307 epoch = datetime(2009, 9, 10, tzinfo=UTC)
2306 old = add_revision_to_branch(2308 # Add some revisions before the epoch.
2309 add_revision_to_branch(
2307 self.factory, branch, epoch - timedelta(days=1))2310 self.factory, branch, epoch - timedelta(days=1))
2308 new = add_revision_to_branch(2311 new = add_revision_to_branch(
2309 self.factory, branch, epoch + timedelta(days=1))2312 self.factory, branch, epoch + timedelta(days=1))
@@ -2318,7 +2321,8 @@
2318 end_date = epoch + timedelta(days=2)2321 end_date = epoch + timedelta(days=2)
2319 in_range = add_revision_to_branch(2322 in_range = add_revision_to_branch(
2320 self.factory, branch, end_date - timedelta(days=1))2323 self.factory, branch, end_date - timedelta(days=1))
2321 too_new = add_revision_to_branch(2324 # Add some revisions after the end_date.
2325 add_revision_to_branch(
2322 self.factory, branch, end_date + timedelta(days=1))2326 self.factory, branch, end_date + timedelta(days=1))
2323 result = branch.getMainlineBranchRevisions(epoch, end_date)2327 result = branch.getMainlineBranchRevisions(epoch, end_date)
2324 branch_revisions = [br for br, rev, ra in result]2328 branch_revisions = [br for br, rev, ra in result]
@@ -2354,7 +2358,8 @@
2354 epoch = datetime(2009, 9, 10, tzinfo=UTC)2358 epoch = datetime(2009, 9, 10, tzinfo=UTC)
2355 old = add_revision_to_branch(2359 old = add_revision_to_branch(
2356 self.factory, branch, epoch + timedelta(days=1))2360 self.factory, branch, epoch + timedelta(days=1))
2357 merged = add_revision_to_branch(2361 # Add some non mainline revision.
2362 add_revision_to_branch(
2358 self.factory, branch, epoch + timedelta(days=2), mainline=False)2363 self.factory, branch, epoch + timedelta(days=2), mainline=False)
2359 new = add_revision_to_branch(2364 new = add_revision_to_branch(
2360 self.factory, branch, epoch + timedelta(days=3))2365 self.factory, branch, epoch + timedelta(days=3))
23612366
=== modified file 'lib/lp/code/model/tests/test_branchcollection.py'
--- lib/lp/code/model/tests/test_branchcollection.py 2009-10-28 04:33:49 +0000
+++ lib/lp/code/model/tests/test_branchcollection.py 2010-05-29 09:01:05 +0000
@@ -364,7 +364,8 @@
364 branch.subscribe(364 branch.subscribe(
365 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL,365 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL,
366 BranchSubscriptionDiffSize.NODIFF,366 BranchSubscriptionDiffSize.NODIFF,
367 CodeReviewNotificationLevel.NOEMAIL)367 CodeReviewNotificationLevel.NOEMAIL,
368 subscriber)
368 collection = self.all_branches.subscribedBy(subscriber)369 collection = self.all_branches.subscribedBy(subscriber)
369 self.assertEqual([branch], list(collection.getBranches()))370 self.assertEqual([branch], list(collection.getBranches()))
370371
@@ -377,7 +378,7 @@
377 owned_branch = self.factory.makeAnyBranch(owner=person)378 owned_branch = self.factory.makeAnyBranch(owner=person)
378 # Unsubscribe the owner, to demonstrate that we show owned branches379 # Unsubscribe the owner, to demonstrate that we show owned branches
379 # even if they aren't subscribed.380 # even if they aren't subscribed.
380 owned_branch.unsubscribe(person)381 owned_branch.unsubscribe(person, person)
381 # Subscribe two other people to the owned branch to make sure382 # Subscribe two other people to the owned branch to make sure
382 # that the BranchSubscription join is doing it right.383 # that the BranchSubscription join is doing it right.
383 self.factory.makeBranchSubscription(branch=owned_branch)384 self.factory.makeBranchSubscription(branch=owned_branch)
@@ -389,7 +390,8 @@
389 subscribed_branch.subscribe(390 subscribed_branch.subscribe(
390 person, BranchSubscriptionNotificationLevel.NOEMAIL,391 person, BranchSubscriptionNotificationLevel.NOEMAIL,
391 BranchSubscriptionDiffSize.NODIFF,392 BranchSubscriptionDiffSize.NODIFF,
392 CodeReviewNotificationLevel.NOEMAIL)393 CodeReviewNotificationLevel.NOEMAIL,
394 person)
393 related_branches = self.all_branches.relatedTo(person)395 related_branches = self.all_branches.relatedTo(person)
394 self.assertEqual(396 self.assertEqual(
395 sorted([owned_branch, registered_branch, subscribed_branch]),397 sorted([owned_branch, registered_branch, subscribed_branch]),
@@ -548,7 +550,8 @@
548 removeSecurityProxy(self.private_branch1).subscribe(550 removeSecurityProxy(self.private_branch1).subscribe(
549 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL,551 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL,
550 BranchSubscriptionDiffSize.NODIFF,552 BranchSubscriptionDiffSize.NODIFF,
551 CodeReviewNotificationLevel.NOEMAIL)553 CodeReviewNotificationLevel.NOEMAIL,
554 subscriber)
552 branches = self.all_branches.visibleByUser(subscriber)555 branches = self.all_branches.visibleByUser(subscriber)
553 self.assertEqual(556 self.assertEqual(
554 sorted([self.public_branch, self.private_branch1]),557 sorted([self.public_branch, self.private_branch1]),
@@ -564,7 +567,8 @@
564 removeSecurityProxy(private_branch).subscribe(567 removeSecurityProxy(private_branch).subscribe(
565 team, BranchSubscriptionNotificationLevel.NOEMAIL,568 team, BranchSubscriptionNotificationLevel.NOEMAIL,
566 BranchSubscriptionDiffSize.NODIFF,569 BranchSubscriptionDiffSize.NODIFF,
567 CodeReviewNotificationLevel.NOEMAIL)570 CodeReviewNotificationLevel.NOEMAIL,
571 team_owner)
568 # Members of the team can see the private branch that the team is572 # Members of the team can see the private branch that the team is
569 # subscribed to.573 # subscribed to.
570 branches = self.all_branches.visibleByUser(team_owner)574 branches = self.all_branches.visibleByUser(team_owner)
571575
=== modified file 'lib/lp/code/model/tests/test_branchjob.py'
--- lib/lp/code/model/tests/test_branchjob.py 2010-05-25 05:19:30 +0000
+++ lib/lp/code/model/tests/test_branchjob.py 2010-05-29 09:01:05 +0000
@@ -322,10 +322,12 @@
322 def test_run_sends_mail(self):322 def test_run_sends_mail(self):
323 """Ensure RevisionMailJob.run sends mail with correct values."""323 """Ensure RevisionMailJob.run sends mail with correct values."""
324 branch = self.factory.makeAnyBranch()324 branch = self.factory.makeAnyBranch()
325 branch.subscribe(branch.registrant,325 branch.subscribe(
326 branch.registrant,
326 BranchSubscriptionNotificationLevel.FULL,327 BranchSubscriptionNotificationLevel.FULL,
327 BranchSubscriptionDiffSize.WHOLEDIFF,328 BranchSubscriptionDiffSize.WHOLEDIFF,
328 CodeReviewNotificationLevel.FULL)329 CodeReviewNotificationLevel.FULL,
330 branch.registrant)
329 job = RevisionMailJob.create(331 job = RevisionMailJob.create(
330 branch, 0, 'from@example.com', 'hello', False, 'subject')332 branch, 0, 'from@example.com', 'hello', False, 'subject')
331 job.run()333 job.run()
@@ -515,10 +517,12 @@
515 product = self.factory.makeProduct(name='foo')517 product = self.factory.makeProduct(name='foo')
516 branch = self.factory.makeProductBranch(518 branch = self.factory.makeProductBranch(
517 name='bar', product=product, owner=jrandom)519 name='bar', product=product, owner=jrandom)
518 branch.subscribe(branch.registrant,520 branch.subscribe(
521 branch.registrant,
519 BranchSubscriptionNotificationLevel.FULL,522 BranchSubscriptionNotificationLevel.FULL,
520 BranchSubscriptionDiffSize.WHOLEDIFF,523 BranchSubscriptionDiffSize.WHOLEDIFF,
521 CodeReviewNotificationLevel.FULL)524 CodeReviewNotificationLevel.FULL,
525 branch.registrant)
522 branch, tree = self.create_branch_and_tree(db_branch=branch)526 branch, tree = self.create_branch_and_tree(db_branch=branch)
523 tree.branch.nick = 'nicholas'527 tree.branch.nick = 'nicholas'
524 tree.lock_write()528 tree.lock_write()
525529
=== modified file 'lib/lp/code/model/tests/test_branchmergeproposals.py'
--- lib/lp/code/model/tests/test_branchmergeproposals.py 2010-05-07 04:53:47 +0000
+++ lib/lp/code/model/tests/test_branchmergeproposals.py 2010-05-29 09:01:05 +0000
@@ -722,16 +722,20 @@
722 subscriber_set = set([source_owner, target_owner])722 subscriber_set = set([source_owner, target_owner])
723 self.assertEqual(subscriber_set, set(recipients.keys()))723 self.assertEqual(subscriber_set, set(recipients.keys()))
724 source_subscriber = self.factory.makePerson()724 source_subscriber = self.factory.makePerson()
725 bmp.source_branch.subscribe(source_subscriber,725 bmp.source_branch.subscribe(
726 source_subscriber,
726 BranchSubscriptionNotificationLevel.NOEMAIL, None,727 BranchSubscriptionNotificationLevel.NOEMAIL, None,
727 CodeReviewNotificationLevel.FULL)728 CodeReviewNotificationLevel.FULL,
729 source_subscriber)
728 recipients = bmp.getNotificationRecipients(730 recipients = bmp.getNotificationRecipients(
729 CodeReviewNotificationLevel.STATUS)731 CodeReviewNotificationLevel.STATUS)
730 subscriber_set.add(source_subscriber)732 subscriber_set.add(source_subscriber)
731 self.assertEqual(subscriber_set, set(recipients.keys()))733 self.assertEqual(subscriber_set, set(recipients.keys()))
732 bmp.source_branch.subscribe(source_subscriber,734 bmp.source_branch.subscribe(
735 source_subscriber,
733 BranchSubscriptionNotificationLevel.NOEMAIL, None,736 BranchSubscriptionNotificationLevel.NOEMAIL, None,
734 CodeReviewNotificationLevel.NOEMAIL)737 CodeReviewNotificationLevel.NOEMAIL,
738 source_subscriber)
735 # By specifying no email, they will no longer get email.739 # By specifying no email, they will no longer get email.
736 subscriber_set.remove(source_subscriber)740 subscriber_set.remove(source_subscriber)
737 recipients = bmp.getNotificationRecipients(741 recipients = bmp.getNotificationRecipients(
@@ -744,11 +748,11 @@
744 full_subscriber = self.factory.makePerson()748 full_subscriber = self.factory.makePerson()
745 bmp.source_branch.subscribe(full_subscriber,749 bmp.source_branch.subscribe(full_subscriber,
746 BranchSubscriptionNotificationLevel.NOEMAIL, None,750 BranchSubscriptionNotificationLevel.NOEMAIL, None,
747 CodeReviewNotificationLevel.FULL)751 CodeReviewNotificationLevel.FULL, full_subscriber)
748 status_subscriber = self.factory.makePerson()752 status_subscriber = self.factory.makePerson()
749 bmp.source_branch.subscribe(status_subscriber,753 bmp.source_branch.subscribe(status_subscriber,
750 BranchSubscriptionNotificationLevel.NOEMAIL, None,754 BranchSubscriptionNotificationLevel.NOEMAIL, None,
751 CodeReviewNotificationLevel.STATUS)755 CodeReviewNotificationLevel.STATUS, status_subscriber)
752 recipients = bmp.getNotificationRecipients(756 recipients = bmp.getNotificationRecipients(
753 CodeReviewNotificationLevel.STATUS)757 CodeReviewNotificationLevel.STATUS)
754 # Both of the branch owners are now subscribed to their own758 # Both of the branch owners are now subscribed to their own
@@ -778,15 +782,15 @@
778 source_subscriber = self.factory.makePerson()782 source_subscriber = self.factory.makePerson()
779 bmp.source_branch.subscribe(source_subscriber,783 bmp.source_branch.subscribe(source_subscriber,
780 BranchSubscriptionNotificationLevel.NOEMAIL, None,784 BranchSubscriptionNotificationLevel.NOEMAIL, None,
781 CodeReviewNotificationLevel.FULL)785 CodeReviewNotificationLevel.FULL, source_subscriber)
782 target_subscriber = self.factory.makePerson()786 target_subscriber = self.factory.makePerson()
783 bmp.target_branch.subscribe(target_subscriber,787 bmp.target_branch.subscribe(target_subscriber,
784 BranchSubscriptionNotificationLevel.NOEMAIL, None,788 BranchSubscriptionNotificationLevel.NOEMAIL, None,
785 CodeReviewNotificationLevel.FULL)789 CodeReviewNotificationLevel.FULL, target_subscriber)
786 prerequisite_subscriber = self.factory.makePerson()790 prerequisite_subscriber = self.factory.makePerson()
787 bmp.prerequisite_branch.subscribe(prerequisite_subscriber,791 bmp.prerequisite_branch.subscribe(prerequisite_subscriber,
788 BranchSubscriptionNotificationLevel.NOEMAIL, None,792 BranchSubscriptionNotificationLevel.NOEMAIL, None,
789 CodeReviewNotificationLevel.FULL)793 CodeReviewNotificationLevel.FULL, prerequisite_subscriber)
790 recipients = bmp.getNotificationRecipients(794 recipients = bmp.getNotificationRecipients(
791 CodeReviewNotificationLevel.FULL)795 CodeReviewNotificationLevel.FULL)
792 self.assertEqual(796 self.assertEqual(
@@ -832,7 +836,7 @@
832 # Make sure that the registrant is subscribed.836 # Make sure that the registrant is subscribed.
833 bmp.source_branch.subscribe(registrant,837 bmp.source_branch.subscribe(registrant,
834 BranchSubscriptionNotificationLevel.NOEMAIL, None,838 BranchSubscriptionNotificationLevel.NOEMAIL, None,
835 CodeReviewNotificationLevel.FULL)839 CodeReviewNotificationLevel.FULL, registrant)
836 recipients = bmp.getNotificationRecipients(840 recipients = bmp.getNotificationRecipients(
837 CodeReviewNotificationLevel.STATUS)841 CodeReviewNotificationLevel.STATUS)
838 reason = recipients[registrant]842 reason = recipients[registrant]
@@ -879,7 +883,7 @@
879 # we don't send them eamil.883 # we don't send them eamil.
880 bmp = self.factory.makeBranchMergeProposal()884 bmp = self.factory.makeBranchMergeProposal()
881 owner = bmp.source_branch.owner885 owner = bmp.source_branch.owner
882 bmp.source_branch.unsubscribe(owner)886 bmp.source_branch.unsubscribe(owner, owner)
883 recipients = bmp.getNotificationRecipients(887 recipients = bmp.getNotificationRecipients(
884 CodeReviewNotificationLevel.STATUS)888 CodeReviewNotificationLevel.STATUS)
885 self.assertFalse(owner in recipients)889 self.assertFalse(owner in recipients)
@@ -892,20 +896,20 @@
892 eric = self.factory.makePerson()896 eric = self.factory.makePerson()
893 bmp.source_branch.subscribe(897 bmp.source_branch.subscribe(
894 eric, BranchSubscriptionNotificationLevel.NOEMAIL, None,898 eric, BranchSubscriptionNotificationLevel.NOEMAIL, None,
895 CodeReviewNotificationLevel.FULL)899 CodeReviewNotificationLevel.FULL, eric)
896 # Subscribe bob to the target branch only.900 # Subscribe bob to the target branch only.
897 bob = self.factory.makePerson()901 bob = self.factory.makePerson()
898 bmp.target_branch.subscribe(902 bmp.target_branch.subscribe(
899 bob, BranchSubscriptionNotificationLevel.NOEMAIL, None,903 bob, BranchSubscriptionNotificationLevel.NOEMAIL, None,
900 CodeReviewNotificationLevel.FULL)904 CodeReviewNotificationLevel.FULL, bob)
901 # Subscribe charlie to both.905 # Subscribe charlie to both.
902 charlie = self.factory.makePerson()906 charlie = self.factory.makePerson()
903 bmp.source_branch.subscribe(907 bmp.source_branch.subscribe(
904 charlie, BranchSubscriptionNotificationLevel.NOEMAIL, None,908 charlie, BranchSubscriptionNotificationLevel.NOEMAIL, None,
905 CodeReviewNotificationLevel.FULL)909 CodeReviewNotificationLevel.FULL, charlie)
906 bmp.target_branch.subscribe(910 bmp.target_branch.subscribe(
907 charlie, BranchSubscriptionNotificationLevel.NOEMAIL, None,911 charlie, BranchSubscriptionNotificationLevel.NOEMAIL, None,
908 CodeReviewNotificationLevel.FULL)912 CodeReviewNotificationLevel.FULL, charlie)
909 # Make both branches private.913 # Make both branches private.
910 removeSecurityProxy(bmp.source_branch).private = True914 removeSecurityProxy(bmp.source_branch).private = True
911 removeSecurityProxy(bmp.target_branch).private = True915 removeSecurityProxy(bmp.target_branch).private = True
@@ -1157,7 +1161,7 @@
1157 proposal.source_branch.subscribe(1161 proposal.source_branch.subscribe(
1158 subscriber,1162 subscriber,
1159 BranchSubscriptionNotificationLevel.NOEMAIL, None,1163 BranchSubscriptionNotificationLevel.NOEMAIL, None,
1160 CodeReviewNotificationLevel.NOEMAIL)1164 CodeReviewNotificationLevel.NOEMAIL, subscriber)
1161 self.assertEqual(1165 self.assertEqual(
1162 ['~albert/mike/work', '~albert/november/work'],1166 ['~albert/mike/work', '~albert/november/work'],
1163 self._get_merge_proposals(albert, visible_by_user=subscriber))1167 self._get_merge_proposals(albert, visible_by_user=subscriber))
11641168
=== added file 'lib/lp/code/model/tests/test_branchsubscription.py'
--- lib/lp/code/model/tests/test_branchsubscription.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/model/tests/test_branchsubscription.py 2010-05-29 09:01:05 +0000
@@ -0,0 +1,115 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Tests for the BranchSubscrptions model object.."""
5
6from __future__ import with_statement
7
8__metaclass__ = type
9
10import unittest
11
12from canonical.testing.layers import DatabaseFunctionalLayer
13from lp.app.errors import UserCannotUnsubscribePerson
14from lp.code.enums import (
15 BranchSubscriptionNotificationLevel, CodeReviewNotificationLevel)
16from lp.testing import TestCaseWithFactory, person_logged_in
17
18
19class TestBranchSubscriptions(TestCaseWithFactory):
20 """Tests relating to branch subscriptions in general."""
21
22 layer = DatabaseFunctionalLayer
23
24 def test_subscribed_by_set(self):
25 """The user subscribing is recorded along the subscriber."""
26 subscriber = self.factory.makePerson()
27 subscribed_by = self.factory.makePerson()
28 branch = self.factory.makeAnyBranch()
29 subscription = branch.subscribe(
30 subscriber, BranchSubscriptionNotificationLevel.NOEMAIL, None,
31 CodeReviewNotificationLevel.NOEMAIL, subscribed_by)
32 self.assertEqual(subscriber, subscription.person)
33 self.assertEqual(subscribed_by, subscription.subscribed_by)
34
35 def test_unsubscribe(self):
36 """Test unsubscribing by the subscriber."""
37 subscription = self.factory.makeBranchSubscription()
38 subscriber = subscription.person
39 branch = subscription.branch
40 branch.unsubscribe(subscriber, subscriber)
41 self.assertFalse(branch.hasSubscription(subscriber))
42
43 def test_unsubscribe_by_subscriber(self):
44 """Test unsubscribing by the person who subscribed the user."""
45 subscribed_by = self.factory.makePerson()
46 subscription = self.factory.makeBranchSubscription(
47 subscribed_by=subscribed_by)
48 subscriber = subscription.person
49 branch = subscription.branch
50 branch.unsubscribe(subscriber, subscribed_by)
51 self.assertFalse(branch.hasSubscription(subscriber))
52
53 def test_unsubscribe_by_unauthorized(self):
54 """Test unsubscribing someone you shouldn't be able to."""
55 subscription = self.factory.makeBranchSubscription()
56 branch = subscription.branch
57 self.assertRaises(
58 UserCannotUnsubscribePerson,
59 branch.unsubscribe,
60 subscription.person,
61 self.factory.makePerson())
62
63
64class TestBranchSubscriptionCanBeUnsubscribedbyUser(TestCaseWithFactory):
65 """Tests for BranchSubscription.canBeUnsubscribedByUser."""
66
67 layer = DatabaseFunctionalLayer
68
69 def test_none(self):
70 """None for a user always returns False."""
71 subscription = self.factory.makeBranchSubscription()
72 self.assertFalse(subscription.canBeUnsubscribedByUser(None))
73
74 def test_self_subscriber(self):
75 """The subscriber has permission to unsubscribe."""
76 subscription = self.factory.makeBranchSubscription()
77 self.assertTrue(
78 subscription.canBeUnsubscribedByUser(subscription.person))
79
80 def test_non_subscriber_fails(self):
81 """An unrelated person can't unsubscribe a user."""
82 subscription = self.factory.makeBranchSubscription()
83 editor = self.factory.makePerson()
84 self.assertFalse(subscription.canBeUnsubscribedByUser(editor))
85
86 def test_subscribed_by(self):
87 """If a user subscribes someone else, the user can unsubscribe."""
88 subscribed_by = self.factory.makePerson()
89 subscriber = self.factory.makePerson()
90 subscription = self.factory.makeBranchSubscription(
91 person=subscriber, subscribed_by=subscribed_by)
92 self.assertTrue(subscription.canBeUnsubscribedByUser(subscribed_by))
93
94 def test_team_member_can_unsubscribe(self):
95 """Any team member can unsubscribe the team from a branch."""
96 team = self.factory.makeTeam()
97 member = self.factory.makePerson()
98 with person_logged_in(team.teamowner):
99 team.addMember(member, team.teamowner)
100 subscription = self.factory.makeBranchSubscription(
101 person=team, subscribed_by=team.teamowner)
102 self.assertTrue(subscription.canBeUnsubscribedByUser(member))
103
104 def test_team_subscriber_can_unsubscribe(self):
105 """A team can be unsubscribed by the subscriber even if they are not a
106 member."""
107 team = self.factory.makeTeam()
108 subscribed_by = self.factory.makePerson()
109 subscription = self.factory.makeBranchSubscription(
110 person=team, subscribed_by=subscribed_by)
111 self.assertTrue(subscription.canBeUnsubscribedByUser(subscribed_by))
112
113
114def test_suite():
115 return unittest.TestLoader().loadTestsFromName(__name__)
0116
=== modified file 'lib/lp/code/scripts/tests/test_scan_branches.py'
--- lib/lp/code/scripts/tests/test_scan_branches.py 2010-04-28 05:40:02 +0000
+++ lib/lp/code/scripts/tests/test_scan_branches.py 2010-05-29 09:01:05 +0000
@@ -52,7 +52,8 @@
52 db_branch.registrant,52 db_branch.registrant,
53 BranchSubscriptionNotificationLevel.FULL,53 BranchSubscriptionNotificationLevel.FULL,
54 BranchSubscriptionDiffSize.WHOLEDIFF,54 BranchSubscriptionDiffSize.WHOLEDIFF,
55 CodeReviewNotificationLevel.FULL)55 CodeReviewNotificationLevel.FULL,
56 db_branch.registrant)
56 transaction.commit()57 transaction.commit()
5758
58 self.run_script_and_assert_success()59 self.run_script_and_assert_success()
5960
=== modified file 'lib/lp/code/scripts/tests/test_sendbranchmail.py'
--- lib/lp/code/scripts/tests/test_sendbranchmail.py 2010-04-28 05:40:02 +0000
+++ lib/lp/code/scripts/tests/test_sendbranchmail.py 2010-05-29 09:01:05 +0000
@@ -24,10 +24,12 @@
2424
25 def createBranch(self):25 def createBranch(self):
26 branch, tree = self.create_branch_and_tree()26 branch, tree = self.create_branch_and_tree()
27 branch.subscribe(branch.registrant,27 branch.subscribe(
28 branch.registrant,
28 BranchSubscriptionNotificationLevel.FULL,29 BranchSubscriptionNotificationLevel.FULL,
29 BranchSubscriptionDiffSize.WHOLEDIFF,30 BranchSubscriptionDiffSize.WHOLEDIFF,
30 CodeReviewNotificationLevel.FULL)31 CodeReviewNotificationLevel.FULL,
32 branch.registrant)
31 transport = tree.bzrdir.root_transport33 transport = tree.bzrdir.root_transport
32 transport.put_bytes('foo', 'bar')34 transport.put_bytes('foo', 'bar')
33 tree.add('foo')35 tree.add('foo')
3436
=== added file 'lib/lp/code/security.py'
--- lib/lp/code/security.py 1970-01-01 00:00:00 +0000
+++ lib/lp/code/security.py 2010-05-29 09:01:05 +0000
@@ -0,0 +1,37 @@
1# Copyright 2010 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).
3
4"""Security adapters for the code module."""
5
6__metaclass__ = type
7__all__ = [
8 'BranchSubscriptionEdit',
9 'BranchSubscriptionView',
10 ]
11
12from canonical.launchpad.security import AuthorizationBase
13
14from lp.code.interfaces.branchsubscription import IBranchSubscription
15
16
17
18class BranchSubscriptionEdit(AuthorizationBase):
19 permission = 'launchpad.Edit'
20 usedfor = IBranchSubscription
21
22 def checkAuthenticated(self, user):
23 """Is the user able to edit a branch subscription?
24
25 Any team member can edit a branch subscription for their team.
26 Launchpad Admins can also edit any branch subscription.
27 """
28 return (user.inTeam(self.obj.person) or
29 user.inTeam(self.obj.subscribed_by) or
30 user.in_admin or
31 user.in_bazaar_experts)
32
33
34class BranchSubscriptionView(BranchSubscriptionEdit):
35 permission = 'launchpad.View'
36
37
038
=== modified file 'lib/lp/code/stories/branches/xx-branch-edit.txt'
--- lib/lp/code/stories/branches/xx-branch-edit.txt 2010-05-24 04:27:46 +0000
+++ lib/lp/code/stories/branches/xx-branch-edit.txt 2010-05-29 09:01:05 +0000
@@ -223,7 +223,7 @@
223 >>> _unused = foogoo_svn.subscribe(sample_person,223 >>> _unused = foogoo_svn.subscribe(sample_person,
224 ... BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,224 ... BranchSubscriptionNotificationLevel.ATTRIBUTEONLY,
225 ... BranchSubscriptionDiffSize.NODIFF,225 ... BranchSubscriptionDiffSize.NODIFF,
226 ... CodeReviewNotificationLevel.NOEMAIL)226 ... CodeReviewNotificationLevel.NOEMAIL, sample_person)
227 >>> logout()227 >>> logout()
228228
229 >>> nopriv_browser = setupBrowser(auth='Basic no-priv@canonical.com:test')229 >>> nopriv_browser = setupBrowser(auth='Basic no-priv@canonical.com:test')
230230
=== modified file 'lib/lp/code/stories/branches/xx-branchmergeproposals.txt'
--- lib/lp/code/stories/branches/xx-branchmergeproposals.txt 2010-04-07 16:05:38 +0000
+++ lib/lp/code/stories/branches/xx-branchmergeproposals.txt 2010-05-29 09:01:05 +0000
@@ -19,7 +19,7 @@
19 ... '~name12/firefox/main')19 ... '~name12/firefox/main')
20 >>> _unused = branch.subscribe(subscriber,20 >>> _unused = branch.subscribe(subscriber,
21 ... BranchSubscriptionNotificationLevel.NOEMAIL, None,21 ... BranchSubscriptionNotificationLevel.NOEMAIL, None,
22 ... CodeReviewNotificationLevel.FULL)22 ... CodeReviewNotificationLevel.FULL, subscriber)
2323
24Also subscribe to gnome-terminal, since Firefox tests are mixed with24Also subscribe to gnome-terminal, since Firefox tests are mixed with
25Gnome Terminal tests.25Gnome Terminal tests.
@@ -28,7 +28,7 @@
28 ... '~name12/gnome-terminal/main')28 ... '~name12/gnome-terminal/main')
29 >>> _unused = branch.subscribe(subscriber,29 >>> _unused = branch.subscribe(subscriber,
30 ... BranchSubscriptionNotificationLevel.NOEMAIL, None,30 ... BranchSubscriptionNotificationLevel.NOEMAIL, None,
31 ... CodeReviewNotificationLevel.FULL)31 ... CodeReviewNotificationLevel.FULL, subscriber)
32 >>> from lp.code.tests.helpers import make_erics_fooix_project32 >>> from lp.code.tests.helpers import make_erics_fooix_project
33 >>> locals().update(make_erics_fooix_project(factory))33 >>> locals().update(make_erics_fooix_project(factory))
34 >>> logout()34 >>> logout()
3535
=== modified file 'lib/lp/code/stories/branches/xx-person-branches.txt'
--- lib/lp/code/stories/branches/xx-person-branches.txt 2010-05-13 16:22:19 +0000
+++ lib/lp/code/stories/branches/xx-person-branches.txt 2010-05-29 09:01:05 +0000
@@ -107,7 +107,7 @@
107107
108 >>> login(ANONYMOUS)108 >>> login(ANONYMOUS)
109 >>> b2 = factory.makeAnyBranch(owner=eric)109 >>> b2 = factory.makeAnyBranch(owner=eric)
110 >>> ignored = b2.unsubscribe(eric)110 >>> ignored = b2.unsubscribe(eric, eric)
111 >>> logout()111 >>> logout()
112112
113 >>> eric_browser.open('http://code.launchpad.dev/~eric')113 >>> eric_browser.open('http://code.launchpad.dev/~eric')
114114
=== modified file 'lib/lp/code/stories/branches/xx-subscribing-branches.txt'
--- lib/lp/code/stories/branches/xx-subscribing-branches.txt 2010-04-20 04:12:30 +0000
+++ lib/lp/code/stories/branches/xx-subscribing-branches.txt 2010-05-29 09:01:05 +0000
@@ -190,25 +190,8 @@
190 >>> browser.getControl('Person').value = 'landscape-developers'190 >>> browser.getControl('Person').value = 'landscape-developers'
191 >>> browser.getControl('Subscribe').click()191 >>> browser.getControl('Subscribe').click()
192192
193The user can only subscribe teams that they are members of.193The user does not have to be in the team to subscribe them.
194194
195 >>> for message in get_feedback_messages(browser.contents):
196 ... print extract_text(message)
197 There is 1 error.
198 You can only subscribe teams that you are a member of.
199
200Sample Person is a member of Landscape Developers.
201
202 >>> browser = setupBrowser(auth='Basic test@canonical.com:test')
203 >>> browser.open(
204 ... 'http://code.launchpad.dev/~name12/gnome-terminal/main')
205 >>> browser.getLink('Subscribe someone else').click()
206 >>> browser.getControl('Notification Level').displayValue = [
207 ... 'Branch attribute and revision notifications']
208 >>> browser.getControl('Generated Diff Size Limit').displayValue = [
209 ... '1000 lines']
210 >>> browser.getControl('Person').value = 'landscape-developers'
211 >>> browser.getControl('Subscribe').click()
212 >>> print_informational_message(browser.contents)195 >>> print_informational_message(browser.contents)
213 Landscape Developers has been subscribed to this branch with:196 Landscape Developers has been subscribed to this branch with:
214 Send notifications for both branch attribute updates197 Send notifications for both branch attribute updates
@@ -234,10 +217,10 @@
234Editing a team subscription217Editing a team subscription
235===========================218===========================
236219
237In order to edit a team subscription the logged in user needs to be220In order to edit a team subscription the logged in user needs to be a member
238a member of the team that is subscribed. There is a link shown in221of the team that is subscribed, or must the the person who subscribed the team
239the subscriptions portlet to edit the subscription of a team that222to the branch. There is a link shown in the subscriptions portlet to edit the
240the logged in user is a member of.223subscription of a team that the logged in user is a member of.
241224
242XXX: thumper 2007-06-11225XXX: thumper 2007-06-11
243There should be a central user subscriptions page. This could then226There should be a central user subscriptions page. This could then
@@ -293,7 +276,7 @@
293 ... CodeReviewNotificationLevel)276 ... CodeReviewNotificationLevel)
294 >>> ignored = branch.subscribe(277 >>> ignored = branch.subscribe(
295 ... private_team, BranchSubscriptionNotificationLevel.NOEMAIL, None,278 ... private_team, BranchSubscriptionNotificationLevel.NOEMAIL, None,
296 ... CodeReviewNotificationLevel.NOEMAIL)279 ... CodeReviewNotificationLevel.NOEMAIL, private_team.teamowner)
297 >>> url = canonical_url(branch)280 >>> url = canonical_url(branch)
298 >>> logout()281 >>> logout()
299282
300283
=== modified file 'lib/lp/code/stories/webservice/xx-branchsubscription.txt'
--- lib/lp/code/stories/webservice/xx-branchsubscription.txt 2009-06-16 03:40:05 +0000
+++ lib/lp/code/stories/webservice/xx-branchsubscription.txt 2010-05-29 09:01:05 +0000
@@ -38,6 +38,7 @@
38 resource_type_link: u'http://.../#branch_subscription'38 resource_type_link: u'http://.../#branch_subscription'
39 review_level: u'No email'39 review_level: u'No email'
40 self_link: u'http://.../~farmer-bob/farm/corn/+subscription/farmer-joe'40 self_link: u'http://.../~farmer-bob/farm/corn/+subscription/farmer-joe'
41 subscribed_by_link: u'http://.../~salgado'
4142
42 >>> def print_subscriber_count(branch):43 >>> def print_subscriber_count(branch):
43 ... subscribers = webservice.get(44 ... subscribers = webservice.get(
@@ -91,6 +92,7 @@
91 resource_type_link: u'http://.../#branch_subscription'92 resource_type_link: u'http://.../#branch_subscription'
92 review_level: u'Status changes only'93 review_level: u'Status changes only'
93 self_link: u'http://.../~farmer-bob/farm/corn/+subscription/farmer-joe'94 self_link: u'http://.../~farmer-bob/farm/corn/+subscription/farmer-joe'
95 subscribed_by_link: u'http://.../~salgado'
9496
9597
96We print the count, and even though subscribe was called again, there's still98We print the count, and even though subscribe was called again, there's still
9799
=== modified file 'lib/lp/code/tests/test_branch.py'
--- lib/lp/code/tests/test_branch.py 2010-05-27 01:46:06 +0000
+++ lib/lp/code/tests/test_branch.py 2010-05-29 09:01:05 +0000
@@ -134,7 +134,7 @@
134 removeSecurityProxy(branch).subscribe(134 removeSecurityProxy(branch).subscribe(
135 person, BranchSubscriptionNotificationLevel.NOEMAIL,135 person, BranchSubscriptionNotificationLevel.NOEMAIL,
136 BranchSubscriptionDiffSize.NODIFF,136 BranchSubscriptionDiffSize.NODIFF,
137 CodeReviewNotificationLevel.NOEMAIL)137 CodeReviewNotificationLevel.NOEMAIL, person)
138 self.assertAuthenticatedView(branch, person, True)138 self.assertAuthenticatedView(branch, person, True)
139139
140 def test_privateBranchAnyoneElse(self):140 def test_privateBranchAnyoneElse(self):
141141
=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
--- lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-05-03 09:31:06 +0000
+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py 2010-05-29 09:01:05 +0000
@@ -85,7 +85,7 @@
85 LaunchpadZopelessLayer.txn.begin()85 LaunchpadZopelessLayer.txn.begin()
86 new_branch = self.factory.makeAnyBranch(*args, **kwargs)86 new_branch = self.factory.makeAnyBranch(*args, **kwargs)
87 # Unsubscribe the implicit owner subscription.87 # Unsubscribe the implicit owner subscription.
88 new_branch.unsubscribe(new_branch.owner)88 new_branch.unsubscribe(new_branch.owner, new_branch.owner)
89 LaunchpadZopelessLayer.txn.commit()89 LaunchpadZopelessLayer.txn.commit()
90 return new_branch90 return new_branch
9191
9292
=== modified file 'lib/lp/codehosting/scanner/tests/test_email.py'
--- lib/lp/codehosting/scanner/tests/test_email.py 2010-05-18 13:12:34 +0000
+++ lib/lp/codehosting/scanner/tests/test_email.py 2010-05-29 09:01:05 +0000
@@ -41,7 +41,8 @@
41 test_user,41 test_user,
42 BranchSubscriptionNotificationLevel.FULL,42 BranchSubscriptionNotificationLevel.FULL,
43 BranchSubscriptionDiffSize.FIVEKLINES,43 BranchSubscriptionDiffSize.FIVEKLINES,
44 CodeReviewNotificationLevel.NOEMAIL)44 CodeReviewNotificationLevel.NOEMAIL,
45 test_user)
45 LaunchpadZopelessLayer.txn.commit()46 LaunchpadZopelessLayer.txn.commit()
46 return branch47 return branch
4748
@@ -151,7 +152,8 @@
151 db_branch.registrant,152 db_branch.registrant,
152 BranchSubscriptionNotificationLevel.FULL,153 BranchSubscriptionNotificationLevel.FULL,
153 BranchSubscriptionDiffSize.WHOLEDIFF,154 BranchSubscriptionDiffSize.WHOLEDIFF,
154 CodeReviewNotificationLevel.FULL)155 CodeReviewNotificationLevel.FULL,
156 db_branch.registrant)
155 self.assertEqual(0, len(list(RevisionMailJob.iterReady())))157 self.assertEqual(0, len(list(RevisionMailJob.iterReady())))
156 notify(events.TipChanged(db_branch, tree.branch, True))158 notify(events.TipChanged(db_branch, tree.branch, True))
157 self.assertEqual(1, len(list(RevisionMailJob.iterReady())))159 self.assertEqual(1, len(list(RevisionMailJob.iterReady())))
@@ -164,7 +166,8 @@
164 db_branch.registrant,166 db_branch.registrant,
165 BranchSubscriptionNotificationLevel.FULL,167 BranchSubscriptionNotificationLevel.FULL,
166 BranchSubscriptionDiffSize.WHOLEDIFF,168 BranchSubscriptionDiffSize.WHOLEDIFF,
167 CodeReviewNotificationLevel.FULL)169 CodeReviewNotificationLevel.FULL,
170 db_branch.registrant)
168 self.assertEqual(0, len(list(RevisionMailJob.iterReady())))171 self.assertEqual(0, len(list(RevisionMailJob.iterReady())))
169 notify(events.RevisionsRemoved(db_branch, tree.branch, ['x']))172 notify(events.RevisionsRemoved(db_branch, tree.branch, ['x']))
170 self.assertEqual(1, len(list(RevisionMailJob.iterReady())))173 self.assertEqual(1, len(list(RevisionMailJob.iterReady())))
171174
=== modified file 'lib/lp/codehosting/tests/test_jobs.py'
--- lib/lp/codehosting/tests/test_jobs.py 2010-04-23 05:49:08 +0000
+++ lib/lp/codehosting/tests/test_jobs.py 2010-05-29 09:01:05 +0000
@@ -30,7 +30,7 @@
30 branch.subscribe(branch.registrant,30 branch.subscribe(branch.registrant,
31 BranchSubscriptionNotificationLevel.FULL,31 BranchSubscriptionNotificationLevel.FULL,
32 BranchSubscriptionDiffSize.WHOLEDIFF,32 BranchSubscriptionDiffSize.WHOLEDIFF,
33 CodeReviewNotificationLevel.FULL)33 CodeReviewNotificationLevel.FULL, branch.registrant)
34 tree_transport = tree.bzrdir.root_transport34 tree_transport = tree.bzrdir.root_transport
35 tree_transport.put_bytes("hello.txt", "Hello World\n")35 tree_transport.put_bytes("hello.txt", "Hello World\n")
36 tree.add('hello.txt')36 tree.add('hello.txt')
3737
=== modified file 'lib/lp/registry/browser/tests/packaging-views.txt'
--- lib/lp/registry/browser/tests/packaging-views.txt 2010-04-16 18:00:31 +0000
+++ lib/lp/registry/browser/tests/packaging-views.txt 2010-05-29 09:01:05 +0000
@@ -350,5 +350,5 @@
350 cnews350 cnews
351 libstdc++351 libstdc++
352 linux-source-2.6.15352 linux-source-2.6.15
353 hot
353 thunderbird354 thunderbird
354 hot
355355
=== modified file 'lib/lp/registry/browser/tests/private-team-creation-views.txt'
--- lib/lp/registry/browser/tests/private-team-creation-views.txt 2009-08-03 21:46:09 +0000
+++ lib/lp/registry/browser/tests/private-team-creation-views.txt 2010-05-29 09:01:05 +0000
@@ -411,7 +411,7 @@
411 ... team,411 ... team,
412 ... BranchSubscriptionNotificationLevel.DIFFSONLY,412 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
413 ... BranchSubscriptionDiffSize.WHOLEDIFF,413 ... BranchSubscriptionDiffSize.WHOLEDIFF,
414 ... CodeReviewNotificationLevel.STATUS)414 ... CodeReviewNotificationLevel.STATUS, team_owner)
415 ... # A branch visibility rule.415 ... # A branch visibility rule.
416 ... from lp.code.enums import BranchVisibilityRule416 ... from lp.code.enums import BranchVisibilityRule
417 ... from lp.registry.interfaces.product import IProductSet417 ... from lp.registry.interfaces.product import IProductSet
418418
=== modified file 'lib/lp/registry/doc/private-team-roles.txt'
--- lib/lp/registry/doc/private-team-roles.txt 2010-04-12 08:04:31 +0000
+++ lib/lp/registry/doc/private-team-roles.txt 2010-05-29 09:01:05 +0000
@@ -130,7 +130,7 @@
130 ... priv_team,130 ... priv_team,
131 ... BranchSubscriptionNotificationLevel.DIFFSONLY,131 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
132 ... BranchSubscriptionDiffSize.WHOLEDIFF,132 ... BranchSubscriptionDiffSize.WHOLEDIFF,
133 ... CodeReviewNotificationLevel.STATUS)133 ... CodeReviewNotificationLevel.STATUS, team_owner)
134 >>> print subscription.person.name134 >>> print subscription.person.name
135 private-team135 private-team
136136
@@ -141,7 +141,7 @@
141 ... pm_team,141 ... pm_team,
142 ... BranchSubscriptionNotificationLevel.DIFFSONLY,142 ... BranchSubscriptionNotificationLevel.DIFFSONLY,
143 ... BranchSubscriptionDiffSize.WHOLEDIFF,143 ... BranchSubscriptionDiffSize.WHOLEDIFF,
144 ... CodeReviewNotificationLevel.STATUS)144 ... CodeReviewNotificationLevel.STATUS, team_owner)
145 Traceback (most recent call last):145 Traceback (most recent call last):
146 ...146 ...
147 PrivatePersonLinkageError: Cannot link person147 PrivatePersonLinkageError: Cannot link person
148148
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-05-19 15:39:52 +0000
+++ lib/lp/registry/model/distroseries.py 2010-05-29 09:01:05 +0000
@@ -332,7 +332,7 @@
332 origin = SQL(joins)332 origin = SQL(joins)
333 condition = SQL(conditions + "AND packaging.id IS NULL")333 condition = SQL(conditions + "AND packaging.id IS NULL")
334 results = IStore(self).using(origin).find(find_spec, condition)334 results = IStore(self).using(origin).find(find_spec, condition)
335 results = results.order_by('score DESC')335 results = results.order_by('score DESC', SourcePackageName.name)
336 return [{336 return [{
337 'package': SourcePackage(337 'package': SourcePackage(
338 sourcepackagename=spn, distroseries=self),338 sourcepackagename=spn, distroseries=self),
339339
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-05-28 23:06:25 +0000
+++ lib/lp/testing/factory.py 2010-05-29 09:01:05 +0000
@@ -988,7 +988,8 @@
988988
989 return proposal989 return proposal
990990
991 def makeBranchSubscription(self, branch=None, person=None):991 def makeBranchSubscription(self, branch=None, person=None,
992 subscribed_by=None):
992 """Create a BranchSubscription.993 """Create a BranchSubscription.
993994
994 :param branch_title: The title to use for the created Branch995 :param branch_title: The title to use for the created Branch
@@ -998,9 +999,11 @@
998 branch = self.makeBranch()999 branch = self.makeBranch()
999 if person is None:1000 if person is None:
1000 person = self.makePerson()1001 person = self.makePerson()
1002 if subscribed_by is None:
1003 subscribed_by = person
1001 return branch.subscribe(person,1004 return branch.subscribe(person,
1002 BranchSubscriptionNotificationLevel.NOEMAIL, None,1005 BranchSubscriptionNotificationLevel.NOEMAIL, None,
1003 CodeReviewNotificationLevel.NOEMAIL)1006 CodeReviewNotificationLevel.NOEMAIL, subscribed_by)
10041007
1005 def makeDiff(self, diff_text=DIFF):1008 def makeDiff(self, diff_text=DIFF):
1006 return Diff.fromFile(StringIO(diff_text), len(diff_text))1009 return Diff.fromFile(StringIO(diff_text), len(diff_text))

Subscribers

People subscribed via source and target branches

to status/vote changes: