Merge lp:~thumper/launchpad/branch-move-methods into lp:launchpad

Proposed by Tim Penhey
Status: Merged
Merged at revision: not available
Proposed branch: lp:~thumper/launchpad/branch-move-methods
Merge into: lp:launchpad
Diff against target: None lines
To merge this branch: bzr merge lp:~thumper/launchpad/branch-move-methods
Reviewer Review Type Date Requested Status
Michael Hudson-Doyle Approve
Review via email: mp+9559@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Tim Penhey (thumper) wrote :
Download full text (3.2 KiB)

= Summary =

This branch adds setOwner and setTarget to IBranch.

IBranch.owner and IBranch.product are changed to be read only in the interface
and API.

== Implementation details ==

Both setOwner and setTarget defer to the IBranchNamespace.moveBranch method.
This method does the validation checks for both owner setting, name clashes,
and visibility policy checks.

The LaunchpadEditForm view updateContextFromData was modified to take a boolean
to determine whether or not the ObjectModifiedEvent listeners should be
notified. This is because the setOwner call needs to remove the 'owner' key
out of the dict as the updateContextFromData will fail if it is there, but we
still want to show that the change has occurred in the email.

A number of changes are just noise in the move to make owner and product read
only through the interface.

== Tests ==

TestBranchSetOwner
TestBranchSetTarget
TestBranchNamespaceMoveBranch
test_retargetBranch

== Demo and Q/A ==

Not much has chanced in the UI. If you can still change the owner of the
branch, then we have succeeded.

= Launchpad lint =

Checking for conflicts. and issues in doctests and templates.
Running jslint, xmllint, pyflakes, and pylint.
Using normal rules.

Linting changed files:
  lib/canonical/launchpad/webapp/launchpadform.py
  lib/lp/code/interfaces/branchtarget.py
  lib/lp/code/configure.zcml
  lib/lp/code/browser/branch.py
  lib/lp/code/interfaces/branch.py
  lib/lp/registry/doc/private-team-roles.txt
  lib/lp/code/interfaces/branchnamespace.py
  lib/lp/registry/browser/tests/private-team-creation-views.txt
  lib/lp/code/model/branchtarget.py
  lib/lp/codehosting/tests/test_acceptance.py
  lib/lp/code/model/branch.py
  lib/lp/code/browser/tests/test_branchmergeproposallisting.py
  lib/lp/code/model/tests/test_branchtarget.py
  lib/lp/code/model/tests/test_branchnamespace.py
  lib/lp/code/model/branchnamespace.py
  lib/lp/code/doc/branch.txt
  lib/lp/code/model/tests/test_revision.py
  lib/lp/code/scripts/tests/test_revisionkarma.py
  lib/canonical/launchpad/interfaces/_schema_circular_imports.py
  lib/lp/code/model/tests/test_branch.py

== Pyflakes notices ==

lib/canonical/launchpad/webapp/launchpadform.py
    22: 'action' imported but unused
    94: redefinition of unused 'action' from line 22
    195: redefinition of unused 'action' from line 22

lib/lp/code/model/branch.py
    73: 'IPerson' imported but unused

lib/lp/code/model/tests/test_branch.py
    61: 'IProductSet' imported but unused

== Pylint notices ==

lib/canonical/launchpad/webapp/launchpadform.py
    245: [W0211, LaunchpadFormView.validate_none] Static method with 'self' as
first argument

lib/lp/code/browser/branch.py
    48: [F0401] Unable to import 'lazr.uri' (No module named uri)

lib/lp/code/model/branch.py
    186: [C0301] Line too long (79/78)
    73: [W0611] Unused import IPerson

lib/lp/code/browser/tests/test_branchmergeproposallisting.py
    16: [C0301] Line too long (79/78)

lib/lp/code/scripts/tests/test_revisionkarma.py
    52: [C0322, TestRevisionKarma.test_junkBranchMoved] Operator not preceded
by a space
    project=self.factory.makeProduct()
    ^

lib/lp/code/model/tests/test_branch.py...

Read more...

Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :
Download full text (39.3 KiB)

I think, on reviewing this branch, that I'd rather you'd have done the
setOwner thing and moveBranch thing in separate branches, if that
would have been possible. Oh well!

Approved, subject to hiding retargetBranch a little harder. Comments
below.

Cheers,
mwh

> === modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
> --- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-07-17 00:26:05 +0000
> +++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-08-03 03:36:16 +0000
> @@ -75,6 +75,11 @@
> IBranch['linkSpecification'].queryTaggedValue(
> LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema= ISpecification
> IBranch['product'].schema = IProduct
> +IBranch['setTarget'].queryTaggedValue(
> + LAZR_WEBSERVICE_EXPORTED)['params']['project'].schema= IProduct
> +IBranch['setTarget'].queryTaggedValue(
> + LAZR_WEBSERVICE_EXPORTED)['params']['source_package'].schema= \
> + ISourcePackage
> IBranch['spec_links'].value_type.schema = ISpecificationBranch
> IBranch['subscribe'].queryTaggedValue(
> LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = IBranchSubscription

I guess once the lp.foo dependencies are properly sorted out, we won't
need this kind of rubbish so much any more....

> === modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
> --- lib/canonical/launchpad/webapp/launchpadform.py 2009-06-25 05:30:52 +0000
> +++ lib/canonical/launchpad/webapp/launchpadform.py 2009-08-03 03:36:16 +0000
> @@ -372,7 +372,7 @@
>
> render_context = True
>
> - def updateContextFromData(self, data, context=None):
> + def updateContextFromData(self, data, context=None, notify_modified=True):
> """Update the context object based on form data.
>
> If no context is given, the view's context is used.

It's micro-optimization, but I don't think there's any reason to take
the snapshot if you're not going to use it.

> @@ -391,7 +391,7 @@
>
> was_changed = form.applyChanges(context, self.form_fields,
> data, self.adapters)
> - if was_changed:
> + if was_changed and notify_modified:
> field_names = [form_field.__name__
> for form_field in self.form_fields]
> notify(ObjectModifiedEvent(

> === modified file 'lib/lp/code/browser/branch.py'
> --- lib/lp/code/browser/branch.py 2009-07-19 04:41:14 +0000
> +++ lib/lp/code/browser/branch.py 2009-08-03 03:36:16 +0000
> @@ -36,12 +36,15 @@
> from zope.app.form.browser import TextAreaWidget
> from zope.traversing.interfaces import IPathAdapter
> from zope.component import getUtility, queryAdapter
> +from zope.event import notify
> from zope.formlib import form
> -from zope.interface import Interface, implements
> +from zope.interface import Interface, implements, providedBy
> from zope.publisher.interfaces import NotFound
> from zope.schema import Choice, Text
> from lazr.delegates import delegates
> from lazr.enum import EnumeratedType, Item
> +from lazr.lifecycle.event import ObjectModifiedEvent
> +from lazr.lifecycle.snapshot import Snapshot
> from lazr.uri import URI
>
> from...

review: Approve
Revision history for this message
Tim Penhey (thumper) wrote :
Download full text (13.9 KiB)

On Mon, 03 Aug 2009 17:54:59 Michael Hudson wrote:
> Review: Approve
> I think, on reviewing this branch, that I'd rather you'd have done the
> setOwner thing and moveBranch thing in separate branches, if that
> would have been possible. Oh well!

Probably, but it wasn't until I realised what was needed that it got done
together. I guess I could have worked harder to separate the bits. Perhaps
next time :)

> > === modified file
> > 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py' ---
> > lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-07-17
> > 00:26:05 +0000 +++
> > lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-08-03
> > 03:36:16 +0000 @@ -75,6 +75,11 @@
> > IBranch['linkSpecification'].queryTaggedValue(
> > LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema= ISpecification
> > IBranch['product'].schema = IProduct
> > +IBranch['setTarget'].queryTaggedValue(
> > + LAZR_WEBSERVICE_EXPORTED)['params']['project'].schema= IProduct
> > +IBranch['setTarget'].queryTaggedValue(
> > + LAZR_WEBSERVICE_EXPORTED)['params']['source_package'].schema= \
> > + ISourcePackage
> > IBranch['spec_links'].value_type.schema = ISpecificationBranch
> > IBranch['subscribe'].queryTaggedValue(
> > LAZR_WEBSERVICE_EXPORTED)['return_type'].schema =
> > IBranchSubscription
>
> I guess once the lp.foo dependencies are properly sorted out, we won't
> need this kind of rubbish so much any more....

Unlikely, as IProduct imports some bits that need IBranch, so we have the
classical circular dependency.

> > === modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
> > --- lib/canonical/launchpad/webapp/launchpadform.py 2009-06-25 05:30:52
> > +0000 +++ lib/canonical/launchpad/webapp/launchpadform.py 2009-08-03
> > 03:36:16 +0000 @@ -372,7 +372,7 @@
> >
> > render_context = True
> >
> > - def updateContextFromData(self, data, context=None):
> > + def updateContextFromData(self, data, context=None,
> > notify_modified=True): """Update the context object based on form data.
> >
> > If no context is given, the view's context is used.
>
> It's micro-optimization, but I don't think there's any reason to take
> the snapshot if you're not going to use it.

Added.

> > === modified file 'lib/lp/code/browser/branch.py'
> > --- lib/lp/code/browser/branch.py 2009-07-19 04:41:14 +0000
> > +++ lib/lp/code/browser/branch.py 2009-08-03 03:36:16 +0000
> > @@ -36,12 +36,15 @@
> > from zope.app.form.browser import TextAreaWidget
> > from zope.traversing.interfaces import IPathAdapter
> > from zope.component import getUtility, queryAdapter
> > +from zope.event import notify
> > from zope.formlib import form
> > -from zope.interface import Interface, implements
> > +from zope.interface import Interface, implements, providedBy
> > from zope.publisher.interfaces import NotFound
> > from zope.schema import Choice, Text
> > from lazr.delegates import delegates
> > from lazr.enum import EnumeratedType, Item
> > +from lazr.lifecycle.event import ObjectModifiedEvent
> > +from lazr.lifecycle.snapshot import Snapshot
> > from lazr.uri import URI
> >
> > from canonical.cachedpr...

=== modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
--- lib/canonical/launchpad/webapp/launchpadform.py 2009-07-31 02:32:22 +0000
+++ lib/canonical/launchpad/webapp/launchpadform.py 2009-08-04 00:25:53 +0000
@@ -386,8 +386,9 @@
386 """386 """
387 if context is None:387 if context is None:
388 context = self.context388 context = self.context
389 context_before_modification = Snapshot(389 if notify_modified:
390 context, providing=providedBy(context))390 context_before_modification = Snapshot(
391 context, providing=providedBy(context))
391392
392 was_changed = form.applyChanges(context, self.form_fields,393 was_changed = form.applyChanges(context, self.form_fields,
393 data, self.adapters)394 data, self.adapters)
394395
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2009-07-31 02:32:22 +0000
+++ lib/lp/code/browser/branch.py 2009-08-04 00:27:41 +0000
@@ -601,6 +601,10 @@
601 @action('Change Branch', name='change')601 @action('Change Branch', name='change')
602 def change_action(self, action, data):602 def change_action(self, action, data):
603 # If the owner or product has changed, add an explicit notification.603 # If the owner or product has changed, add an explicit notification.
604 # We take our own snapshot here to make sure that the snapshot records
605 # changes to the owner and private, and we notify the listeners
606 # explicitly below rather than the notification that would normally be
607 # sent in updateContextFromData.
604 branch_before_modification = Snapshot(608 branch_before_modification = Snapshot(
605 self.context, providing=providedBy(self.context))609 self.context, providing=providedBy(self.context))
606 if 'owner' in data:610 if 'owner' in data:
607611
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2009-07-31 01:07:04 +0000
+++ lib/lp/code/interfaces/branch.py 2009-08-05 01:49:53 +0000
@@ -32,7 +32,6 @@
32 'IBranchNavigationMenu',32 'IBranchNavigationMenu',
33 'IBranchSet',33 'IBranchSet',
34 'NoSuchBranch',34 'NoSuchBranch',
35 'UnknownBranchTargetError',
36 'UnknownBranchTypeError',35 'UnknownBranchTypeError',
37 'user_has_special_branch_access',36 'user_has_special_branch_access',
38 ]37 ]
@@ -122,10 +121,6 @@
122 """Raised when the user specifies an unrecognized branch type."""121 """Raised when the user specifies an unrecognized branch type."""
123122
124123
125class UnknownBranchTargetError(Exception):
126 """Raised when an unexpected type is passed through as a branch target."""
127
128
129class BranchCreationForbidden(BranchCreationException):124class BranchCreationForbidden(BranchCreationException):
130 """A Branch visibility policy forbids branch creation.125 """A Branch visibility policy forbids branch creation.
131126
@@ -445,6 +440,10 @@
445 Only one of `project` or `source_package` can be set, and if neither440 Only one of `project` or `source_package` can be set, and if neither
446 is set, the branch gets moved into the junk namespace of the branch441 is set, the branch gets moved into the junk namespace of the branch
447 owner.442 owner.
443
444 :raises BranchTargetError: if both project and source_package are set,
445 or if either the project or source_package fail to be adapted to an
446 `IBranchTarget`.
448 """447 """
449448
450 reviewer = exported(449 reviewer = exported(
451450
=== modified file 'lib/lp/code/interfaces/branchtarget.py'
--- lib/lp/code/interfaces/branchtarget.py 2009-07-28 05:20:23 +0000
+++ lib/lp/code/interfaces/branchtarget.py 2009-08-04 00:13:03 +0000
@@ -113,12 +113,3 @@
113113
114 def getBugTask(bug):114 def getBugTask(bug):
115 """Get the BugTask for a given bug related to the branch target."""115 """Get the BugTask for a given bug related to the branch target."""
116
117 def retargetBranch(branch):
118 """Set the branch target to refer to this target.
119
120 This only updates the target related attributes of the branch.
121
122 No name clashes are checked, nor privacy policies. Those are handled
123 by the IBranchNamespace.moveBranch method.
124 """
125116
=== modified file 'lib/lp/code/model/branchnamespace.py'
--- lib/lp/code/model/branchnamespace.py 2009-07-28 05:20:23 +0000
+++ lib/lp/code/model/branchnamespace.py 2009-08-04 00:14:50 +0000
@@ -177,7 +177,7 @@
177 # attributes are readonly through the interface.177 # attributes are readonly through the interface.
178 naked_branch = removeSecurityProxy(branch)178 naked_branch = removeSecurityProxy(branch)
179 naked_branch.owner = self.owner179 naked_branch.owner = self.owner
180 self.target.retargetBranch(naked_branch)180 self.target._retargetBranch(naked_branch)
181 naked_branch.name = new_name181 naked_branch.name = new_name
182182
183 def createBranchWithPrefix(self, branch_type, prefix, registrant,183 def createBranchWithPrefix(self, branch_type, prefix, registrant,
184184
=== modified file 'lib/lp/code/model/branchtarget.py'
--- lib/lp/code/model/branchtarget.py 2009-07-28 05:20:23 +0000
+++ lib/lp/code/model/branchtarget.py 2009-08-04 00:14:25 +0000
@@ -129,15 +129,15 @@
129 # those cases.129 # those cases.
130 return bug.default_bugtask130 return bug.default_bugtask
131131
132 def retargetBranch(self, branch):132 def _retargetBranch(self, branch):
133 """See `IBranchTarget`."""133 """Set the branch target to refer to this target.
134 # Since product, distroseries and sourcepackagename are not writable134
135 # as defined by the interface, we need to rip off the security proxy135 This only updates the target related attributes of the branch, and
136 # here.136 expects a branch without a security proxy as a parameter.
137 naked_branch = removeSecurityProxy(branch)137 """
138 naked_branch.product = None138 branch.product = None
139 naked_branch.distroseries = self.sourcepackage.distroseries139 branch.distroseries = self.sourcepackage.distroseries
140 naked_branch.sourcepackagename = self.sourcepackage.sourcepackagename140 branch.sourcepackagename = self.sourcepackage.sourcepackagename
141141
142142
143class PersonBranchTarget(_BaseBranchTarget):143class PersonBranchTarget(_BaseBranchTarget):
@@ -193,15 +193,15 @@
193 """See `IBranchTarget`."""193 """See `IBranchTarget`."""
194 return bug.default_bugtask194 return bug.default_bugtask
195195
196 def retargetBranch(self, branch):196 def _retargetBranch(self, branch):
197 """See `IBranchTarget`."""197 """Set the branch target to refer to this target.
198 # Since product, distroseries and sourcepackagename are not writable198
199 # as defined by the interface, we need to rip off the security proxy199 This only updates the target related attributes of the branch, and
200 # here.200 expects a branch without a security proxy as a parameter.
201 naked_branch = removeSecurityProxy(branch)201 """
202 naked_branch.product = None202 branch.product = None
203 naked_branch.distroseries = None203 branch.distroseries = None
204 naked_branch.sourcepackagename = None204 branch.sourcepackagename = None
205205
206206
207class ProductBranchTarget(_BaseBranchTarget):207class ProductBranchTarget(_BaseBranchTarget):
@@ -286,15 +286,15 @@
286 task = bug.bugtasks[0]286 task = bug.bugtasks[0]
287 return task287 return task
288288
289 def retargetBranch(self, branch):289 def _retargetBranch(self, branch):
290 """See `IBranchTarget`."""290 """Set the branch target to refer to this target.
291 # Since product, distroseries and sourcepackagename are not writable291
292 # as defined by the interface, we need to rip off the security proxy292 This only updates the target related attributes of the branch, and
293 # here.293 expects a branch without a security proxy as a parameter.
294 naked_branch = removeSecurityProxy(branch)294 """
295 naked_branch.product = self.product295 branch.product = self.product
296 naked_branch.distroseries = None296 branch.distroseries = None
297 naked_branch.sourcepackagename = None297 branch.sourcepackagename = None
298298
299299
300def get_canonical_url_data_for_target(branch_target):300def get_canonical_url_data_for_target(branch_target):
301301
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2009-08-02 23:17:10 +0000
+++ lib/lp/code/model/tests/test_branch.py 2009-08-05 02:03:41 +0000
@@ -40,7 +40,7 @@
40from lp.code.interfaces.branch import (40from lp.code.interfaces.branch import (
41 BranchCannotBePrivate, BranchCannotBePublic,41 BranchCannotBePrivate, BranchCannotBePublic,
42 BranchCreatorNotMemberOfOwnerTeam, BranchCreatorNotOwner,42 BranchCreatorNotMemberOfOwnerTeam, BranchCreatorNotOwner,
43 CannotDeleteBranch, DEFAULT_BRANCH_STATUS_IN_LISTING)43 BranchTargetError, CannotDeleteBranch, DEFAULT_BRANCH_STATUS_IN_LISTING)
44from lp.code.interfaces.branchlookup import IBranchLookup44from lp.code.interfaces.branchlookup import IBranchLookup
45from lp.code.interfaces.branchnamespace import IBranchNamespaceSet45from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
46from lp.code.interfaces.branchmergeproposal import InvalidBranchMergeProposal46from lp.code.interfaces.branchmergeproposal import InvalidBranchMergeProposal
@@ -58,7 +58,6 @@
58from lp.code.model.codeimport import CodeImport, CodeImportSet58from lp.code.model.codeimport import CodeImport, CodeImportSet
59from lp.code.model.codereviewcomment import CodeReviewComment59from lp.code.model.codereviewcomment import CodeReviewComment
60from lp.registry.interfaces.person import IPersonSet60from lp.registry.interfaces.person import IPersonSet
61from lp.registry.interfaces.product import IProductSet
62from lp.registry.model.product import ProductSet61from lp.registry.model.product import ProductSet
63from lp.registry.model.sourcepackage import SourcePackage62from lp.registry.model.sourcepackage import SourcePackage
64from lp.soyuz.interfaces.publishing import PackagePublishingPocket63from lp.soyuz.interfaces.publishing import PackagePublishingPocket
@@ -1722,6 +1721,17 @@
17221721
1723 layer = DatabaseFunctionalLayer1722 layer = DatabaseFunctionalLayer
17241723
1724 def test_not_both_project_and_source_package(self):
1725 # Only one of project or source_package can be passed in, not both.
1726 branch = self.factory.makePersonalBranch()
1727 project = self.factory.makeProduct()
1728 source_package = self.factory.makeSourcePackage()
1729 login_person(branch.owner)
1730 self.assertRaises(
1731 BranchTargetError,
1732 branch.setTarget,
1733 user=branch.owner, project=project, source_package=source_package)
1734
1725 def test_junk_branch_to_project_branch(self):1735 def test_junk_branch_to_project_branch(self):
1726 # A junk branch can be moved to a project.1736 # A junk branch can be moved to a project.
1727 branch = self.factory.makePersonalBranch()1737 branch = self.factory.makePersonalBranch()
17281738
=== modified file 'lib/lp/code/model/tests/test_branchtarget.py'
--- lib/lp/code/model/tests/test_branchtarget.py 2009-07-28 05:20:23 +0000
+++ lib/lp/code/model/tests/test_branchtarget.py 2009-08-04 00:23:05 +0000
@@ -49,19 +49,19 @@
49 def test_retargetBranch_packageBranch(self):49 def test_retargetBranch_packageBranch(self):
50 # Retarget an existing package branch to this target.50 # Retarget an existing package branch to this target.
51 branch = self.factory.makePackageBranch()51 branch = self.factory.makePackageBranch()
52 self.target.retargetBranch(branch)52 self.target._retargetBranch(removeSecurityProxy(branch))
53 self.assertEqual(self.target, branch.target)53 self.assertEqual(self.target, branch.target)
5454
55 def test_retargetBranch_productBranch(self):55 def test_retargetBranch_productBranch(self):
56 # Retarget an existing product branch to this target.56 # Retarget an existing product branch to this target.
57 branch = self.factory.makeProductBranch()57 branch = self.factory.makeProductBranch()
58 self.target.retargetBranch(branch)58 self.target._retargetBranch(removeSecurityProxy(branch))
59 self.assertEqual(self.target, branch.target)59 self.assertEqual(self.target, branch.target)
6060
61 def test_retargetBranch_personalBranch(self):61 def test_retargetBranch_personalBranch(self):
62 # Retarget an existing personal branch to this target.62 # Retarget an existing personal branch to this target.
63 branch = self.factory.makePersonalBranch()63 branch = self.factory.makePersonalBranch()
64 self.target.retargetBranch(branch)64 self.target._retargetBranch(removeSecurityProxy(branch))
65 self.assertEqual(self.target, branch.target)65 self.assertEqual(self.target, branch.target)
6666
6767
@@ -236,7 +236,7 @@
236 # match the target as the target is the branch owner for a personal236 # match the target as the target is the branch owner for a personal
237 # branch.237 # branch.
238 branch = self.factory.makePackageBranch(owner=self.original)238 branch = self.factory.makePackageBranch(owner=self.original)
239 self.target.retargetBranch(branch)239 self.target._retargetBranch(removeSecurityProxy(branch))
240 self.assertEqual(self.target, branch.target)240 self.assertEqual(self.target, branch.target)
241241
242 def test_retargetBranch_productBranch(self):242 def test_retargetBranch_productBranch(self):
@@ -245,7 +245,7 @@
245 # match the target as the target is the branch owner for a personal245 # match the target as the target is the branch owner for a personal
246 # branch.246 # branch.
247 branch = self.factory.makeProductBranch(owner=self.original)247 branch = self.factory.makeProductBranch(owner=self.original)
248 self.target.retargetBranch(branch)248 self.target._retargetBranch(removeSecurityProxy(branch))
249 self.assertEqual(self.target, branch.target)249 self.assertEqual(self.target, branch.target)
250250
251 def test_retargetBranch_personalBranch(self):251 def test_retargetBranch_personalBranch(self):
@@ -254,7 +254,7 @@
254 # match the target as the target is the branch owner for a personal254 # match the target as the target is the branch owner for a personal
255 # branch.255 # branch.
256 branch = self.factory.makePersonalBranch(owner=self.original)256 branch = self.factory.makePersonalBranch(owner=self.original)
257 self.target.retargetBranch(branch)257 self.target._retargetBranch(removeSecurityProxy(branch))
258 self.assertEqual(self.target, branch.target)258 self.assertEqual(self.target, branch.target)
259259
260260
261261
=== modified file 'lib/lp/code/scripts/tests/test_revisionkarma.py'
--- lib/lp/code/scripts/tests/test_revisionkarma.py 2009-08-02 23:17:10 +0000
+++ lib/lp/code/scripts/tests/test_revisionkarma.py 2009-08-04 00:36:50 +0000
@@ -49,7 +49,7 @@
49 branch.createBranchRevision(1, rev)49 branch.createBranchRevision(1, rev)
50 # Once the branch is connected to the revision, we now specify50 # Once the branch is connected to the revision, we now specify
51 # a product for the branch.51 # a product for the branch.
52 project=self.factory.makeProduct()52 project = self.factory.makeProduct()
53 branch.setTarget(user=branch.owner, project=project)53 branch.setTarget(user=branch.owner, project=project)
54 # Commit and switch to the script db user.54 # Commit and switch to the script db user.
55 transaction.commit()55 transaction.commit()
5656
=== modified file 'lib/lp/codehosting/tests/test_acceptance.py'
--- lib/lp/codehosting/tests/test_acceptance.py 2009-08-03 01:41:07 +0000
+++ lib/lp/codehosting/tests/test_acceptance.py 2009-08-04 00:40:11 +0000
@@ -14,7 +14,6 @@
14from bzrlib.tests import TestCaseWithTransport14from bzrlib.tests import TestCaseWithTransport
15from bzrlib.urlutils import local_path_from_url15from bzrlib.urlutils import local_path_from_url
16from bzrlib.workingtree import WorkingTree16from bzrlib.workingtree import WorkingTree
17from zope.security.proxy import removeSecurityProxy
1817
19from lp.codehosting.bzrutils import DenyingServer18from lp.codehosting.bzrutils import DenyingServer
20from lp.codehosting.tests.helpers import (19from lp.codehosting.tests.helpers import (
@@ -383,7 +382,7 @@
383 # rename as far as bzr is concerned: the URL changes.382 # rename as far as bzr is concerned: the URL changes.
384 LaunchpadZopelessTestSetup().txn.begin()383 LaunchpadZopelessTestSetup().txn.begin()
385 branch = self.getDatabaseBranch('testuser', None, 'test-branch')384 branch = self.getDatabaseBranch('testuser', None, 'test-branch')
386 removeSecurityProxy(branch).product = Product.byName('firefox')385 branch.setTarget(user=branch.owner, project=Product.byName('firefox'))
387 LaunchpadZopelessTestSetup().txn.commit()386 LaunchpadZopelessTestSetup().txn.commit()
388387
389 self.assertNotBranch(388 self.assertNotBranch(

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-07-17 00:26:05 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2009-07-31 01:07:04 +0000
@@ -75,6 +75,11 @@
75IBranch['linkSpecification'].queryTaggedValue(75IBranch['linkSpecification'].queryTaggedValue(
76 LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema= ISpecification76 LAZR_WEBSERVICE_EXPORTED)['params']['spec'].schema= ISpecification
77IBranch['product'].schema = IProduct77IBranch['product'].schema = IProduct
78IBranch['setTarget'].queryTaggedValue(
79 LAZR_WEBSERVICE_EXPORTED)['params']['project'].schema= IProduct
80IBranch['setTarget'].queryTaggedValue(
81 LAZR_WEBSERVICE_EXPORTED)['params']['source_package'].schema= \
82 ISourcePackage
78IBranch['spec_links'].value_type.schema = ISpecificationBranch83IBranch['spec_links'].value_type.schema = ISpecificationBranch
79IBranch['subscribe'].queryTaggedValue(84IBranch['subscribe'].queryTaggedValue(
80 LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = IBranchSubscription85 LAZR_WEBSERVICE_EXPORTED)['return_type'].schema = IBranchSubscription
8186
=== modified file 'lib/canonical/launchpad/webapp/launchpadform.py'
--- lib/canonical/launchpad/webapp/launchpadform.py 2009-06-25 05:30:52 +0000
+++ lib/canonical/launchpad/webapp/launchpadform.py 2009-07-31 02:32:22 +0000
@@ -372,7 +372,7 @@
372372
373 render_context = True373 render_context = True
374374
375 def updateContextFromData(self, data, context=None):375 def updateContextFromData(self, data, context=None, notify_modified=True):
376 """Update the context object based on form data.376 """Update the context object based on form data.
377377
378 If no context is given, the view's context is used.378 If no context is given, the view's context is used.
@@ -391,7 +391,7 @@
391391
392 was_changed = form.applyChanges(context, self.form_fields,392 was_changed = form.applyChanges(context, self.form_fields,
393 data, self.adapters)393 data, self.adapters)
394 if was_changed:394 if was_changed and notify_modified:
395 field_names = [form_field.__name__395 field_names = [form_field.__name__
396 for form_field in self.form_fields]396 for form_field in self.form_fields]
397 notify(ObjectModifiedEvent(397 notify(ObjectModifiedEvent(
398398
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2009-07-19 04:41:14 +0000
+++ lib/lp/code/browser/branch.py 2009-07-31 02:32:22 +0000
@@ -36,12 +36,15 @@
36from zope.app.form.browser import TextAreaWidget36from zope.app.form.browser import TextAreaWidget
37from zope.traversing.interfaces import IPathAdapter37from zope.traversing.interfaces import IPathAdapter
38from zope.component import getUtility, queryAdapter38from zope.component import getUtility, queryAdapter
39from zope.event import notify
39from zope.formlib import form40from zope.formlib import form
40from zope.interface import Interface, implements41from zope.interface import Interface, implements, providedBy
41from zope.publisher.interfaces import NotFound42from zope.publisher.interfaces import NotFound
42from zope.schema import Choice, Text43from zope.schema import Choice, Text
43from lazr.delegates import delegates44from lazr.delegates import delegates
44from lazr.enum import EnumeratedType, Item45from lazr.enum import EnumeratedType, Item
46from lazr.lifecycle.event import ObjectModifiedEvent
47from lazr.lifecycle.snapshot import Snapshot
45from lazr.uri import URI48from lazr.uri import URI
4649
47from canonical.cachedproperty import cachedproperty50from canonical.cachedproperty import cachedproperty
@@ -574,7 +577,6 @@
574 the user to be able to edit it.577 the user to be able to edit it.
575 """578 """
576 use_template(IBranch, include=[579 use_template(IBranch, include=[
577 'owner',
578 'name',580 'name',
579 'url',581 'url',
580 'description',582 'description',
@@ -582,6 +584,7 @@
582 'whiteboard',584 'whiteboard',
583 ])585 ])
584 private = copy_field(IBranch['private'], readonly=False)586 private = copy_field(IBranch['private'], readonly=False)
587 owner = copy_field(IBranch['owner'], readonly=False)
585588
586589
587class BranchEditFormView(LaunchpadEditFormView):590class BranchEditFormView(LaunchpadEditFormView):
@@ -598,9 +601,12 @@
598 @action('Change Branch', name='change')601 @action('Change Branch', name='change')
599 def change_action(self, action, data):602 def change_action(self, action, data):
600 # If the owner or product has changed, add an explicit notification.603 # If the owner or product has changed, add an explicit notification.
604 branch_before_modification = Snapshot(
605 self.context, providing=providedBy(self.context))
601 if 'owner' in data:606 if 'owner' in data:
602 new_owner = data['owner']607 new_owner = data.pop('owner')
603 if new_owner != self.context.owner:608 if new_owner != self.context.owner:
609 self.context.setOwner(new_owner, self.user)
604 self.request.response.addNotification(610 self.request.response.addNotification(
605 "The branch owner has been changed to %s (%s)"611 "The branch owner has been changed to %s (%s)"
606 % (new_owner.displayname, new_owner.name))612 % (new_owner.displayname, new_owner.name))
@@ -616,7 +622,13 @@
616 else:622 else:
617 self.request.response.addNotification(623 self.request.response.addNotification(
618 "The branch is now publicly accessible.")624 "The branch is now publicly accessible.")
619 if self.updateContextFromData(data):625 if self.updateContextFromData(data, notify_modified=False):
626 # Notify the object has changed with the snapshot that was taken
627 # earler.
628 field_names = [
629 form_field.__name__ for form_field in self.form_fields]
630 notify(ObjectModifiedEvent(
631 self.context, branch_before_modification, field_names))
620 # Only specify that the context was modified if there632 # Only specify that the context was modified if there
621 # was in fact a change.633 # was in fact a change.
622 self.context.date_last_modified = UTC_NOW634 self.context.date_last_modified = UTC_NOW
623635
=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposallisting.py'
--- lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2009-07-17 00:26:05 +0000
+++ lib/lp/code/browser/tests/test_branchmergeproposallisting.py 2009-08-02 23:25:37 +0000
@@ -8,14 +8,15 @@
8from unittest import TestLoader8from unittest import TestLoader
99
10import transaction10import transaction
11from zope.security.proxy import removeSecurityProxy
1112
13from canonical.launchpad.webapp.servers import LaunchpadTestRequest
14from canonical.testing import DatabaseFunctionalLayer
12from lp.code.browser.branchmergeproposallisting import (15from lp.code.browser.branchmergeproposallisting import (
13 ActiveReviewsView, BranchMergeProposalListingView, PersonActiveReviewsView,16 ActiveReviewsView, BranchMergeProposalListingView, PersonActiveReviewsView,
14 ProductActiveReviewsView)17 ProductActiveReviewsView)
15from lp.code.enums import CodeReviewVote18from lp.code.enums import CodeReviewVote
16from lp.testing import ANONYMOUS, login, login_person, TestCaseWithFactory19from lp.testing import ANONYMOUS, login, login_person, TestCaseWithFactory
17from canonical.launchpad.webapp.servers import LaunchpadTestRequest
18from canonical.testing import DatabaseFunctionalLayer
1920
20_default = object()21_default = object()
2122
@@ -201,8 +202,7 @@
201 self.assertReviewGroupForUser(202 self.assertReviewGroupForUser(
202 self.bmp.registrant, self._view.OTHER)203 self.bmp.registrant, self._view.OTHER)
203 team = self.factory.makeTeam(self.bmp.registrant)204 team = self.factory.makeTeam(self.bmp.registrant)
204 login_person(self.bmp.source_branch.owner)205 removeSecurityProxy(self.bmp.source_branch).owner = team
205 self.bmp.source_branch.owner = team
206 self.assertReviewGroupForUser(206 self.assertReviewGroupForUser(
207 self.bmp.registrant, ActiveReviewsView.MINE)207 self.bmp.registrant, ActiveReviewsView.MINE)
208208
209209
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2009-07-23 02:06:55 +0000
+++ lib/lp/code/configure.zcml 2009-07-30 00:30:02 +0000
@@ -430,9 +430,9 @@
430 "/>430 "/>
431 <require431 <require
432 permission="launchpad.Edit"432 permission="launchpad.Edit"
433 attributes="destroySelf setPrivate"433 attributes="destroySelf setPrivate setOwner setTarget"
434 set_attributes="name url mirror_status_message434 set_attributes="name url mirror_status_message
435 owner author description product lifecycle_status435 description lifecycle_status
436 last_mirrored last_mirrored_id last_mirror_attempt436 last_mirrored last_mirrored_id last_mirror_attempt
437 mirror_failures pull_disabled next_mirror_time437 mirror_failures pull_disabled next_mirror_time
438 last_scanned last_scanned_id revision_count branch_type438 last_scanned last_scanned_id revision_count branch_type
439439
=== modified file 'lib/lp/code/doc/branch.txt'
--- lib/lp/code/doc/branch.txt 2009-06-16 03:31:05 +0000
+++ lib/lp/code/doc/branch.txt 2009-08-02 23:59:31 +0000
@@ -116,9 +116,9 @@
116 >>> print new_branch.owner.name116 >>> print new_branch.owner.name
117 registrant117 registrant
118118
119A user can create a branch where the owner is either themselves,119A user can create a branch where the owner is either themselves, or a team
120or a team that they are a member of. The registrant is not writable,120that they are a member of. Neither the owner nor the registrant are writable,
121whereas the owner is.121but the owner can be set using the `setOwner` method.
122122
123 >>> login('admin@canonical.com')123 >>> login('admin@canonical.com')
124 >>> new_branch.registrant = factory.makePerson()124 >>> new_branch.registrant = factory.makePerson()
@@ -126,7 +126,8 @@
126 ...126 ...
127 ForbiddenAttribute: ('registrant', <Branch ...>)127 ForbiddenAttribute: ('registrant', <Branch ...>)
128128
129 >>> new_branch.owner = factory.makePerson(name='new-owner')129 >>> team = factory.makeTeam(name='new-owner', owner=new_branch.owner)
130 >>> new_branch.setOwner(new_owner=team, user=new_branch.owner)
130 >>> print new_branch.registrant.name131 >>> print new_branch.registrant.name
131 registrant132 registrant
132 >>> print new_branch.owner.name133 >>> print new_branch.owner.name
133134
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2009-07-29 03:44:05 +0000
+++ lib/lp/code/interfaces/branch.py 2009-07-31 01:07:04 +0000
@@ -19,6 +19,7 @@
19 'BranchCreatorNotMemberOfOwnerTeam',19 'BranchCreatorNotMemberOfOwnerTeam',
20 'BranchCreatorNotOwner',20 'BranchCreatorNotOwner',
21 'BranchExists',21 'BranchExists',
22 'BranchTargetError',
22 'BranchTypeError',23 'BranchTypeError',
23 'CannotDeleteBranch',24 'CannotDeleteBranch',
24 'DEFAULT_BRANCH_STATUS_IN_LISTING',25 'DEFAULT_BRANCH_STATUS_IN_LISTING',
@@ -31,6 +32,7 @@
31 'IBranchNavigationMenu',32 'IBranchNavigationMenu',
32 'IBranchSet',33 'IBranchSet',
33 'NoSuchBranch',34 'NoSuchBranch',
35 'UnknownBranchTargetError',
34 'UnknownBranchTypeError',36 'UnknownBranchTypeError',
35 'user_has_special_branch_access',37 'user_has_special_branch_access',
36 ]38 ]
@@ -90,7 +92,7 @@
90 """Raised when creating a branch that already exists."""92 """Raised when creating a branch that already exists."""
9193
92 def __init__(self, existing_branch):94 def __init__(self, existing_branch):
93 # XXX: JonathanLange 2008-12-04 spec=package-branches: This error95 # XXX: TimPenhey 2009-07-12 bug=405214: This error
94 # message logic is incorrect, but the exact text is being tested96 # message logic is incorrect, but the exact text is being tested
95 # in branch-xmlrpc.txt.97 # in branch-xmlrpc.txt.
96 params = {'name': existing_branch.name}98 params = {'name': existing_branch.name}
@@ -108,6 +110,10 @@
108 BranchCreationException.__init__(self, message)110 BranchCreationException.__init__(self, message)
109111
110112
113class BranchTargetError(Exception):
114 """Raised when there is an error determining a branch target."""
115
116
111class CannotDeleteBranch(Exception):117class CannotDeleteBranch(Exception):
112 """The branch cannot be deleted at this time."""118 """The branch cannot be deleted at this time."""
113119
@@ -116,6 +122,10 @@
116 """Raised when the user specifies an unrecognized branch type."""122 """Raised when the user specifies an unrecognized branch type."""
117123
118124
125class UnknownBranchTargetError(Exception):
126 """Raised when an unexpected type is passed through as a branch target."""
127
128
119class BranchCreationForbidden(BranchCreationException):129class BranchCreationForbidden(BranchCreationException):
120 """A Branch visibility policy forbids branch creation.130 """A Branch visibility policy forbids branch creation.
121131
@@ -402,14 +412,41 @@
402 title=_("The user that registered the branch."),412 title=_("The user that registered the branch."),
403 required=True, readonly=True,413 required=True, readonly=True,
404 vocabulary='ValidPersonOrTeam'))414 vocabulary='ValidPersonOrTeam'))
415
405 owner = exported(416 owner = exported(
406 ParticipatingPersonChoice(417 ParticipatingPersonChoice(
407 title=_('Owner'),418 title=_('Owner'),
408 required=True,419 required=True, readonly=True,
409 vocabulary='UserTeamsParticipationPlusSelf',420 vocabulary='UserTeamsParticipationPlusSelf',
410 description=_("Either yourself or a team you are a member of. "421 description=_("Either yourself or a team you are a member of. "
411 "This controls who can modify the branch.")))422 "This controls who can modify the branch.")))
412423
424 @call_with(user=REQUEST_USER)
425 @operation_parameters(
426 new_owner=Reference(
427 title=_("The new owner of the branch."),
428 schema=IPerson))
429 @export_write_operation()
430 def setOwner(new_owner, user):
431 """Set the owner of the branch to be `new_owner`."""
432
433 @call_with(user=REQUEST_USER)
434 @operation_parameters(
435 project=Reference(
436 title=_("The project the branch belongs to."),
437 schema=Interface, required=False), # Really IProduct
438 source_package=Reference(
439 title=_("The source package the branch belongs to."),
440 schema=Interface, required=False)) # Really ISourcePackage
441 @export_write_operation()
442 def setTarget(user, project=None, source_package=None):
443 """Set the target of the branch to be `project` or `source_package`.
444
445 Only one of `project` or `source_package` can be set, and if neither
446 is set, the branch gets moved into the junk namespace of the branch
447 owner.
448 """
449
413 reviewer = exported(450 reviewer = exported(
414 PublicPersonChoice(451 PublicPersonChoice(
415 title=_('Default Review Team'),452 title=_('Default Review Team'),
@@ -456,7 +493,7 @@
456 product = exported(493 product = exported(
457 ReferenceChoice(494 ReferenceChoice(
458 title=_('Project'),495 title=_('Project'),
459 required=False,496 required=False, readonly=True,
460 vocabulary='Product',497 vocabulary='Product',
461 schema=Interface,498 schema=Interface,
462 description=_("The project this branch belongs to.")),499 description=_("The project this branch belongs to.")),
463500
=== modified file 'lib/lp/code/interfaces/branchnamespace.py'
--- lib/lp/code/interfaces/branchnamespace.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/interfaces/branchnamespace.py 2009-07-27 10:19:21 +0000
@@ -70,6 +70,24 @@
70 def isNameUsed(name):70 def isNameUsed(name):
71 """Is 'name' already used in this namespace?"""71 """Is 'name' already used in this namespace?"""
7272
73 def moveBranch(branch, mover, new_name=None, rename_if_necessary=False):
74 """Move the branch into this namespace.
75
76 :param branch: The `IBranch` to move.
77 :param mover: The `IPerson` doing the moving.
78 :param new_name: A new name for the branch.
79 :param rename_if_necessary: Rename the branch if the branch name
80 exists already in this namespace.
81 :raises BranchCreatorNotMemberOfOwnerTeam: if the namespace owner is
82 a team, and 'mover' is not in that team.
83 :raises BranchCreatorNotOwner: if the namespace owner is an individual
84 and 'mover' is not the owner.
85 :raises BranchCreationForbidden: if 'mover' is not allowed to create
86 a branch in this namespace due to privacy rules.
87 :raises BranchExists: if a branch with the 'name' exists already in
88 the namespace, and 'rename_if_necessary' is False.
89 """
90
7391
74class IBranchNamespacePolicy(Interface):92class IBranchNamespacePolicy(Interface):
75 """Methods relating to branch creation and validation."""93 """Methods relating to branch creation and validation."""
@@ -133,11 +151,13 @@
133 validation constraints on IBranch.name.151 validation constraints on IBranch.name.
134 """152 """
135153
136 def validateMove(branch, mover):154 def validateMove(branch, mover, name=None):
137 """Check that 'mover' can move 'branch' into this namespace.155 """Check that 'mover' can move 'branch' into this namespace.
138156
139 :param branch: An `IBranch` that might be moved.157 :param branch: An `IBranch` that might be moved.
140 :param mover: The `IPerson` who would move it.158 :param mover: The `IPerson` who would move it.
159 :param name: A new name for the branch. If None, the branch name is
160 used.
141 :raises BranchCreatorNotMemberOfOwnerTeam: if the namespace owner is161 :raises BranchCreatorNotMemberOfOwnerTeam: if the namespace owner is
142 a team, and 'mover' is not in that team.162 a team, and 'mover' is not in that team.
143 :raises BranchCreatorNotOwner: if the namespace owner is an individual163 :raises BranchCreatorNotOwner: if the namespace owner is an individual
144164
=== modified file 'lib/lp/code/interfaces/branchtarget.py'
--- lib/lp/code/interfaces/branchtarget.py 2009-07-17 00:26:05 +0000
+++ lib/lp/code/interfaces/branchtarget.py 2009-07-28 05:20:23 +0000
@@ -113,3 +113,12 @@
113113
114 def getBugTask(bug):114 def getBugTask(bug):
115 """Get the BugTask for a given bug related to the branch target."""115 """Get the BugTask for a given bug related to the branch target."""
116
117 def retargetBranch(branch):
118 """Set the branch target to refer to this target.
119
120 This only updates the target related attributes of the branch.
121
122 No name clashes are checked, nor privacy policies. Those are handled
123 by the IBranchNamespace.moveBranch method.
124 """
116125
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2009-07-19 04:41:14 +0000
+++ lib/lp/code/model/branch.py 2009-07-31 01:07:04 +0000
@@ -57,7 +57,7 @@
57from lp.code.event.branchmergeproposal import NewBranchMergeProposalEvent57from lp.code.event.branchmergeproposal import NewBranchMergeProposalEvent
58from lp.code.interfaces.branch import (58from lp.code.interfaces.branch import (
59 bazaar_identity, BranchCannotBePrivate, BranchCannotBePublic,59 bazaar_identity, BranchCannotBePrivate, BranchCannotBePublic,
60 BranchTypeError, CannotDeleteBranch,60 BranchTargetError, BranchTypeError, CannotDeleteBranch,
61 DEFAULT_BRANCH_STATUS_IN_LISTING, IBranch,61 DEFAULT_BRANCH_STATUS_IN_LISTING, IBranch,
62 IBranchNavigationMenu, IBranchSet)62 IBranchNavigationMenu, IBranchSet)
63from lp.code.interfaces.branchcollection import IAllBranches63from lp.code.interfaces.branchcollection import IAllBranches
@@ -71,7 +71,7 @@
71from lp.code.interfaces.seriessourcepackagebranch import (71from lp.code.interfaces.seriessourcepackagebranch import (
72 IFindOfficialBranchLinks)72 IFindOfficialBranchLinks)
73from lp.registry.interfaces.person import (73from lp.registry.interfaces.person import (
74 validate_person_not_private_membership, validate_public_person)74 IPerson, validate_person_not_private_membership, validate_public_person)
7575
7676
77class Branch(SQLBase):77class Branch(SQLBase):
@@ -113,6 +113,12 @@
113 owner = ForeignKey(113 owner = ForeignKey(
114 dbName='owner', foreignKey='Person',114 dbName='owner', foreignKey='Person',
115 storm_validator=validate_person_not_private_membership, notNull=True)115 storm_validator=validate_person_not_private_membership, notNull=True)
116
117 def setOwner(self, new_owner, user):
118 """See `IBranch`."""
119 new_namespace = self.target.getNamespace(new_owner)
120 new_namespace.moveBranch(self, user, rename_if_necessary=True)
121
116 reviewer = ForeignKey(122 reviewer = ForeignKey(
117 dbName='reviewer', foreignKey='Person',123 dbName='reviewer', foreignKey='Person',
118 storm_validator=validate_public_person, default=None)124 storm_validator=validate_public_person, default=None)
@@ -162,6 +168,28 @@
162 target = self.product168 target = self.product
163 return IBranchTarget(target)169 return IBranchTarget(target)
164170
171 def setTarget(self, user, project=None, source_package=None):
172 """See `IBranch`."""
173 if project is not None:
174 if source_package is not None:
175 raise BranchTargetError(
176 'Cannot specify both a project and a source package.')
177 else:
178 target = IBranchTarget(project)
179 if target is None:
180 raise BranchTargetError(
181 '%r is not a valid project target' % project)
182 elif source_package is not None:
183 target = IBranchTarget(source_package)
184 if target is None:
185 raise BranchTargetError(
186 '%r is not a valid source package target' % source_package)
187 else:
188 target = IBranchTarget(self.owner)
189 # Person targets are always valid.
190 namespace = target.getNamespace(self.owner)
191 namespace.moveBranch(self, user, rename_if_necessary=True)
192
165 @property193 @property
166 def namespace(self):194 def namespace(self):
167 """See `IBranch`."""195 """See `IBranch`."""
168196
=== modified file 'lib/lp/code/model/branchnamespace.py'
--- lib/lp/code/model/branchnamespace.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/model/branchnamespace.py 2009-07-28 05:20:23 +0000
@@ -16,6 +16,7 @@
16from zope.component import getUtility16from zope.component import getUtility
17from zope.event import notify17from zope.event import notify
18from zope.interface import implements18from zope.interface import implements
19from zope.security.proxy import removeSecurityProxy
1920
20from lazr.lifecycle.event import ObjectCreatedEvent21from lazr.lifecycle.event import ObjectCreatedEvent
21from storm.locals import And22from storm.locals import And
@@ -160,6 +161,25 @@
160 self.validateBranchName(name)161 self.validateBranchName(name)
161 self.validateRegistrant(mover)162 self.validateRegistrant(mover)
162163
164 def moveBranch(self, branch, mover, new_name=None,
165 rename_if_necessary=False):
166 """See `IBranchNamespace`."""
167 # Check to see if the branch is already in this namespace.
168 old_namespace = branch.namespace
169 if self.name == old_namespace.name:
170 return
171 if new_name is None:
172 new_name = branch.name
173 if rename_if_necessary:
174 new_name = self.findUnusedName(new_name)
175 self.validateMove(branch, mover, new_name)
176 # Remove the security proxy of the branch as the owner and target
177 # attributes are readonly through the interface.
178 naked_branch = removeSecurityProxy(branch)
179 naked_branch.owner = self.owner
180 self.target.retargetBranch(naked_branch)
181 naked_branch.name = new_name
182
163 def createBranchWithPrefix(self, branch_type, prefix, registrant,183 def createBranchWithPrefix(self, branch_type, prefix, registrant,
164 url=None):184 url=None):
165 """See `IBranchNamespace`."""185 """See `IBranchNamespace`."""
166186
=== modified file 'lib/lp/code/model/branchtarget.py'
--- lib/lp/code/model/branchtarget.py 2009-07-17 00:26:05 +0000
+++ lib/lp/code/model/branchtarget.py 2009-07-28 05:20:23 +0000
@@ -13,7 +13,8 @@
1313
14from zope.component import getUtility14from zope.component import getUtility
15from zope.interface import implements15from zope.interface import implements
16from zope.security.proxy import isinstance as zope_isinstance16from zope.security.proxy import (
17 removeSecurityProxy, isinstance as zope_isinstance)
1718
18from lp.code.interfaces.branchcollection import IAllBranches19from lp.code.interfaces.branchcollection import IAllBranches
19from lp.code.interfaces.branchtarget import (20from lp.code.interfaces.branchtarget import (
@@ -128,6 +129,16 @@
128 # those cases.129 # those cases.
129 return bug.default_bugtask130 return bug.default_bugtask
130131
132 def retargetBranch(self, branch):
133 """See `IBranchTarget`."""
134 # Since product, distroseries and sourcepackagename are not writable
135 # as defined by the interface, we need to rip off the security proxy
136 # here.
137 naked_branch = removeSecurityProxy(branch)
138 naked_branch.product = None
139 naked_branch.distroseries = self.sourcepackage.distroseries
140 naked_branch.sourcepackagename = self.sourcepackage.sourcepackagename
141
131142
132class PersonBranchTarget(_BaseBranchTarget):143class PersonBranchTarget(_BaseBranchTarget):
133 implements(IBranchTarget)144 implements(IBranchTarget)
@@ -182,6 +193,16 @@
182 """See `IBranchTarget`."""193 """See `IBranchTarget`."""
183 return bug.default_bugtask194 return bug.default_bugtask
184195
196 def retargetBranch(self, branch):
197 """See `IBranchTarget`."""
198 # Since product, distroseries and sourcepackagename are not writable
199 # as defined by the interface, we need to rip off the security proxy
200 # here.
201 naked_branch = removeSecurityProxy(branch)
202 naked_branch.product = None
203 naked_branch.distroseries = None
204 naked_branch.sourcepackagename = None
205
185206
186class ProductBranchTarget(_BaseBranchTarget):207class ProductBranchTarget(_BaseBranchTarget):
187 implements(IBranchTarget)208 implements(IBranchTarget)
@@ -265,6 +286,16 @@
265 task = bug.bugtasks[0]286 task = bug.bugtasks[0]
266 return task287 return task
267288
289 def retargetBranch(self, branch):
290 """See `IBranchTarget`."""
291 # Since product, distroseries and sourcepackagename are not writable
292 # as defined by the interface, we need to rip off the security proxy
293 # here.
294 naked_branch = removeSecurityProxy(branch)
295 naked_branch.product = self.product
296 naked_branch.distroseries = None
297 naked_branch.sourcepackagename = None
298
268299
269def get_canonical_url_data_for_target(branch_target):300def get_canonical_url_data_for_target(branch_target):
270 """Return the `ICanonicalUrlData` for an `IBranchTarget`."""301 """Return the `ICanonicalUrlData` for an `IBranchTarget`."""
271302
=== modified file 'lib/lp/code/model/tests/test_branch.py'
--- lib/lp/code/model/tests/test_branch.py 2009-07-23 02:06:55 +0000
+++ lib/lp/code/model/tests/test_branch.py 2009-08-02 23:17:10 +0000
@@ -39,6 +39,7 @@
39 BranchVisibilityRule, CodeReviewNotificationLevel)39 BranchVisibilityRule, CodeReviewNotificationLevel)
40from lp.code.interfaces.branch import (40from lp.code.interfaces.branch import (
41 BranchCannotBePrivate, BranchCannotBePublic,41 BranchCannotBePrivate, BranchCannotBePublic,
42 BranchCreatorNotMemberOfOwnerTeam, BranchCreatorNotOwner,
42 CannotDeleteBranch, DEFAULT_BRANCH_STATUS_IN_LISTING)43 CannotDeleteBranch, DEFAULT_BRANCH_STATUS_IN_LISTING)
43from lp.code.interfaces.branchlookup import IBranchLookup44from lp.code.interfaces.branchlookup import IBranchLookup
44from lp.code.interfaces.branchnamespace import IBranchNamespaceSet45from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
@@ -213,8 +214,7 @@
213 # attribute is updated too.214 # attribute is updated too.
214 branch = self.factory.makeAnyBranch()215 branch = self.factory.makeAnyBranch()
215 new_owner = self.factory.makePerson()216 new_owner = self.factory.makePerson()
216 login('admin@canonical.com')217 removeSecurityProxy(branch).owner = new_owner
217 branch.owner = new_owner
218 # Call the function that is normally called through the event system218 # Call the function that is normally called through the event system
219 # to auto reload the fields updated by the db triggers.219 # to auto reload the fields updated by the db triggers.
220 update_trigger_modified_fields(branch)220 update_trigger_modified_fields(branch)
@@ -1054,9 +1054,9 @@
10541054
1055 def setUp(self):1055 def setUp(self):
1056 TestCaseWithFactory.setUp(self, 'admin@canonical.com')1056 TestCaseWithFactory.setUp(self, 'admin@canonical.com')
1057 self.product = getUtility(IProductSet).getByName('firefox')1057 self.product = self.factory.makeProduct()
10581058
1059 self.user = getUtility(IPersonSet).getByName('no-priv')1059 self.user = self.factory.makePerson()
1060 self.source = self.factory.makeProductBranch(1060 self.source = self.factory.makeProductBranch(
1061 name='source-branch', owner=self.user, product=self.product)1061 name='source-branch', owner=self.user, product=self.product)
1062 self.target = self.factory.makeProductBranch(1062 self.target = self.factory.makeProductBranch(
@@ -1069,7 +1069,7 @@
10691069
1070 def test_junkSource(self):1070 def test_junkSource(self):
1071 """Junk branches cannot be used as a source for merge proposals."""1071 """Junk branches cannot be used as a source for merge proposals."""
1072 self.source.product = None1072 self.source.setTarget(user=self.source.owner)
1073 self.assertRaises(1073 self.assertRaises(
1074 InvalidBranchMergeProposal, self.source.addLandingTarget,1074 InvalidBranchMergeProposal, self.source.addLandingTarget,
1075 self.user, self.target)1075 self.user, self.target)
@@ -1078,12 +1078,13 @@
1078 """The product of the target branch must match the product of the1078 """The product of the target branch must match the product of the
1079 source branch.1079 source branch.
1080 """1080 """
1081 self.target.product = None1081 self.target.setTarget(user=self.target.owner)
1082 self.assertRaises(1082 self.assertRaises(
1083 InvalidBranchMergeProposal, self.source.addLandingTarget,1083 InvalidBranchMergeProposal, self.source.addLandingTarget,
1084 self.user, self.target)1084 self.user, self.target)
10851085
1086 self.target.product = getUtility(IProductSet).getByName('bzr')1086 project = self.factory.makeProduct()
1087 self.target.setTarget(user=self.target.owner, project=project)
1087 self.assertRaises(1088 self.assertRaises(
1088 InvalidBranchMergeProposal, self.source.addLandingTarget,1089 InvalidBranchMergeProposal, self.source.addLandingTarget,
1089 self.user, self.target)1090 self.user, self.target)
@@ -1097,12 +1098,13 @@
1097 def test_dependentBranchSameProduct(self):1098 def test_dependentBranchSameProduct(self):
1098 """The dependent branch, if it is there, must be for the same product.1099 """The dependent branch, if it is there, must be for the same product.
1099 """1100 """
1100 self.dependent.product = None1101 self.dependent.setTarget(user=self.dependent.owner)
1101 self.assertRaises(1102 self.assertRaises(
1102 InvalidBranchMergeProposal, self.source.addLandingTarget,1103 InvalidBranchMergeProposal, self.source.addLandingTarget,
1103 self.user, self.target, self.dependent)1104 self.user, self.target, self.dependent)
11041105
1105 self.dependent.product = getUtility(IProductSet).getByName('bzr')1106 project = self.factory.makeProduct()
1107 self.dependent.setTarget(user=self.dependent.owner, project=project)
1106 self.assertRaises(1108 self.assertRaises(
1107 InvalidBranchMergeProposal, self.source.addLandingTarget,1109 InvalidBranchMergeProposal, self.source.addLandingTarget,
1108 self.user, self.target, self.dependent)1110 self.user, self.target, self.dependent)
@@ -1650,5 +1652,138 @@
1650 self.assertEqual(branch.spec_links.count(), 0)1652 self.assertEqual(branch.spec_links.count(), 0)
16511653
16521654
1655class TestBranchSetOwner(TestCaseWithFactory):
1656 """Tests for IBranch.setOwner."""
1657
1658 layer = DatabaseFunctionalLayer
1659
1660 def test_owner_sets_team(self):
1661 # The owner of the branch can set the owner of the branch to be a team
1662 # they are a member of.
1663 branch = self.factory.makeAnyBranch()
1664 team = self.factory.makeTeam(owner=branch.owner)
1665 login_person(branch.owner)
1666 branch.setOwner(team, branch.owner)
1667 self.assertEqual(team, branch.owner)
1668
1669 def test_owner_cannot_set_nonmember_team(self):
1670 # The owner of the branch cannot set the owner to be a team they are
1671 # not a member of.
1672 branch = self.factory.makeAnyBranch()
1673 team = self.factory.makeTeam()
1674 login_person(branch.owner)
1675 self.assertRaises(
1676 BranchCreatorNotMemberOfOwnerTeam,
1677 branch.setOwner,
1678 team, branch.owner)
1679
1680 def test_owner_cannot_set_other_user(self):
1681 # The owner of the branch cannot set the new owner to be another
1682 # person.
1683 branch = self.factory.makeAnyBranch()
1684 person = self.factory.makePerson()
1685 login_person(branch.owner)
1686 self.assertRaises(
1687 BranchCreatorNotOwner,
1688 branch.setOwner,
1689 person, branch.owner)
1690
1691 def test_admin_can_set_any_team_or_person(self):
1692 # A Launchpad admin can set the branch to be owned by any team or
1693 # person.
1694 branch = self.factory.makeAnyBranch()
1695 team = self.factory.makeTeam()
1696 # To get a random administrator, choose the admin team owner.
1697 admin = getUtility(ILaunchpadCelebrities).admin.teamowner
1698 login_person(admin)
1699 branch.setOwner(team, admin)
1700 self.assertEqual(team, branch.owner)
1701 person = self.factory.makePerson()
1702 branch.setOwner(person, admin)
1703 self.assertEqual(person, branch.owner)
1704
1705 def test_bazaar_experts_can_set_any_team_or_person(self):
1706 # A bazaar expert can set the branch to be owned by any team or
1707 # person.
1708 branch = self.factory.makeAnyBranch()
1709 team = self.factory.makeTeam()
1710 # To get a random administrator, choose the admin team owner.
1711 experts = getUtility(ILaunchpadCelebrities).bazaar_experts.teamowner
1712 login_person(experts)
1713 branch.setOwner(team, experts)
1714 self.assertEqual(team, branch.owner)
1715 person = self.factory.makePerson()
1716 branch.setOwner(person, experts)
1717 self.assertEqual(person, branch.owner)
1718
1719
1720class TestBranchSetTarget(TestCaseWithFactory):
1721 """Tests for IBranch.setTarget."""
1722
1723 layer = DatabaseFunctionalLayer
1724
1725 def test_junk_branch_to_project_branch(self):
1726 # A junk branch can be moved to a project.
1727 branch = self.factory.makePersonalBranch()
1728 project = self.factory.makeProduct()
1729 login_person(branch.owner)
1730 branch.setTarget(user=branch.owner, project=project)
1731 self.assertEqual(project, branch.target.context)
1732
1733 def test_junk_branch_to_package_branch(self):
1734 # A junk branch can be moved to a source package.
1735 branch = self.factory.makePersonalBranch()
1736 source_package = self.factory.makeSourcePackage()
1737 login_person(branch.owner)
1738 branch.setTarget(user=branch.owner, source_package=source_package)
1739 self.assertEqual(source_package, branch.target.context)
1740
1741 def test_project_branch_to_other_project_branch(self):
1742 # Move a branch from one project to another.
1743 branch = self.factory.makeProductBranch()
1744 project = self.factory.makeProduct()
1745 login_person(branch.owner)
1746 branch.setTarget(user=branch.owner, project=project)
1747 self.assertEqual(project, branch.target.context)
1748
1749 def test_project_branch_to_package_branch(self):
1750 # Move a branch from a project to a package.
1751 branch = self.factory.makeProductBranch()
1752 source_package = self.factory.makeSourcePackage()
1753 login_person(branch.owner)
1754 branch.setTarget(user=branch.owner, source_package=source_package)
1755 self.assertEqual(source_package, branch.target.context)
1756
1757 def test_project_branch_to_junk_branch(self):
1758 # Move a branch from a project to junk.
1759 branch = self.factory.makeProductBranch()
1760 login_person(branch.owner)
1761 branch.setTarget(user=branch.owner)
1762 self.assertEqual(branch.owner, branch.target.context)
1763
1764 def test_package_branch_to_other_package_branch(self):
1765 # Move a branch from one package to another.
1766 branch = self.factory.makePackageBranch()
1767 source_package = self.factory.makeSourcePackage()
1768 login_person(branch.owner)
1769 branch.setTarget(user=branch.owner, source_package=source_package)
1770 self.assertEqual(source_package, branch.target.context)
1771
1772 def test_package_branch_to_project_branch(self):
1773 # Move a branch from a package to a project.
1774 branch = self.factory.makePackageBranch()
1775 project = self.factory.makeProduct()
1776 login_person(branch.owner)
1777 branch.setTarget(user=branch.owner, project=project)
1778 self.assertEqual(project, branch.target.context)
1779
1780 def test_package_branch_to_junk_branch(self):
1781 # Move a branch from a package to junk.
1782 branch = self.factory.makePackageBranch()
1783 login_person(branch.owner)
1784 branch.setTarget(user=branch.owner)
1785 self.assertEqual(branch.owner, branch.target.context)
1786
1787
1653def test_suite():1788def test_suite():
1654 return TestLoader().loadTestsFromName(__name__)1789 return TestLoader().loadTestsFromName(__name__)
16551790
=== renamed file 'lib/lp/code/tests/test_branchnamespace.py' => 'lib/lp/code/model/tests/test_branchnamespace.py'
--- lib/lp/code/tests/test_branchnamespace.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/model/tests/test_branchnamespace.py 2009-07-29 02:16:43 +0000
@@ -1871,5 +1871,70 @@
1871 BranchCreatorNotOwner, self.albert, self.doug)1871 BranchCreatorNotOwner, self.albert, self.doug)
18721872
18731873
1874class TestBranchNamespaceMoveBranch(TestCaseWithFactory):
1875 """Test the IBranchNamespace.moveBranch method.
1876
1877 The edge cases of the validateMove are tested in the NamespaceMixin for
1878 each of the namespaces.
1879 """
1880
1881 layer = DatabaseFunctionalLayer
1882
1883 def assertNamespacesEqual(self, expected, result):
1884 """Assert that the namespaces refer to the same thing.
1885
1886 The name of the namespace contains the user name and the context
1887 parts, so is the easiest thing to check.
1888 """
1889 self.assertEqual(expected.name, result.name)
1890
1891 def test_move_to_same_namespace(self):
1892 # Moving to the same namespace is effectively a no-op. No exceptions
1893 # about matching branch names should be raised.
1894 branch = self.factory.makeAnyBranch()
1895 namespace = branch.namespace
1896 namespace.moveBranch(branch, branch.owner)
1897 self.assertNamespacesEqual(namespace, branch.namespace)
1898
1899 def test_name_clash_raises(self):
1900 # A name clash will raise an exception.
1901 branch = self.factory.makeAnyBranch(name="test")
1902 another = self.factory.makeAnyBranch(owner=branch.owner, name="test")
1903 namespace = another.namespace
1904 self.assertRaises(
1905 BranchExists, namespace.moveBranch, branch, branch.owner)
1906
1907 def test_move_with_rename(self):
1908 # A name clash with 'rename_if_necessary' set to True will cause the
1909 # branch to be renamed instead of raising an error.
1910 branch = self.factory.makeAnyBranch(name="test")
1911 another = self.factory.makeAnyBranch(owner=branch.owner, name="test")
1912 namespace = another.namespace
1913 namespace.moveBranch(branch, branch.owner, rename_if_necessary=True)
1914 self.assertEqual("test-1", branch.name)
1915 self.assertNamespacesEqual(namespace, branch.namespace)
1916
1917 def test_move_with_new_name(self):
1918 # A new name for the branch can be specified as part of the move.
1919 branch = self.factory.makeAnyBranch(name="test")
1920 another = self.factory.makeAnyBranch(owner=branch.owner, name="test")
1921 namespace = another.namespace
1922 namespace.moveBranch(branch, branch.owner, new_name="foo")
1923 self.assertEqual("foo", branch.name)
1924 self.assertNamespacesEqual(namespace, branch.namespace)
1925
1926 def test_sets_branch_owner(self):
1927 # Moving to a new namespace may change the owner of the branch if the
1928 # owner of the namespace is different.
1929 branch = self.factory.makeAnyBranch(name="test")
1930 team = self.factory.makeTeam(branch.owner)
1931 product = self.factory.makeProduct()
1932 namespace = ProductNamespace(team, product)
1933 namespace.moveBranch(branch, branch.owner)
1934 self.assertEqual(team, branch.owner)
1935 # And for paranoia.
1936 self.assertNamespacesEqual(namespace, branch.namespace)
1937
1938
1874def test_suite():1939def test_suite():
1875 return unittest.TestLoader().loadTestsFromName(__name__)1940 return unittest.TestLoader().loadTestsFromName(__name__)
18761941
=== modified file 'lib/lp/code/model/tests/test_branchtarget.py'
--- lib/lp/code/model/tests/test_branchtarget.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/model/tests/test_branchtarget.py 2009-07-28 05:20:23 +0000
@@ -46,6 +46,24 @@
46 branches = self.target.collection.getBranches()46 branches = self.target.collection.getBranches()
47 self.assertEqual([branch], list(branches))47 self.assertEqual([branch], list(branches))
4848
49 def test_retargetBranch_packageBranch(self):
50 # Retarget an existing package branch to this target.
51 branch = self.factory.makePackageBranch()
52 self.target.retargetBranch(branch)
53 self.assertEqual(self.target, branch.target)
54
55 def test_retargetBranch_productBranch(self):
56 # Retarget an existing product branch to this target.
57 branch = self.factory.makeProductBranch()
58 self.target.retargetBranch(branch)
59 self.assertEqual(self.target, branch.target)
60
61 def test_retargetBranch_personalBranch(self):
62 # Retarget an existing personal branch to this target.
63 branch = self.factory.makePersonalBranch()
64 self.target.retargetBranch(branch)
65 self.assertEqual(self.target, branch.target)
66
4967
50class TestPackageBranchTarget(TestCaseWithFactory, BaseBranchTargetTests):68class TestPackageBranchTarget(TestCaseWithFactory, BaseBranchTargetTests):
5169
@@ -212,6 +230,33 @@
212 # The default merge target is always None.230 # The default merge target is always None.
213 self.assertIs(None, self.target.default_merge_target)231 self.assertIs(None, self.target.default_merge_target)
214232
233 def test_retargetBranch_packageBranch(self):
234 # Retarget an existing package branch to this target. Override the
235 # mixin tests, and specify the owner of the branch. This is needed to
236 # match the target as the target is the branch owner for a personal
237 # branch.
238 branch = self.factory.makePackageBranch(owner=self.original)
239 self.target.retargetBranch(branch)
240 self.assertEqual(self.target, branch.target)
241
242 def test_retargetBranch_productBranch(self):
243 # Retarget an existing product branch to this target. Override the
244 # mixin tests, and specify the owner of the branch. This is needed to
245 # match the target as the target is the branch owner for a personal
246 # branch.
247 branch = self.factory.makeProductBranch(owner=self.original)
248 self.target.retargetBranch(branch)
249 self.assertEqual(self.target, branch.target)
250
251 def test_retargetBranch_personalBranch(self):
252 # Retarget an existing personal branch to this target. Override the
253 # mixin tests, and specify the owner of the branch. This is needed to
254 # match the target as the target is the branch owner for a personal
255 # branch.
256 branch = self.factory.makePersonalBranch(owner=self.original)
257 self.target.retargetBranch(branch)
258 self.assertEqual(self.target, branch.target)
259
215260
216class TestProductBranchTarget(TestCaseWithFactory, BaseBranchTargetTests):261class TestProductBranchTarget(TestCaseWithFactory, BaseBranchTargetTests):
217262
218263
=== modified file 'lib/lp/code/model/tests/test_revision.py'
--- lib/lp/code/model/tests/test_revision.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/model/tests/test_revision.py 2009-08-02 23:17:10 +0000
@@ -146,7 +146,8 @@
146 branch.createBranchRevision(1, rev)146 branch.createBranchRevision(1, rev)
147 # Once the branch is connected to the revision, we now specify147 # Once the branch is connected to the revision, we now specify
148 # a product for the branch.148 # a product for the branch.
149 branch.product = self.factory.makeProduct()149 project = self.factory.makeProduct()
150 branch.setTarget(user=branch.owner, project=project)
150 # The revision is now identified as needing karma allocated.151 # The revision is now identified as needing karma allocated.
151 self.assertEqual(152 self.assertEqual(
152 [rev], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))153 [rev], list(RevisionSet.getRevisionsNeedingKarmaAllocated()))
153154
=== modified file 'lib/lp/code/scripts/tests/test_revisionkarma.py'
--- lib/lp/code/scripts/tests/test_revisionkarma.py 2009-06-25 04:06:00 +0000
+++ lib/lp/code/scripts/tests/test_revisionkarma.py 2009-08-02 23:17:10 +0000
@@ -49,7 +49,8 @@
49 branch.createBranchRevision(1, rev)49 branch.createBranchRevision(1, rev)
50 # Once the branch is connected to the revision, we now specify50 # Once the branch is connected to the revision, we now specify
51 # a product for the branch.51 # a product for the branch.
52 branch.product = self.factory.makeProduct()52 project=self.factory.makeProduct()
53 branch.setTarget(user=branch.owner, project=project)
53 # Commit and switch to the script db user.54 # Commit and switch to the script db user.
54 transaction.commit()55 transaction.commit()
55 LaunchpadZopelessLayer.switchDbUser(config.revisionkarma.dbuser)56 LaunchpadZopelessLayer.switchDbUser(config.revisionkarma.dbuser)
5657
=== modified file 'lib/lp/codehosting/tests/test_acceptance.py'
--- lib/lp/codehosting/tests/test_acceptance.py 2009-07-17 00:26:05 +0000
+++ lib/lp/codehosting/tests/test_acceptance.py 2009-08-02 23:38:51 +0000
@@ -8,13 +8,13 @@
8import atexit8import atexit
9import os9import os
10import unittest10import unittest
11from xml.dom.minidom import parseString
12import xmlrpclib11import xmlrpclib
1312
14import bzrlib.branch13import bzrlib.branch
15from bzrlib.tests import TestCaseWithTransport14from bzrlib.tests import TestCaseWithTransport
16from bzrlib.urlutils import local_path_from_url15from bzrlib.urlutils import local_path_from_url
17from bzrlib.workingtree import WorkingTree16from bzrlib.workingtree import WorkingTree
17from zope.security.proxy import removeSecurityProxy
1818
19from lp.codehosting.bzrutils import DenyingServer19from lp.codehosting.bzrutils import DenyingServer
20from lp.codehosting.tests.helpers import (20from lp.codehosting.tests.helpers import (
@@ -382,7 +382,8 @@
382 # rename as far as bzr is concerned: the URL changes.382 # rename as far as bzr is concerned: the URL changes.
383 LaunchpadZopelessTestSetup().txn.begin()383 LaunchpadZopelessTestSetup().txn.begin()
384 branch = self.getDatabaseBranch('testuser', None, 'test-branch')384 branch = self.getDatabaseBranch('testuser', None, 'test-branch')
385 branch.product = database.Product.byName('firefox')385 removeSecurityProxy(branch).product = database.Product.byName(
386 'firefox')
386 LaunchpadZopelessTestSetup().txn.commit()387 LaunchpadZopelessTestSetup().txn.commit()
387388
388 self.assertNotBranch(389 self.assertNotBranch(
389390
=== modified file 'lib/lp/registry/browser/tests/private-team-creation-views.txt'
--- lib/lp/registry/browser/tests/private-team-creation-views.txt 2009-06-16 17:12:30 +0000
+++ lib/lp/registry/browser/tests/private-team-creation-views.txt 2009-08-03 01:02:44 +0000
@@ -400,8 +400,7 @@
400 ... bugtask = bug.default_bugtask400 ... bugtask = bug.default_bugtask
401 ... bugtask.transitionToAssignee(team)401 ... bugtask.transitionToAssignee(team)
402 ... # A branch.402 ... # A branch.
403 ... branch = factory.makeBranch()403 ... branch = factory.makeBranch(owner=team)
404 ... branch.owner = team
405 ... # A branch subscription.404 ... # A branch subscription.
406 ... from lp.code.enums import (405 ... from lp.code.enums import (
407 ... BranchSubscriptionDiffSize,406 ... BranchSubscriptionDiffSize,
408407
=== modified file 'lib/lp/registry/doc/private-team-roles.txt'
--- lib/lp/registry/doc/private-team-roles.txt 2009-06-19 01:27:32 +0000
+++ lib/lp/registry/doc/private-team-roles.txt 2009-08-03 00:41:34 +0000
@@ -23,9 +23,10 @@
23-----------------23-----------------
2424
25 >>> # Create the necessary teams.25 >>> # Create the necessary teams.
26 >>> from lp.registry.interfaces.person import PersonVisibility
27 >>> team_owner = factory.makePerson(name='team-owner')26 >>> team_owner = factory.makePerson(name='team-owner')
28 >>> login('foo.bar@canonical.com')27 >>> from lp.registry.interfaces.person import IPersonSet, PersonVisibility
28 >>> admin_user = getUtility(IPersonSet).getByEmail('admin@canonical.com')
29 >>> login_person(admin_user)
29 >>> priv_team = factory.makeTeam(name='private-team',30 >>> priv_team = factory.makeTeam(name='private-team',
30 ... owner=team_owner,31 ... owner=team_owner,
31 ... visibility=PersonVisibility.PRIVATE)32 ... visibility=PersonVisibility.PRIVATE)
@@ -102,12 +103,12 @@
102Private teams can be assigned as the owner of a branch103Private teams can be assigned as the owner of a branch
103104
104 >>> branch = factory.makeBranch()105 >>> branch = factory.makeBranch()
105 >>> branch.owner = priv_team106 >>> branch.setOwner(priv_team, user=admin_user)
106107
107But private membership teams cannot own a branch.108But private membership teams cannot own a branch.
108109
109 >>> branch = factory.makeBranch()110 >>> branch = factory.makeBranch()
110 >>> branch.owner = pm_team111 >>> branch.setOwner(pm_team, user=admin_user)
111 Traceback (most recent call last):112 Traceback (most recent call last):
112 ...113 ...
113 PrivatePersonLinkageError: Cannot link person114 PrivatePersonLinkageError: Cannot link person