launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01842
[Merge] lp:~matsubara/launchpad/ec2-land-update-mp into lp:launchpad
Diogo Matsubara has proposed merging lp:~matsubara/launchpad/ec2-land-update-mp into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Hi there,
this branch changes the ec2 land command to add the built commit message to the merge proposal.
It also changes the method to get_commit_message to build_commit_message to make it more clear what it's doing.
To run tests: bin/test -tt test_autoland
--
https://code.launchpad.net/~matsubara/launchpad/ec2-land-update-mp/+merge/40228
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~matsubara/launchpad/ec2-land-update-mp into lp:launchpad.
=== modified file 'lib/canonical/launchpad/interfaces/_schema_circular_imports.py'
--- lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-03 22:37:55 +0000
+++ lib/canonical/launchpad/interfaces/_schema_circular_imports.py 2010-11-05 19:52:03 +0000
@@ -383,7 +383,7 @@
IDistroSeries, 'getPackageUploads', IPackageUpload)
patch_reference_property(IDistroSeries, 'parent_series', IDistroSeries)
patch_plain_parameter_type(
- IDistroSeries, 'deriveDistroSeries', 'distribution', IDistroSeries)
+ IDistroSeries, 'deriveDistroSeries', 'distribution', IDistribution)
# IDistroSeriesDifferenceComment
IDistroSeriesDifferenceComment['comment_author'].schema = IPerson
=== modified file 'lib/devscripts/autoland.py'
--- lib/devscripts/autoland.py 2010-10-24 21:00:11 +0000
+++ lib/devscripts/autoland.py 2010-11-05 19:52:03 +0000
@@ -160,21 +160,35 @@
# URL. Do it ourselves.
return URI(scheme='bzr+ssh', host=host, path='/' + branch.unique_name)
- def get_commit_message(self, commit_text, testfix=False, no_qa=False,
+ def build_commit_message(self, commit_text, testfix=False, no_qa=False,
incremental=False, rollback=None):
"""Get the Launchpad-style commit message for a merge proposal."""
reviews = self.get_reviews()
bugs = self.get_bugs()
- tags = ''.join([
+ tags = [
get_testfix_clause(testfix),
get_reviewer_clause(reviews),
get_bugs_clause(bugs),
get_qa_clause(bugs, no_qa,
incremental, rollback=rollback),
- ])
-
- return '%s %s' % (tags, commit_text)
+ ]
+
+ # Make sure we don't add duplicated tags to commit_text.
+ commit_tags = tags[:]
+ for tag in tags:
+ if tag in commit_text:
+ commit_tags.remove(tag)
+
+ if commit_tags:
+ return '%s %s' % (''.join(commit_tags), commit_text)
+ else:
+ return commit_text
+
+ def set_commit_message(self, commit_message):
+ """Set the Launchpad-style commit message for a merge proposal."""
+ self._mp.commit_message = commit_message
+ self._mp.lp_save()
def get_testfix_clause(testfix=False):
=== modified file 'lib/devscripts/ec2test/builtins.py'
--- lib/devscripts/ec2test/builtins.py 2010-10-29 14:52:10 +0000
+++ lib/devscripts/ec2test/builtins.py 2010-11-05 19:52:03 +0000
@@ -411,7 +411,7 @@
if rollback and (no_qa or incremental):
print "--rollback option used. Ignoring --no-qa and --incremental."
try:
- commit_message = mp.get_commit_message(
+ commit_message = mp.build_commit_message(
commit_text, testfix, no_qa, incremental, rollback=rollback)
except MissingReviewError:
raise BzrCommandError(
@@ -428,6 +428,15 @@
"--incremental option requires bugs linked to the branch. "
"Link the bugs or remove the --incremental option.")
+ # Override the commit message in the MP with the commit message built
+ # with the proper tags.
+ try:
+ mp.set_commit_message(commit_message)
+ except Exception, e:
+ raise BzrCommandError(
+ "Unable to set the commit message in the merge proposal.\n"
+ "Got: %s" % e)
+
if print_commit:
print commit_message
return
=== modified file 'lib/devscripts/tests/test_autoland.py'
--- lib/devscripts/tests/test_autoland.py 2010-09-03 15:13:01 +0000
+++ lib/devscripts/tests/test_autoland.py 2010-11-05 19:52:03 +0000
@@ -59,6 +59,10 @@
def __init__(self, root=None):
self._root = root
+ self.commit_message = None
+
+ def lp_save(self):
+ pass
class TestPQMRegexAcceptance(unittest.TestCase):
@@ -91,7 +95,7 @@
raise self.failureException(msg)
def _test_commit_message_match(self, incr, no_qa, testfix):
- commit_message = self.mp.get_commit_message("Foobaring the sbrubble.",
+ commit_message = self.mp.build_commit_message("Foobaring the sbrubble.",
testfix, no_qa, incr)
self.assertRegexpMatches(commit_message, self.devel_open_re)
self.assertRegexpMatches(commit_message, self.dbdevel_normal_re)
@@ -150,7 +154,7 @@
self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
self.assertEqual("[r=foo][ui=none][bug=20] Foobaring the sbrubble.",
- self.mp.get_commit_message("Foobaring the sbrubble.",
+ self.mp.build_commit_message("Foobaring the sbrubble.",
testfix, no_qa, incr))
def test_commit_no_bugs_no_noqa(self):
@@ -161,7 +165,7 @@
self.mp.get_bugs = FakeMethod([])
self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
- self.assertRaises(MissingBugsError, self.mp.get_commit_message,
+ self.assertRaises(MissingBugsError, self.mp.build_commit_message,
testfix, no_qa, incr)
def test_commit_no_bugs_with_noqa(self):
@@ -173,7 +177,7 @@
self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
self.assertEqual("[r=foo][ui=none][no-qa] Foobaring the sbrubble.",
- self.mp.get_commit_message("Foobaring the sbrubble.",
+ self.mp.build_commit_message("Foobaring the sbrubble.",
testfix, no_qa, incr))
def test_commit_bugs_with_noqa(self):
@@ -186,7 +190,7 @@
self.assertEqual(
"[r=foo][ui=none][bug=20][no-qa] Foobaring the sbrubble.",
- self.mp.get_commit_message("Foobaring the sbrubble.",
+ self.mp.build_commit_message("Foobaring the sbrubble.",
testfix, no_qa, incr))
def test_commit_bugs_with_incr(self):
@@ -199,7 +203,7 @@
self.assertEqual(
"[r=foo][ui=none][bug=20][incr] Foobaring the sbrubble.",
- self.mp.get_commit_message("Foobaring the sbrubble.",
+ self.mp.build_commit_message("Foobaring the sbrubble.",
testfix, no_qa, incr))
def test_commit_no_bugs_with_incr(self):
@@ -212,7 +216,7 @@
self.assertEqual(
"[r=foo][ui=none][bug=20][incr] Foobaring the sbrubble.",
- self.mp.get_commit_message("Foobaring the sbrubble.",
+ self.mp.build_commit_message("Foobaring the sbrubble.",
testfix, no_qa, incr))
def test_commit_with_noqa_and_incr(self):
@@ -225,7 +229,7 @@
self.assertEqual(
"[r=foo][ui=none][bug=20][no-qa][incr] Foobaring the sbrubble.",
- self.mp.get_commit_message("Foobaring the sbrubble.",
+ self.mp.build_commit_message("Foobaring the sbrubble.",
testfix, no_qa, incr))
def test_commit_with_rollback(self):
@@ -234,8 +238,29 @@
self.assertEqual(
"[r=foo][ui=none][bug=20][rollback=123] Foobaring the sbrubble.",
- self.mp.get_commit_message("Foobaring the sbrubble.",
- rollback=123))
+ self.mp.build_commit_message("Foobaring the sbrubble.",
+ rollback=123))
+
+ def test_takes_into_account_existing_tags_on_commit_text(self):
+ self.mp.get_bugs = FakeMethod([self.fake_bug])
+ self.mp.get_reviews = FakeMethod({None : [self.fake_person]})
+
+ self.assertEqual(
+ "[r=foo][ui=none][bug=20][rollback=123] Foobaring the sbrubble.",
+ self.mp.build_commit_message(
+ "[r=foo][ui=none][bug=20][rollback=123] Foobaring the sbrubble.",
+ rollback=123))
+
+
+class TestSetCommitMessage(unittest.TestCase):
+
+ def setUp(self):
+ self.mp = MergeProposal(FakeLPMergeProposal())
+
+ def test_set_commit_message(self):
+ commit_message = "Foobaring the sbrubble."
+ self.mp.set_commit_message(commit_message)
+ self.assertEqual(self.mp._mp.commit_message, commit_message)
class TestGetTestfixClause(unittest.TestCase):
=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml 2010-10-03 15:30:06 +0000
+++ lib/lp/blueprints/browser/configure.zcml 2010-11-05 19:52:03 +0000
@@ -8,6 +8,21 @@
xmlns:i18n="http://namespaces.zope.org/i18n"
xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
i18n_domain="launchpad">
+
+ <adapter
+ name="blueprints"
+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+ for="lp.blueprints.interfaces.specificationtarget.IHasSpecifications"
+ factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
+ permission="zope.Public"/>
+ <adapter
+ name="blueprints"
+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+ for="lp.registry.interfaces.person.IPerson"
+ factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
+ permission="zope.Public"/>
+
+
<browser:navigation
module="lp.blueprints.browser.sprint"
classes="
=== modified file 'lib/lp/blueprints/browser/specification.py'
--- lib/lp/blueprints/browser/specification.py 2010-11-01 03:32:29 +0000
+++ lib/lp/blueprints/browser/specification.py 2010-11-05 19:52:03 +0000
@@ -536,13 +536,14 @@
@action(_('Change'), name='change')
def change_action(self, action, data):
+ old_status = self.context.lifecycle_status
self.updateContextFromData(data)
# We need to ensure that resolution is recorded if the spec is now
# resolved.
- newstate = self.context.updateLifecycleStatus(self.user)
- if newstate is not None:
+ new_status = self.context.lifecycle_status
+ if new_status != old_status:
self.request.response.addNotification(
- 'blueprint is now considered "%s".' % newstate.title)
+ 'Blueprint is now considered "%s".' % new_status.title)
self.next_url = canonical_url(self.context)
@@ -735,7 +736,7 @@
class SpecificationSupersedingView(LaunchpadFormView):
schema = ISpecification
field_names = ['superseded_by']
- label = _('Mark specification superseded')
+ label = _('Mark blueprint superseded')
custom_widget('superseded_by', SupersededByWidget)
@property
@@ -762,7 +763,7 @@
description=_(
"The blueprint which supersedes this one. Note "
"that selecting a blueprint here and pressing "
- "Continue will change the specification status "
+ "Continue will change the blueprint status "
"to Superseded.")),
render_context=self.render_context)
@@ -784,7 +785,7 @@
newstate = self.context.updateLifecycleStatus(self.user)
if newstate is not None:
self.request.response.addNotification(
- 'Specification is now considered "%s".' % newstate.title)
+ 'Blueprint is now considered "%s".' % newstate.title)
self.next_url = canonical_url(self.context)
@property
=== modified file 'lib/lp/blueprints/browser/tests/test_specification.py'
--- lib/lp/blueprints/browser/tests/test_specification.py 2010-08-20 20:31:18 +0000
+++ lib/lp/blueprints/browser/tests/test_specification.py 2010-11-05 19:52:03 +0000
@@ -8,10 +8,13 @@
from lazr.restful.testing.webservice import FakeRequest
from zope.publisher.interfaces import NotFound
+from canonical.launchpad.webapp.interfaces import BrowserNotificationLevel
from canonical.launchpad.webapp.servers import StepsToGo
from canonical.testing.layers import DatabaseFunctionalLayer
from lp.blueprints.browser import specification
-from lp.testing import TestCaseWithFactory
+from lp.blueprints.enums import SpecificationImplementationStatus
+from lp.testing import login_person, TestCaseWithFactory
+from lp.testing.views import create_initialized_view
class LocalFakeRequest(FakeRequest):
@@ -108,6 +111,82 @@
self.specification.getBranchLink(branch), self.traverse(segments))
+class TestSpecificationEditStatusView(TestCaseWithFactory):
+ """Test the SpecificationEditStatusView."""
+
+ layer = DatabaseFunctionalLayer
+
+ def test_records_started(self):
+ spec = self.factory.makeSpecification(
+ implementation_status=SpecificationImplementationStatus.NOTSTARTED)
+ login_person(spec.owner)
+ form = {
+ 'field.implementation_status': 'STARTED',
+ 'field.actions.change': 'Change',
+ }
+ view = create_initialized_view(spec, name='+status', form=form)
+ self.assertEqual(
+ SpecificationImplementationStatus.STARTED, spec.implementation_status)
+ self.assertEqual(spec.owner, spec.starter)
+ [notification] = view.request.notifications
+ self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
+ self.assertEqual(
+ 'Blueprint is now considered "Started".', notification.message)
+
+ def test_unchanged_lifecycle_has_no_notification(self):
+ spec = self.factory.makeSpecification(
+ implementation_status=SpecificationImplementationStatus.STARTED)
+ login_person(spec.owner)
+ form = {
+ 'field.implementation_status': 'SLOW',
+ 'field.actions.change': 'Change',
+ }
+ view = create_initialized_view(spec, name='+status', form=form)
+ self.assertEqual(
+ SpecificationImplementationStatus.SLOW, spec.implementation_status)
+ self.assertEqual(0, len(view.request.notifications))
+
+ def test_records_unstarting(self):
+ # If a spec was started, and is changed to not started, a notice is shown.
+ # Also the spec.starter is cleared out.
+ spec = self.factory.makeSpecification(
+ implementation_status=SpecificationImplementationStatus.STARTED)
+ login_person(spec.owner)
+ form = {
+ 'field.implementation_status': 'NOTSTARTED',
+ 'field.actions.change': 'Change',
+ }
+ view = create_initialized_view(spec, name='+status', form=form)
+ self.assertEqual(
+ SpecificationImplementationStatus.NOTSTARTED,
+ spec.implementation_status)
+ self.assertIs(None, spec.starter)
+ [notification] = view.request.notifications
+ self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
+ self.assertEqual(
+ 'Blueprint is now considered "Not started".', notification.message)
+
+ def test_records_completion(self):
+ # If a spec is marked as implemented the user is notifiec it is now
+ # complete.
+ spec = self.factory.makeSpecification(
+ implementation_status=SpecificationImplementationStatus.STARTED)
+ login_person(spec.owner)
+ form = {
+ 'field.implementation_status': 'IMPLEMENTED',
+ 'field.actions.change': 'Change',
+ }
+ view = create_initialized_view(spec, name='+status', form=form)
+ self.assertEqual(
+ SpecificationImplementationStatus.IMPLEMENTED,
+ spec.implementation_status)
+ self.assertEqual(spec.owner, spec.completer)
+ [notification] = view.request.notifications
+ self.assertEqual(BrowserNotificationLevel.NOTICE, notification.level)
+ self.assertEqual(
+ 'Blueprint is now considered "Complete".', notification.message)
+
+
class TestSecificationHelpers(unittest.TestCase):
"""Test specification helper functions."""
=== modified file 'lib/lp/blueprints/configure.zcml'
--- lib/lp/blueprints/configure.zcml 2010-08-25 04:12:59 +0000
+++ lib/lp/blueprints/configure.zcml 2010-11-05 19:52:03 +0000
@@ -22,258 +22,220 @@
name="blueprints" />
- <lp:help-folder
- folder="help" type="lp.blueprints.publisher.BlueprintsLayer" />
-
- <!-- Sprint -->
-
- <class
- class="lp.blueprints.model.sprint.Sprint">
- <allow
- interface="lp.blueprints.interfaces.sprint.ISprint"/>
- <require
- permission="launchpad.AnyPerson"
- set_schema="lp.blueprints.interfaces.sprint.ISprint"/>
- </class>
- <adapter
- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
- for="lp.blueprints.interfaces.sprint.ISprint"
- factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
- permission="zope.Public"/>
- <adapter
- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
- for="lp.blueprints.interfaces.sprint.ISprintSet"
- factory="lp.blueprints.browser.sprint.SprintSetBreadcrumb"
- permission="zope.Public"/>
- <adapter
- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
- for="lp.blueprints.interfaces.specification.ISpecification"
- factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
- permission="zope.Public"/>
-
- <!-- This is a view used to export data needed by the sprint scheduler.
- As there are no API stability guarantees, the view name starts
- with "temp" to discourage people from relying on it. -->
-
-
- <!-- SprintSet -->
-
- <class
- class="lp.blueprints.model.sprint.SprintSet">
- <allow
- interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
- </class>
- <securedutility
- class="lp.blueprints.model.sprint.SprintSet"
- provides="lp.blueprints.interfaces.sprint.ISprintSet">
- <allow
- interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
- </securedutility>
-
- <!-- SprintSpecification -->
-
- <class
- class="lp.blueprints.model.sprintspecification.SprintSpecification">
- <allow
- interface="lp.blueprints.interfaces.sprintspecification.ISprintSpecification"/>
- <require
- permission="launchpad.Edit"
- set_attributes="whiteboard"/>
- </class>
-
- <!-- SpecificationDependency -->
-
- <class
- class="lp.blueprints.model.specificationdependency.SpecificationDependency">
- <allow
- interface="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
- <require
- permission="zope.Public"
- set_schema="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
- </class>
- <adapter
- for="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"
- factory="lp.blueprints.interfaces.specificationdependency.SpecDependencyIsAlsoRemoval"
- provides="lp.blueprints.interfaces.specificationdependency.ISpecificationDependencyRemoval"/>
-
- <!-- SpecificationSubscription -->
-
- <class
- class="lp.blueprints.model.specificationsubscription.SpecificationSubscription">
- <allow
- interface="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription"/>
- <require
- permission="launchpad.Edit"
- set_attributes="essential"/>
- </class>
- <subscriber
- for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription lazr.lifecycle.interfaces.IObjectCreatedEvent"
- handler="canonical.launchpad.mailnotification.notify_specification_subscription_created"/>
- <subscriber
- for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription lazr.lifecycle.interfaces.IObjectModifiedEvent"
- handler="canonical.launchpad.mailnotification.notify_specification_subscription_modified"/>
-
- <!-- SpecificationFeedback -->
-
- <class
- class="lp.blueprints.model.specificationfeedback.SpecificationFeedback">
- <allow
- interface="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
- <require
- permission="zope.Public"
- set_schema="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
- </class>
-
- <!-- SprintAttendance -->
-
- <class
- class="lp.blueprints.model.sprintattendance.SprintAttendance">
- <allow
- interface="lp.blueprints.interfaces.sprintattendance.ISprintAttendance"/>
- <require
- permission="launchpad.Edit"
- set_attributes="time_starts time_ends"/>
- </class>
-
- <!-- ISpecificationBranch -->
-
- <class
- class="lp.blueprints.model.specificationbranch.SpecificationBranch">
- <allow
- interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch"/>
- <require
- permission="launchpad.AnyPerson"
- set_attributes="summary"/>
- </class>
- <subscriber
- for="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch lazr.lifecycle.interfaces.IObjectCreatedEvent"
- handler="canonical.launchpad.subscribers.karma.spec_branch_created"/>
- <facet
- facet="specifications"/>
-
- <!-- SpecificationBranchSet -->
-
- <securedutility
- class="lp.blueprints.model.specificationbranch.SpecificationBranchSet"
- provides="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet">
- <allow
- interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet"/>
- </securedutility>
- <facet
- facet="specifications">
-
- <!-- Specification -->
-
- <class
- class="lp.blueprints.model.specification.Specification">
- <allow
- interface="lp.blueprints.interfaces.specification.ISpecification"/>
-
- <!-- We allow any authenticated person to update the whiteboard -->
-
- <require
- permission="launchpad.AnyPerson"
- set_attributes="whiteboard"/>
-
- <!-- NB: goals and goalstatus are not to be set directly, it should
- only be set through the proposeGoal / acceptBy / declineBy
- methods
- -->
-
- <require
- permission="launchpad.Edit"
- set_attributes="name title summary definition_status specurl superseded_by milestone product distribution approver assignee drafter man_days implementation_status"/>
- <require
- permission="launchpad.Admin"
- set_attributes="priority direction_approved"/>
- <allow
- attributes="
- bugs
+ <lp:help-folder
+ folder="help" type="lp.blueprints.publisher.BlueprintsLayer" />
+
+ <!-- Sprint -->
+
+ <class
+ class="lp.blueprints.model.sprint.Sprint">
+ <allow
+ interface="lp.blueprints.interfaces.sprint.ISprint"/>
+ <require
+ permission="launchpad.AnyPerson"
+ set_schema="lp.blueprints.interfaces.sprint.ISprint"/>
+ </class>
+ <adapter
+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+ for="lp.blueprints.interfaces.sprint.ISprint"
+ factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
+ permission="zope.Public"/>
+ <adapter
+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+ for="lp.blueprints.interfaces.sprint.ISprintSet"
+ factory="lp.blueprints.browser.sprint.SprintSetBreadcrumb"
+ permission="zope.Public"/>
+ <adapter
+ provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
+ for="lp.blueprints.interfaces.specification.ISpecification"
+ factory="canonical.launchpad.webapp.breadcrumb.TitleBreadcrumb"
+ permission="zope.Public"/>
+
+ <!-- This is a view used to export data needed by the sprint scheduler.
+ As there are no API stability guarantees, the view name starts
+ with "temp" to discourage people from relying on it. -->
+
+ <!-- SprintSet -->
+
+ <class
+ class="lp.blueprints.model.sprint.SprintSet">
+ <allow interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
+ </class>
+ <securedutility
+ class="lp.blueprints.model.sprint.SprintSet"
+ provides="lp.blueprints.interfaces.sprint.ISprintSet">
+ <allow interface="lp.blueprints.interfaces.sprint.ISprintSet"/>
+ </securedutility>
+
+ <!-- SprintSpecification -->
+
+ <class
+ class="lp.blueprints.model.sprintspecification.SprintSpecification">
+ <allow
+ interface="lp.blueprints.interfaces.sprintspecification.ISprintSpecification"/>
+ <require
+ permission="launchpad.Edit"
+ set_attributes="whiteboard"/>
+ </class>
+
+ <!-- SpecificationDependency -->
+
+ <class class="lp.blueprints.model.specificationdependency.SpecificationDependency">
+ <allow interface="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
+ <require
+ permission="zope.Public"
+ set_schema="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"/>
+ </class>
+ <adapter
+ for="lp.blueprints.interfaces.specificationdependency.ISpecificationDependency"
+ factory="lp.blueprints.interfaces.specificationdependency.SpecDependencyIsAlsoRemoval"
+ provides="lp.blueprints.interfaces.specificationdependency.ISpecificationDependencyRemoval"/>
+
+ <!-- SpecificationSubscription -->
+
+ <class class="lp.blueprints.model.specificationsubscription.SpecificationSubscription">
+ <allow
+ interface="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription"/>
+ <require
+ permission="launchpad.Edit"
+ set_attributes="essential"/>
+ </class>
+ <subscriber
+ for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription
+ lazr.lifecycle.interfaces.IObjectCreatedEvent"
+ handler="canonical.launchpad.mailnotification.notify_specification_subscription_created"/>
+ <subscriber
+ for="lp.blueprints.interfaces.specificationsubscription.ISpecificationSubscription
+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
+ handler="canonical.launchpad.mailnotification.notify_specification_subscription_modified"/>
+
+ <!-- SpecificationFeedback -->
+
+ <class class="lp.blueprints.model.specificationfeedback.SpecificationFeedback">
+ <allow interface="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
+ <require
+ permission="zope.Public"
+ set_schema="lp.blueprints.interfaces.specificationfeedback.ISpecificationFeedback"/>
+ </class>
+
+ <!-- SprintAttendance -->
+
+ <class class="lp.blueprints.model.sprintattendance.SprintAttendance">
+ <allow interface="lp.blueprints.interfaces.sprintattendance.ISprintAttendance"/>
+ <require
+ permission="launchpad.Edit"
+ set_attributes="time_starts time_ends"/>
+ </class>
+
+ <!-- ISpecificationBranch -->
+
+ <class class="lp.blueprints.model.specificationbranch.SpecificationBranch">
+ <allow interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch"/>
+ <require
+ permission="launchpad.AnyPerson"
+ set_attributes="summary"/>
+ </class>
+ <subscriber
+ for="lp.blueprints.interfaces.specificationbranch.ISpecificationBranch
+ lazr.lifecycle.interfaces.IObjectCreatedEvent"
+ handler="canonical.launchpad.subscribers.karma.spec_branch_created"/>
+
+ <!-- SpecificationBranchSet -->
+
+ <securedutility
+ class="lp.blueprints.model.specificationbranch.SpecificationBranchSet"
+ provides="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet">
+ <allow interface="lp.blueprints.interfaces.specificationbranch.ISpecificationBranchSet"/>
+ </securedutility>
+
+ <!-- Specification -->
+
+ <class class="lp.blueprints.model.specification.Specification">
+ <allow interface="lp.blueprints.interfaces.specification.ISpecification"/>
+ <!-- We allow any authenticated person to update the whiteboard -->
+ <require
+ permission="launchpad.AnyPerson"
+ set_attributes="whiteboard"/>
+ <!-- NB: goals and goalstatus are not to be set directly, it should
+ only be set through the proposeGoal / acceptBy / declineBy
+ methods -->
+ <require
+ permission="launchpad.Edit"
+ set_attributes="name title summary definition_status specurl
+ superseded_by milestone product distribution
+ approver assignee drafter man_days
+ implementation_status"/>
+ <require
+ permission="launchpad.Admin"
+ set_attributes="priority direction_approved"/>
+ <allow
+ attributes="bugs
bug_links"/>
- <require
- permission="launchpad.AnyPerson"
- attributes="
- linkBug
+ <require
+ permission="launchpad.AnyPerson"
+ attributes="linkBug
unlinkBug"/>
- </class>
- <class
- class="lp.blueprints.model.specificationbug.SpecificationBug">
- <allow
- interface="lp.blueprints.interfaces.specificationbug.ISpecificationBug"/>
- </class>
- <subscriber
- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectCreatedEvent"
- handler="canonical.launchpad.subscribers.karma.spec_created"/>
- <subscriber
- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectModifiedEvent"
- handler="canonical.launchpad.subscribers.karma.spec_modified"/>
-
- <!-- these pages are simple enough they don't need browser code -->
-
-
- <!-- these pages require special browser code -->
-
-
- <!-- SpecificationSet -->
-
- <class
- class="lp.blueprints.model.specification.SpecificationSet">
- <allow
- interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
- </class>
- <securedutility
- class="lp.blueprints.model.specification.SpecificationSet"
- provides="lp.blueprints.interfaces.specification.ISpecificationSet">
- <allow
- interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
- </securedutility>
-
- <!-- SpecificationDelta -->
-
- <class
- class="lp.blueprints.adapters.SpecificationDelta">
- <allow
- interface="lp.blueprints.interfaces.specification.ISpecificationDelta"/>
- </class>
-
- <!-- SpecificationMessage -->
-
- <class
- class="lp.blueprints.model.specificationmessage.SpecificationMessage">
- <allow
- interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessage"/>
- </class>
-
- <!-- SpecificationMessageSet -->
-
- <class
- class="lp.blueprints.model.specificationmessage.SpecificationMessageSet">
- <allow
- interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
- </class>
- <securedutility
- class="lp.blueprints.model.specificationmessage.SpecificationMessageSet"
- provides="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet">
- <allow
- interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
- </securedutility>
- </facet>
- <subscriber
- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectModifiedEvent"
- handler="lp.blueprints.subscribers.specification_goalstatus"/>
- <subscriber
- for="lp.blueprints.interfaces.specification.ISpecification lazr.lifecycle.interfaces.IObjectModifiedEvent"
- handler="canonical.launchpad.mailnotification.notify_specification_modified"/>
- <adapter
- name="blueprints"
- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
- for="lp.blueprints.interfaces.specificationtarget.IHasSpecifications"
- factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
- permission="zope.Public"/>
- <adapter
- name="blueprints"
- provides="canonical.launchpad.webapp.interfaces.IBreadcrumb"
- for="lp.registry.interfaces.person.IPerson"
- factory="lp.blueprints.browser.specificationtarget.BlueprintsVHostBreadcrumb"
- permission="zope.Public"/>
+ </class>
+
+ <class class="lp.blueprints.model.specificationbug.SpecificationBug">
+ <allow interface="lp.blueprints.interfaces.specificationbug.ISpecificationBug"/>
+ </class>
+
+ <subscriber
+ for="lp.blueprints.interfaces.specification.ISpecification
+ lazr.lifecycle.interfaces.IObjectCreatedEvent"
+ handler="canonical.launchpad.subscribers.karma.spec_created"/>
+ <subscriber
+ for="lp.blueprints.interfaces.specification.ISpecification
+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
+ handler="canonical.launchpad.subscribers.karma.spec_modified"/>
+ <subscriber
+ for="lp.blueprints.interfaces.specification.ISpecification
+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
+ handler="lp.blueprints.subscribers.specification_update_lifecycle_status"/>
+ <subscriber
+ for="lp.blueprints.interfaces.specification.ISpecification
+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
+ handler="lp.blueprints.subscribers.specification_goalstatus"/>
+ <subscriber
+ for="lp.blueprints.interfaces.specification.ISpecification
+ lazr.lifecycle.interfaces.IObjectModifiedEvent"
+ handler="canonical.launchpad.mailnotification.notify_specification_modified"/>
+
+ <!-- SpecificationSet -->
+
+ <class class="lp.blueprints.model.specification.SpecificationSet">
+ <allow interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
+ </class>
+
+ <securedutility
+ class="lp.blueprints.model.specification.SpecificationSet"
+ provides="lp.blueprints.interfaces.specification.ISpecificationSet">
+ <allow interface="lp.blueprints.interfaces.specification.ISpecificationSet"/>
+ </securedutility>
+
+ <!-- SpecificationDelta -->
+
+ <class class="lp.blueprints.adapters.SpecificationDelta">
+ <allow interface="lp.blueprints.interfaces.specification.ISpecificationDelta"/>
+ </class>
+
+ <!-- SpecificationMessage -->
+
+ <class class="lp.blueprints.model.specificationmessage.SpecificationMessage">
+ <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessage"/>
+ </class>
+
+ <!-- SpecificationMessageSet -->
+
+ <class class="lp.blueprints.model.specificationmessage.SpecificationMessageSet">
+ <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
+ </class>
+
+ <securedutility
+ class="lp.blueprints.model.specificationmessage.SpecificationMessageSet"
+ provides="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet">
+ <allow interface="lp.blueprints.interfaces.specificationmessage.ISpecificationMessageSet"/>
+ </securedutility>
+
</configure>
=== modified file 'lib/lp/blueprints/interfaces/specification.py'
--- lib/lp/blueprints/interfaces/specification.py 2010-11-02 20:10:56 +0000
+++ lib/lp/blueprints/interfaces/specification.py 2010-11-05 19:52:03 +0000
@@ -41,6 +41,7 @@
SpecificationDefinitionStatus,
SpecificationGoalStatus,
SpecificationImplementationStatus,
+ SpecificationLifecycleStatus,
SpecificationPriority,
)
from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
@@ -337,6 +338,12 @@
'attribute, and also considers informational specs to be '
'started when they are approved.')
+ lifecycle_status = Choice(
+ title=_('Lifecycle Status'),
+ vocabulary=SpecificationLifecycleStatus,
+ default=SpecificationLifecycleStatus.NOTSTARTED,
+ readonly=True)
+
def retarget(product=None, distribution=None):
"""Retarget the spec to a new product or distribution. One of
product or distribution must be None (but not both).
=== modified file 'lib/lp/blueprints/model/specification.py'
--- lib/lp/blueprints/model/specification.py 2010-11-02 20:10:56 +0000
+++ lib/lp/blueprints/model/specification.py 2010-11-05 19:52:03 +0000
@@ -12,7 +12,6 @@
from lazr.lifecycle.event import (
ObjectCreatedEvent,
- ObjectDeletedEvent,
ObjectModifiedEvent,
)
from lazr.lifecycle.objectdelta import ObjectDelta
@@ -24,11 +23,7 @@
SQLRelatedJoin,
StringCol,
)
-from storm.expr import (
- LeftJoin,
- )
from storm.locals import (
- ClassAlias,
Desc,
SQL,
)
@@ -386,6 +381,16 @@
(self.definition_status ==
SpecificationDefinitionStatus.APPROVED)))
+ @property
+ def lifecycle_status(self):
+ """Combine the is_complete and is_started emergent properties."""
+ if self.is_complete:
+ return SpecificationLifecycleStatus.COMPLETE
+ elif self.is_started:
+ return SpecificationLifecycleStatus.STARTED
+ else:
+ return SpecificationLifecycleStatus.NOTSTARTED
+
def updateLifecycleStatus(self, user):
"""See ISpecification."""
newstatus = None
=== modified file 'lib/lp/blueprints/subscribers.py'
--- lib/lp/blueprints/subscribers.py 2010-11-01 03:43:58 +0000
+++ lib/lp/blueprints/subscribers.py 2010-11-05 19:52:03 +0000
@@ -18,3 +18,13 @@
return
if delta.productseries is not None or delta.distroseries is not None:
spec.goalstatus = SpecificationGoalStatus.PROPOSED
+
+
+def specification_update_lifecycle_status(spec, event):
+ """Mark the specification as started and/or complete if appropriate.
+
+ Does nothing if there is no user associated with the event.
+ """
+ if event.user is None:
+ return
+ spec.updateLifecycleStatus(IPerson(event.user))
=== modified file 'lib/lp/bugs/interfaces/bugtask.py'
--- lib/lp/bugs/interfaces/bugtask.py 2010-11-03 14:31:33 +0000
+++ lib/lp/bugs/interfaces/bugtask.py 2010-11-05 19:52:03 +0000
@@ -1188,8 +1188,8 @@
self.hardware_is_linked_to_bug = hardware_is_linked_to_bug
self.linked_branches = linked_branches
self.structural_subscriber = structural_subscriber
- self.modified_since = None
- self.created_since = None
+ self.modified_since = modified_since
+ self.created_since = created_since
def setProduct(self, product):
"""Set the upstream context on which to filter the search."""
=== modified file 'lib/lp/bugs/mail/bugnotificationrecipients.py'
--- lib/lp/bugs/mail/bugnotificationrecipients.py 2010-08-20 20:31:18 +0000
+++ lib/lp/bugs/mail/bugnotificationrecipients.py 2010-11-05 19:52:03 +0000
@@ -10,7 +10,7 @@
from zope.interface import implements
-from canonical.launchpad.interfaces import INotificationRecipientSet
+from canonical.launchpad.interfaces.launchpad import INotificationRecipientSet
from lp.services.mail.basemailer import RecipientReason
from lp.services.mail.notificationrecipientset import NotificationRecipientSet
=== modified file 'lib/lp/bugs/scripts/checkwatches/core.py'
--- lib/lp/bugs/scripts/checkwatches/core.py 2010-10-26 15:47:24 +0000
+++ lib/lp/bugs/scripts/checkwatches/core.py 2010-11-05 19:52:03 +0000
@@ -36,15 +36,7 @@
from zope.component import getUtility
from canonical.database.sqlbase import flush_database_updates
-from canonical.launchpad.interfaces import (
- CreateBugParams,
- IBugTrackerSet,
- IBugWatchSet,
- IDistribution,
- ILaunchpadCelebrities,
- IPersonSet,
- PersonCreationRationale,
- )
+from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
from canonical.launchpad.scripts.logger import log as default_log
from lp.bugs import externalbugtracker
from lp.bugs.externalbugtracker import (
@@ -52,6 +44,9 @@
BugWatchUpdateError,
UnknownBugTrackerTypeError,
)
+from lp.bugs.interfaces.bug import CreateBugParams
+from lp.bugs.interfaces.bugtracker import IBugTrackerSet
+from lp.bugs.interfaces.bugwatch import IBugWatchSet
from lp.bugs.scripts.checkwatches.base import (
commit_before,
with_interaction,
@@ -61,6 +56,11 @@
from lp.bugs.scripts.checkwatches.utilities import (
get_bugwatcherrortype_for_error,
)
+from lp.registry.interfaces.distribution import IDistribution
+from lp.registry.interfaces.person import (
+ IPersonSet,
+ PersonCreationRationale,
+ )
from lp.services.database.bulk import reload
from lp.services.scripts.base import LaunchpadCronScript
=== modified file 'lib/lp/bugs/scripts/checkwatches/scheduler.py'
--- lib/lp/bugs/scripts/checkwatches/scheduler.py 2010-10-03 15:30:06 +0000
+++ lib/lp/bugs/scripts/checkwatches/scheduler.py 2010-11-05 19:52:03 +0000
@@ -11,7 +11,7 @@
import transaction
from canonical.database.sqlbase import sqlvalues
-from canonical.launchpad.interfaces import IMasterStore
+from canonical.launchpad.interfaces.lpstorm import IMasterStore
from canonical.launchpad.utilities.looptuner import TunableLoop
from lp.bugs.interfaces.bugwatch import BUG_WATCH_ACTIVITY_SUCCESS_STATUSES
from lp.bugs.model.bugwatch import BugWatch
=== modified file 'lib/lp/bugs/tests/test_bugtask_search.py'
--- lib/lp/bugs/tests/test_bugtask_search.py 2010-10-29 13:00:57 +0000
+++ lib/lp/bugs/tests/test_bugtask_search.py 2010-11-05 19:52:03 +0000
@@ -25,6 +25,7 @@
from lp.bugs.interfaces.bugattachment import BugAttachmentType
from lp.bugs.interfaces.bugtask import (
+ BugBranchSearch,
BugTaskImportance,
BugTaskSearchParams,
BugTaskStatus,
@@ -53,29 +54,30 @@
super(SearchTestBase, self).setUp()
self.bugtask_set = getUtility(IBugTaskSet)
+ def assertSearchFinds(self, params, expected_bugtasks):
+ # Run a search for the given search parameters and check if
+ # the result matches the expected bugtasks.
+ search_result = self.runSearch(params)
+ expected = self.resultValuesForBugtasks(expected_bugtasks)
+ self.assertEqual(expected, search_result)
+
def test_search_all_bugtasks_for_target(self):
# BugTaskSet.search() returns all bug tasks for a given bug
# target, if only the bug target is passed as a search parameter.
params = self.getBugTaskSearchParams(user=None)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks)
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks)
def test_private_bug_in_search_result(self):
# Private bugs are not included in search results for anonymous users.
with person_logged_in(self.owner):
self.bugtasks[-1].bug.setPrivate(True, self.owner)
params = self.getBugTaskSearchParams(user=None)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks)[:-1]
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:-1])
# Private bugs are not included in search results for ordinary users.
user = self.factory.makePerson()
params = self.getBugTaskSearchParams(user=user)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks)[:-1]
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:-1])
# If the user is subscribed to the bug, it is included in the
# search result.
@@ -83,16 +85,12 @@
with person_logged_in(self.owner):
self.bugtasks[-1].bug.subscribe(user, self.owner)
params = self.getBugTaskSearchParams(user=user)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks)
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks)
# Private bugs are included in search results for admins.
admin = getUtility(IPersonSet).getByEmail('foo.bar@xxxxxxxxxxxxx')
params = self.getBugTaskSearchParams(user=admin)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks)
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks)
def test_search_by_bug_reporter(self):
# Search results can be limited to bugs filed by a given person.
@@ -100,9 +98,7 @@
reporter = bugtask.bug.owner
params = self.getBugTaskSearchParams(
user=None, bug_reporter=reporter)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks([bugtask])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, [bugtask])
def test_search_by_bug_commenter(self):
# Search results can be limited to bugs having a comment from a
@@ -118,9 +114,7 @@
expected.bug.newMessage(owner=commenter, content='a comment')
params = self.getBugTaskSearchParams(
user=None, bug_commenter=commenter)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks([expected])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, [expected])
def test_search_by_person_affected_by_bug(self):
# Search results can be limited to bugs which affect a given person.
@@ -130,9 +124,7 @@
expected.bug.markUserAffected(affected_user)
params = self.getBugTaskSearchParams(
user=None, affected_user=affected_user)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks([expected])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, [expected])
def test_search_by_bugtask_assignee(self):
# Search results can be limited to bugtask assigned to a given
@@ -142,9 +134,7 @@
with person_logged_in(assignee):
expected.transitionToAssignee(assignee)
params = self.getBugTaskSearchParams(user=None, assignee=assignee)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks([expected])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, [expected])
def test_search_by_bug_subscriber(self):
# Search results can be limited to bugs to which a given person
@@ -154,9 +144,69 @@
with person_logged_in(subscriber):
expected.bug.subscribe(subscriber, subscribed_by=subscriber)
params = self.getBugTaskSearchParams(user=None, subscriber=subscriber)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks([expected])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, [expected])
+
+ def subscribeToTarget(self, subscriber):
+ # Subscribe the given person to the search target.
+ with person_logged_in(subscriber):
+ self.searchtarget.addSubscription(
+ subscriber, subscribed_by=subscriber)
+
+ def _findBugtaskForOtherProduct(self, bugtask, main_product):
+ # Return the bugtask for the product that is not related to the
+ # main bug target.
+ #
+ # The default bugtasks of this test suite are created by
+ # ObjectFactory.makeBugTask() as follows:
+ # - a new bug is created having a new product as the target.
+ # - another bugtask is created for self.searchtarget (or,
+ # when self.searchtarget is a milestone, for the product
+ # of the milestone)
+ # This method returns the bug task for the product that is not
+ # related to the main bug target.
+ bug = bugtask.bug
+ for other_task in bug.bugtasks:
+ other_target = other_task.target
+ if (IProduct.providedBy(other_target)
+ and other_target != main_product):
+ return other_task
+ self.fail(
+ 'No bug task found for a product that is not the target of '
+ 'the main test bugtask.')
+
+ def findBugtaskForOtherProduct(self, bugtask):
+ # Return the bugtask for the product that is not related to the
+ # main bug target.
+ #
+ # This method must ober overridden for product related tests.
+ return self._findBugtaskForOtherProduct(bugtask, None)
+
+ def test_search_by_structural_subscriber(self):
+ # Search results can be limited to bugs with a bug target to which
+ # a given person has a structural subscription.
+ subscriber = self.factory.makePerson()
+ # If the given person is not subscribed, no bugtasks are returned.
+ params = self.getBugTaskSearchParams(
+ user=None, structural_subscriber=subscriber)
+ self.assertSearchFinds(params, [])
+ # When the person is subscribed, all bugtasks are returned.
+ self.subscribeToTarget(subscriber)
+ params = self.getBugTaskSearchParams(
+ user=None, structural_subscriber=subscriber)
+ self.assertSearchFinds(params, self.bugtasks)
+
+ # Searching for a structural subscriber does not return a bugtask,
+ # if the person is subscribed to another target than the main
+ # bug target.
+ other_subscriber = self.factory.makePerson()
+ other_bugtask = self.findBugtaskForOtherProduct(self.bugtasks[0])
+ other_target = other_bugtask.target
+ with person_logged_in(other_subscriber):
+ other_target.addSubscription(
+ other_subscriber, subscribed_by=other_subscriber)
+ params = self.getBugTaskSearchParams(
+ user=None, structural_subscriber=other_subscriber)
+ self.assertSearchFinds(params, [])
def test_search_by_bug_attachment(self):
# Search results can be limited to bugs having attachments of
@@ -171,23 +221,17 @@
# We can search for bugs with non-patch attachments...
params = self.getBugTaskSearchParams(
user=None, attachmenttype=BugAttachmentType.UNSPECIFIED)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:1])
# ... for bugs with patches...
params = self.getBugTaskSearchParams(
user=None, attachmenttype=BugAttachmentType.PATCH)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[1:2])
# and for bugs with patches or attachments
params = self.getBugTaskSearchParams(
user=None, attachmenttype=any(
BugAttachmentType.PATCH,
BugAttachmentType.UNSPECIFIED))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:2])
def setUpFullTextSearchTests(self):
# Set text fields indexed by Bug.fti, BugTask.fti or
@@ -205,40 +249,30 @@
self.setUpFullTextSearchTests()
params = self.getBugTaskSearchParams(
user=None, searchtext='one title')
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:1])
# ... by BugTask.fti ...
params = self.getBugTaskSearchParams(
user=None, searchtext='two explanation')
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[1:2])
# ...and by MessageChunk.fti
params = self.getBugTaskSearchParams(
user=None, searchtext='three comment')
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[2:3])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[2:3])
def test_fast_fulltext_search(self):
# Fast full text searches find text indexed by Bug.fti...
self.setUpFullTextSearchTests()
params = self.getBugTaskSearchParams(
user=None, fast_searchtext='one title')
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:1])
# ... but not text indexed by BugTask.fti ...
params = self.getBugTaskSearchParams(
user=None, fast_searchtext='two explanation')
- search_result = self.runSearch(params)
- self.assertEqual([], search_result)
+ self.assertSearchFinds(params, [])
# ..or by MessageChunk.fti
params = self.getBugTaskSearchParams(
user=None, fast_searchtext='three comment')
- search_result = self.runSearch(params)
- self.assertEqual([], search_result)
+ self.assertSearchFinds(params, [])
def test_has_no_upstream_bugtask(self):
# Search results can be limited to bugtasks of bugs that do
@@ -258,13 +292,13 @@
IDistributionSourcePackage.providedBy(self.searchtarget)):
if IDistribution.providedBy(self.searchtarget):
bug = self.factory.makeBug(distribution=self.searchtarget)
- expected = self.resultValuesForBugtasks([bug.default_bugtask])
+ expected = [bug.default_bugtask]
else:
bug = self.factory.makeBug(
distribution=self.searchtarget.distribution)
bugtask = self.factory.makeBugTask(
bug=bug, target=self.searchtarget)
- expected = self.resultValuesForBugtasks([bugtask])
+ expected = [bugtask]
else:
# Bugs without distribution related bugtasks have always at
# least one product related bugtask, hence a
@@ -273,23 +307,14 @@
expected = []
params = self.getBugTaskSearchParams(
user=None, has_no_upstream_bugtask=True)
- search_result = self.runSearch(params)
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, expected)
def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
# Change the status of another bugtask of the same bug to the
# given status.
- bug = bugtask.bug
- for other_task in bug.bugtasks:
- other_target = other_task.target
- if other_task != bugtask and IProduct.providedBy(other_target):
- with person_logged_in(other_target.owner):
- other_task.transitionToStatus(
- new_status, other_target.owner)
- return
- self.fail(
- 'No bug task found for a product that is not the target of '
- 'the main test bugtask.')
+ other_task = self.findBugtaskForOtherProduct(bugtask)
+ with person_logged_in(other_task.target.owner):
+ other_task.transitionToStatus(new_status, other_task.target.owner)
def test_upstream_status(self):
# Search results can be filtered by the status of an upstream
@@ -299,14 +324,11 @@
# with status NEW for the "other" product, hence all bug tasks
# will be returned in a search for bugs that are open upstream.
params = self.getBugTaskSearchParams(user=None, open_upstream=True)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks)
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks)
# A search for tasks resolved upstream does not yield any bugtask.
params = self.getBugTaskSearchParams(
user=None, resolved_upstream=True)
- search_result = self.runSearch(params)
- self.assertEqual([], search_result)
+ self.assertSearchFinds(params, [])
# But if we set upstream bug tasks to "fix committed" or "fix
# released", the related bug tasks for our test target appear in
# the search result.
@@ -314,14 +336,11 @@
self.bugtasks[0], BugTaskStatus.FIXCOMMITTED)
self.changeStatusOfBugTaskForOtherProduct(
self.bugtasks[1], BugTaskStatus.FIXRELEASED)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:2])
# A search for bug tasks open upstream now returns only one
# test task.
params = self.getBugTaskSearchParams(user=None, open_upstream=True)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
+ self.assertSearchFinds(params, self.bugtasks[2:])
def test_tags(self):
# Search results can be limited to bugs having given tags.
@@ -330,44 +349,31 @@
self.bugtasks[1].bug.tags = ['tag1', 'tag3']
params = self.getBugTaskSearchParams(
user=None, tag=any('tag2', 'tag3'))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:2])
params = self.getBugTaskSearchParams(
user=None, tag=all('tag2', 'tag3'))
- search_result = self.runSearch(params)
- self.assertEqual([], search_result)
+ self.assertSearchFinds(params, [])
params = self.getBugTaskSearchParams(
user=None, tag=all('tag1', 'tag3'))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[1:2])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[1:2])
params = self.getBugTaskSearchParams(
user=None, tag=all('tag1', '-tag3'))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:1])
params = self.getBugTaskSearchParams(
user=None, tag=all('-tag1'))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[2:])
params = self.getBugTaskSearchParams(
user=None, tag=all('*'))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:2])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:2])
params = self.getBugTaskSearchParams(
user=None, tag=all('-*'))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[2:])
def test_date_closed(self):
# Search results can be filtered by the date_closed time
@@ -379,13 +385,106 @@
self.assertTrue(utc_now >= self.bugtasks[2].date_closed)
params = self.getBugTaskSearchParams(
user=None, date_closed=greater_than(utc_now-timedelta(days=1)))
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[2:])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[2:])
params = self.getBugTaskSearchParams(
user=None, date_closed=greater_than(utc_now+timedelta(days=1)))
- search_result = self.runSearch(params)
- self.assertEqual([], search_result)
+ self.assertSearchFinds(params, [])
+
+ def test_created_since(self):
+ # Search results can be limited to bugtasks created after a
+ # given time.
+ one_day_ago = self.bugtasks[0].datecreated - timedelta(days=1)
+ two_days_ago = self.bugtasks[0].datecreated - timedelta(days=2)
+ with person_logged_in(self.owner):
+ self.bugtasks[0].datecreated = two_days_ago
+ params = self.getBugTaskSearchParams(
+ user=None, created_since=one_day_ago)
+ self.assertSearchFinds(params, self.bugtasks[1:])
+
+ def test_modified_since(self):
+ # Search results can be limited to bugs modified after a
+ # given time.
+ one_day_ago = (
+ self.bugtasks[0].bug.date_last_updated - timedelta(days=1))
+ two_days_ago = (
+ self.bugtasks[0].bug.date_last_updated - timedelta(days=2))
+ with person_logged_in(self.owner):
+ self.bugtasks[0].bug.date_last_updated = two_days_ago
+ params = self.getBugTaskSearchParams(
+ user=None, modified_since=one_day_ago)
+ self.assertSearchFinds(params, self.bugtasks[1:])
+
+ def test_branches_linked(self):
+ # Search results can be limited to bugs with or without linked
+ # branches.
+ with person_logged_in(self.owner):
+ branch = self.factory.makeBranch()
+ self.bugtasks[0].bug.linkBranch(branch, self.owner)
+ params = self.getBugTaskSearchParams(
+ user=None, linked_branches=BugBranchSearch.BUGS_WITH_BRANCHES)
+ self.assertSearchFinds(params, self.bugtasks[:1])
+ params = self.getBugTaskSearchParams(
+ user=None, linked_branches=BugBranchSearch.BUGS_WITHOUT_BRANCHES)
+ self.assertSearchFinds(params, self.bugtasks[1:])
+
+ def test_limit_search_to_one_bug(self):
+ # Search results can be limited to a given bug.
+ params = self.getBugTaskSearchParams(
+ user=None, bug=self.bugtasks[0].bug)
+ self.assertSearchFinds(params, self.bugtasks[:1])
+ other_bug = self.factory.makeBug()
+ params = self.getBugTaskSearchParams(user=None, bug=other_bug)
+ self.assertSearchFinds(params, [])
+
+ def test_filter_by_status(self):
+ # Search results can be limited to bug tasks with a given status.
+ params = self.getBugTaskSearchParams(
+ user=None, status=BugTaskStatus.FIXCOMMITTED)
+ self.assertSearchFinds(params, self.bugtasks[2:])
+ params = self.getBugTaskSearchParams(
+ user=None, status=any(BugTaskStatus.NEW, BugTaskStatus.TRIAGED))
+ self.assertSearchFinds(params, self.bugtasks[:2])
+ params = self.getBugTaskSearchParams(
+ user=None, status=BugTaskStatus.WONTFIX)
+ self.assertSearchFinds(params, [])
+
+ def test_filter_by_importance(self):
+ # Search results can be limited to bug tasks with a given importance.
+ params = self.getBugTaskSearchParams(
+ user=None, importance=BugTaskImportance.HIGH)
+ self.assertSearchFinds(params, self.bugtasks[:1])
+ params = self.getBugTaskSearchParams(
+ user=None,
+ importance=any(BugTaskImportance.HIGH, BugTaskImportance.LOW))
+ self.assertSearchFinds(params, self.bugtasks[:2])
+ params = self.getBugTaskSearchParams(
+ user=None, importance=BugTaskImportance.MEDIUM)
+ self.assertSearchFinds(params, [])
+
+ def test_omit_duplicate_bugs(self):
+ # Duplicate bugs can optionally be excluded from search results.
+ # The default behaviour is to include duplicates.
+ duplicate_bug = self.bugtasks[0].bug
+ master_bug = self.bugtasks[1].bug
+ with person_logged_in(self.owner):
+ duplicate_bug.markAsDuplicate(master_bug)
+ params = self.getBugTaskSearchParams(user=None)
+ self.assertSearchFinds(params, self.bugtasks)
+ # If we explicitly pass the parameter omit_duplicates=False, we get
+ # the same result.
+ params = self.getBugTaskSearchParams(user=None, omit_dupes=False)
+ self.assertSearchFinds(params, self.bugtasks)
+ # If omit_duplicates is set to True, the first task bug is omitted.
+ params = self.getBugTaskSearchParams(user=None, omit_dupes=True)
+ self.assertSearchFinds(params, self.bugtasks[1:])
+
+ def test_has_cve(self):
+ # Search results can be limited to bugs linked to a CVE.
+ with person_logged_in(self.owner):
+ cve = self.factory.makeCVE('2010-0123')
+ self.bugtasks[0].bug.linkCVE(cve, self.owner)
+ params = self.getBugTaskSearchParams(user=None, has_cve=True)
+ self.assertSearchFinds(params, self.bugtasks[:1])
class ProductAndDistributionTests:
@@ -405,9 +504,7 @@
self.bugtasks[0].bug.addNomination(nominator, series1)
self.bugtasks[1].bug.addNomination(nominator, series2)
params = self.getBugTaskSearchParams(user=None, nominated_for=series1)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks[:1])
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks[:1])
class BugTargetTestBase:
@@ -445,15 +542,12 @@
supervisor = self.factory.makeTeam(owner=self.owner)
params = self.getBugTaskSearchParams(
user=None, bug_supervisor=supervisor)
- search_result = self.runSearch(params)
- self.assertEqual([], search_result)
+ self.assertSearchFinds(params, [])
# If we appoint a bug supervisor, searching for bug tasks
# by supervisor will return all bugs for our test target.
self.setSupervisor(supervisor)
- search_result = self.runSearch(params)
- expected = self.resultValuesForBugtasks(self.bugtasks)
- self.assertEqual(expected, search_result)
+ self.assertSearchFinds(params, self.bugtasks)
def setSupervisor(self, supervisor):
"""Set the bug supervisor for the bug task target."""
@@ -484,6 +578,11 @@
"""See `ProductAndDistributionTests`."""
return self.factory.makeProductSeries(product=self.searchtarget)
+ def findBugtaskForOtherProduct(self, bugtask):
+ # Return the bugtask for the product that is not related to the
+ # main bug target.
+ return self._findBugtaskForOtherProduct(bugtask, self.searchtarget)
+
class ProductSeriesTarget(BugTargetTestBase):
"""Use a product series as the bug target."""
@@ -503,6 +602,31 @@
params.setProductSeries(self.searchtarget)
return params
+ def changeStatusOfBugTaskForOtherProduct(self, bugtask, new_status):
+ # Change the status of another bugtask of the same bug to the
+ # given status.
+ #
+ # This method is called by SearchTestBase.test_upstream_status().
+ # A search for bugs which are open or closed upstream has an
+ # odd behaviour when the search target is a product series: In
+ # this case, all bugs with an open or closed bug task for _any_
+ # product are returned, including bug tasks for the main product
+ # of the series. Hence we must set the status for all products
+ # in order to avoid a failure of test_upstream_status().
+ bug = bugtask.bug
+ for other_task in bugtask.related_tasks:
+ other_target = other_task.target
+ if IProduct.providedBy(other_target):
+ with person_logged_in(other_target.owner):
+ other_task.transitionToStatus(
+ new_status, other_target.owner)
+
+ def findBugtaskForOtherProduct(self, bugtask):
+ # Return the bugtask for the product that not related to the
+ # main bug target.
+ return self._findBugtaskForOtherProduct(
+ bugtask, self.searchtarget.product)
+
class ProjectGroupTarget(BugTargetTestBase, BugTargetWithBugSuperVisor):
"""Use a project group as the bug target."""
@@ -525,8 +649,10 @@
def makeBugTasks(self):
"""Create bug tasks for the search target."""
self.bugtasks = []
+ self.products = []
with person_logged_in(self.owner):
product = self.factory.makeProduct(owner=self.owner)
+ self.products.append(product)
product.project = self.searchtarget
self.bugtasks.append(
self.factory.makeBugTask(target=product))
@@ -535,6 +661,7 @@
BugTaskStatus.TRIAGED, self.owner)
product = self.factory.makeProduct(owner=self.owner)
+ self.products.append(product)
product.project = self.searchtarget
self.bugtasks.append(
self.factory.makeBugTask(target=product))
@@ -543,6 +670,7 @@
BugTaskStatus.NEW, self.owner)
product = self.factory.makeProduct(owner=self.owner)
+ self.products.append(product)
product.project = self.searchtarget
self.bugtasks.append(
self.factory.makeBugTask(target=product))
@@ -557,6 +685,19 @@
for bugtask in self.bugtasks:
bugtask.target.setBugSupervisor(supervisor, self.owner)
+ def findBugtaskForOtherProduct(self, bugtask):
+ # Return the bugtask for the product that not related to the
+ # main bug target.
+ bug = bugtask.bug
+ for other_task in bug.bugtasks:
+ other_target = other_task.target
+ if (IProduct.providedBy(other_target)
+ and other_target not in self.products):
+ return other_task
+ self.fail(
+ 'No bug task found for a product that is not the target of '
+ 'the main test bugtask.')
+
class MilestoneTarget(BugTargetTestBase):
"""Use a milestone as the bug target."""
@@ -583,6 +724,11 @@
for bugtask in self.bugtasks:
bugtask.transitionToMilestone(self.searchtarget, self.owner)
+ def findBugtaskForOtherProduct(self, bugtask):
+ # Return the bugtask for the product that not related to the
+ # main bug target.
+ return self._findBugtaskForOtherProduct(bugtask, self.product)
+
class DistributionTarget(BugTargetTestBase, ProductAndDistributionTests,
BugTargetWithBugSuperVisor):
@@ -645,6 +791,14 @@
params.setSourcePackage(self.searchtarget)
return params
+ def subscribeToTarget(self, subscriber):
+ # Subscribe the given person to the search target.
+ # Source packages do not support structural subscriptions,
+ # so we subscribe to the distro series instead.
+ with person_logged_in(subscriber):
+ self.searchtarget.distroseries.addSubscription(
+ subscriber, subscribed_by=subscriber)
+
class DistributionSourcePackageTarget(BugTargetTestBase,
BugTargetWithBugSuperVisor):
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2010-11-04 19:59:02 +0000
+++ lib/lp/code/browser/branch.py 2010-11-05 19:52:03 +0000
@@ -140,6 +140,7 @@
from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
+from lp.code.interfaces.sourcepackagerecipe import recipes_enabled
from lp.registry.interfaces.person import (
IPerson,
IPersonSet,
@@ -382,7 +383,7 @@
'+upgrade', 'Upgrade this branch', icon='edit', enabled=enabled)
def create_recipe(self):
- if not self.context.private and config.build_from_branch.enabled:
+ if not self.context.private and recipes_enabled():
enabled = True
else:
enabled = False
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2010-10-26 21:37:42 +0000
+++ lib/lp/code/browser/configure.zcml 2010-11-05 19:52:03 +0000
@@ -1262,7 +1262,7 @@
permission="launchpad.AnyPerson"
facet="branches"
name="+new-recipe"
- template="../../app/templates/generic-edit.pt"/>
+ template="../templates/sourcepackagerecipe-new.pt"/>
<browser:page
for="lp.code.interfaces.sourcepackagerecipe.ISourcePackageRecipe"
layer="lp.code.publisher.CodeLayer"
=== modified file 'lib/lp/code/browser/sourcepackagerecipelisting.py'
--- lib/lp/code/browser/sourcepackagerecipelisting.py 2010-08-23 02:07:45 +0000
+++ lib/lp/code/browser/sourcepackagerecipelisting.py 2010-11-05 19:52:03 +0000
@@ -20,6 +20,7 @@
Link,
)
from lp.code.interfaces.branch import IBranch
+from lp.code.interfaces.sourcepackagerecipe import recipes_enabled
from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.product import IProduct
@@ -32,7 +33,7 @@
enabled = False
if self.context.getRecipes().count():
enabled = True
- if not config.build_from_branch.enabled:
+ if not recipes_enabled():
enabled = False
return Link(
'+recipes', text, icon='info', enabled=enabled, site='code')
=== modified file 'lib/lp/code/browser/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-10-29 00:06:49 +0000
+++ lib/lp/code/browser/tests/test_sourcepackagerecipe.py 2010-11-05 19:52:03 +0000
@@ -121,7 +121,7 @@
browser.getControl(name='field.name').value = 'daily'
browser.getControl('Description').value = 'Make some food!'
browser.getControl('Secret Squirrel').click()
- browser.getControl('Build daily').click()
+ browser.getControl('Automatically build each day').click()
browser.getControl('Create Recipe').click()
pattern = """\
@@ -184,7 +184,7 @@
browser.getControl(name='field.name').value = 'daily'
browser.getControl('Description').value = 'Make some food!'
browser.getControl('Secret Squirrel').click()
- browser.getControl('Build daily').click()
+ browser.getControl('Automatically build each day').click()
browser.getControl('Owner').displayValue = ['Good Chefs']
browser.getControl('Create Recipe').click()
@@ -279,7 +279,7 @@
browser.getControl(name='field.name').value = 'daily'
browser.getControl('Description').value = 'Make some food!'
- browser.getControl('Build daily').click()
+ browser.getControl('Automatically build each day').click()
browser.getControl('Create Recipe').click()
self.assertEqual(
extract_text(find_tags_by_class(browser.contents, 'message')[2]),
=== added file 'lib/lp/code/help/related-recipes.html'
--- lib/lp/code/help/related-recipes.html 1970-01-01 00:00:00 +0000
+++ lib/lp/code/help/related-recipes.html 2010-11-05 19:52:03 +0000
@@ -0,0 +1,38 @@
+<html>
+ <head>
+ <title>Related source package recipes</title>
+ <link rel="stylesheet" type="text/css"
+ href="/+icing/yui/cssreset/reset.css" />
+ <link rel="stylesheet" type="text/css"
+ href="/+icing/yui/cssfonts/fonts.css" />
+ <link rel="stylesheet" type="text/css"
+ href="/+icing/yui/cssbase/base.css" />
+ </head>
+ <body>
+ <h1>Related source package recipes</h1>
+
+ <p>
+ You can ask Launchpad to make an automatic daily build
+ of the code in this branch and place the resultant package(s)
+ in your chosen PPA. (<a href="https://help.launchpad.net/Packaging/SourceBuilds" target="_blank">Read more</a>)
+ </p>
+
+ <p>A "recipe" is a description of the steps Launchpad's package builder
+ should take to construct a source package from a set of Bazaar branches
+ that you specify.
+ </p>
+
+ <p>
+ Using a recipe, you tell Launchpad:
+ </p>
+ <ul class="bulleted">
+ <li>in which branch to find the code</li>
+ <li>where to find the packaging information — e.g. in a separate
+ branch or in an existing Ubuntu package branch</li>
+ <li>the correct package version (so users will still be able to upgrade
+ to the stable version of the distro once it gets released)</li>
+ <li>any modifications necessary to make the source build properly</li>
+ </ul>
+
+ </body>
+</html>
=== modified file 'lib/lp/code/interfaces/sourcepackagerecipe.py'
--- lib/lp/code/interfaces/sourcepackagerecipe.py 2010-09-08 15:29:51 +0000
+++ lib/lp/code/interfaces/sourcepackagerecipe.py 2010-11-05 19:52:03 +0000
@@ -14,6 +14,7 @@
'ISourcePackageRecipeData',
'ISourcePackageRecipeSource',
'MINIMAL_RECIPE_TEXT',
+ 'recipes_enabled',
]
@@ -45,12 +46,14 @@
TextLine,
)
+from canonical.config import config
from canonical.launchpad import _
from canonical.launchpad.validators.name import name_validator
from lp.code.interfaces.branch import IBranch
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.role import IHasOwner
+from lp.services import features
from lp.services.fields import (
PersonChoice,
PublicPersonChoice,
@@ -168,7 +171,8 @@
" build a source package for"),
readonly=False)
build_daily = exported(Bool(
- title=_("Build daily")))
+ title=_("Automatically build each day, if the source has changed"),
+ description=_("You can manually request a build at any time.")))
name = exported(TextLine(
title=_("Name"), required=True,
@@ -208,3 +212,13 @@
def exists(owner, name):
"""Check to see if a recipe by the same name and owner exists."""
+
+
+def recipes_enabled():
+ """Return True if recipes are enabled."""
+ # Features win:
+ if features.getFeatureFlag(u'code.recipes_enabled'):
+ return True
+ if not config.build_from_branch.enabled:
+ return False
+ return True
=== modified file 'lib/lp/code/model/sourcepackagerecipe.py'
--- lib/lp/code/model/sourcepackagerecipe.py 2010-09-02 15:56:16 +0000
+++ lib/lp/code/model/sourcepackagerecipe.py 2010-11-05 19:52:03 +0000
@@ -46,6 +46,7 @@
ISourcePackageRecipe,
ISourcePackageRecipeData,
ISourcePackageRecipeSource,
+ recipes_enabled,
)
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuildSource,
@@ -215,7 +216,7 @@
def requestBuild(self, archive, requester, distroseries, pocket,
manual=False):
"""See `ISourcePackageRecipe`."""
- if not config.build_from_branch.enabled:
+ if not recipes_enabled():
raise ValueError('Source package recipe builds disabled.')
if not archive.is_ppa:
raise NonPPABuildRequest
=== modified file 'lib/lp/code/model/tests/test_sourcepackagerecipe.py'
--- lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-10-27 14:20:21 +0000
+++ lib/lp/code/model/tests/test_sourcepackagerecipe.py 2010-11-05 19:52:03 +0000
@@ -41,6 +41,7 @@
ISourcePackageRecipe,
ISourcePackageRecipeSource,
MINIMAL_RECIPE_TEXT,
+ recipes_enabled,
)
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuild,
@@ -65,10 +66,12 @@
)
from lp.testing import (
ANONYMOUS,
+ feature_flags,
launchpadlib_for,
login,
login_person,
person_logged_in,
+ set_feature_flag,
TestCaseWithFactory,
ws_object,
)
@@ -79,6 +82,10 @@
layer = DatabaseFunctionalLayer
+ def setUp(self):
+ super(TestSourcePackageRecipe, self).setUp()
+ self.useContext(feature_flags())
+
def test_implements_interface(self):
"""SourcePackageRecipe implements ISourcePackageRecipe."""
recipe = self.factory.makeSourcePackageRecipe()
@@ -347,6 +354,15 @@
ValueError, recipe.requestBuild, ppa, ppa.owner, distroseries,
PackagePublishingPocket.RELEASE)
+ def test_recipes_enabled_config(self):
+ self.pushConfig('build_from_branch', enabled=False)
+ self.assertFalse(recipes_enabled())
+
+ def test_recipes_enabled_flag(self):
+ self.pushConfig('build_from_branch', enabled=False)
+ set_feature_flag(u'code.recipes_enabled', u'on')
+ self.assertTrue(recipes_enabled())
+
def test_requestBuildRejectsOverQuota(self):
"""Build requests that exceed quota raise an exception."""
requester = self.factory.makePerson(name='requester')
=== modified file 'lib/lp/code/templates/branch-index.pt'
--- lib/lp/code/templates/branch-index.pt 2010-10-19 01:24:36 +0000
+++ lib/lp/code/templates/branch-index.pt 2010-11-05 19:52:03 +0000
@@ -111,7 +111,7 @@
replace="structure context/@@++branch-pending-merges" />
<tal:branch-recipes
replace="structure context/@@++branch-recipes"
- condition="modules/canonical.config/config/build_from_branch/enabled"
+ condition="modules/lp.code.interfaces.sourcepackagerecipe/recipes_enabled"
/>
<tal:related-bugs-specs
replace="structure context/@@++branch-related-bugs-specs" />
=== modified file 'lib/lp/code/templates/branch-recipes.pt'
--- lib/lp/code/templates/branch-recipes.pt 2010-07-21 13:22:25 +0000
+++ lib/lp/code/templates/branch-recipes.pt 2010-11-05 19:52:03 +0000
@@ -15,6 +15,9 @@
</a>
using this branch.
+ <a href="/+help/related-recipes.html" target="help" class="sprite maybe">
+ <span class="invisible-link">Tag help</span>
+ </a>
</div>
<span
=== added file 'lib/lp/code/templates/sourcepackagerecipe-new.pt'
--- lib/lp/code/templates/sourcepackagerecipe-new.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/code/templates/sourcepackagerecipe-new.pt 2010-11-05 19:52:03 +0000
@@ -0,0 +1,29 @@
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ metal:use-macro="view/macro:page/main_only"
+ i18n:domain="launchpad">
+ <body>
+ <div metal:fill-slot="main">
+
+ <div>
+ <p>A "recipe" is a description of the steps bzr-builder should take to
+ construct a source package from the various bzr branches. Its format
+ specifies:</p>
+ <ul class="bulleted">
+ <li>where to use the code from (trunk branch, beta branch, etc.), where to get the packaging from (separate branch? ubuntu branch?)</li>
+ <li>the correct package version (so users will still be able to upgrade to the stable version of the distro once it gets released)</li>
+ <li>what to modify to make the source build properly</li>
+ </ul>
+
+ <p>We strongly recommend that you test your recipe locally first.
+ <a href="https://help.launchpad.net/Packaging/SourceBuilds/GettingStarted">Read more...</a></p>
+
+ </div>
+
+ <div metal:use-macro="context/@@launchpad_form/form" />
+ </div>
+ </body>
+</html>
=== modified file 'lib/lp/coop/answersbugs/model.py'
--- lib/lp/coop/answersbugs/model.py 2010-10-03 15:30:06 +0000
+++ lib/lp/coop/answersbugs/model.py 2010-11-05 19:52:03 +0000
@@ -13,7 +13,7 @@
from zope.interface import implements
from canonical.database.sqlbase import SQLBase
-from canonical.launchpad.interfaces import IQuestionBug
+from lp.coop.answersbugs.interfaces import IQuestionBug
class QuestionBug(SQLBase):
=== modified file 'lib/lp/registry/interfaces/distroseries.py'
--- lib/lp/registry/interfaces/distroseries.py 2010-11-02 06:21:58 +0000
+++ lib/lp/registry/interfaces/distroseries.py 2010-11-05 19:52:03 +0000
@@ -810,7 +810,6 @@
description=copy_field(description, required=False),
version=copy_field(version, required=False),
distribution=copy_field(distribution, required=False),
- status=copy_field(status, required=False),
architectures=List(
title=_("The list of architectures to copy to the derived "
"distroseries."), value_type=TextLine(),
@@ -827,7 +826,7 @@
@call_with(user=REQUEST_USER)
@export_write_operation()
def deriveDistroSeries(user, name, displayname, title, summary,
- description, version, distribution, status,
+ description, version, distribution,
architectures, packagesets, rebuild):
"""Derive a distroseries from this one.
@@ -851,9 +850,6 @@
:param distribution: The distribution the derived series will
belong to. If it isn't specified this distroseries'
distribution is used.
- :param status: The status the new distroseries will be created
- in. If the distroseries isn't specified, this parameter will
- be ignored. Defaults to FROZEN.
:param architectures: The architectures to copy to the derived
series. If not specified, all of the architectures are copied.
:param packagesets: The packagesets to copy to the derived series.
=== modified file 'lib/lp/registry/interfaces/person.py'
--- lib/lp/registry/interfaces/person.py 2010-11-04 19:59:02 +0000
+++ lib/lp/registry/interfaces/person.py 2010-11-05 19:52:03 +0000
@@ -1077,8 +1077,11 @@
since it inherits from `IPerson`) is a member of himself
(i.e. `person1.inTeam(person1)`).
- :param team: An object providing `IPerson`, the name of a
- team, or `None` (in which case `False` is returned).
+ :param team: One of an object providing `IPerson`, the string name of a
+ team or `None`. If a string was supplied the team is looked up.
+ :return: A bool with the result of the membership lookup. When looking
+ up the team from a string finds nothing or team was `None` then
+ `False` is returned.
"""
def clearInTeamCache():
=== modified file 'lib/lp/registry/model/distroseries.py'
--- lib/lp/registry/model/distroseries.py 2010-11-03 17:47:52 +0000
+++ lib/lp/registry/model/distroseries.py 2010-11-05 19:52:03 +0000
@@ -1890,8 +1890,7 @@
def deriveDistroSeries(self, user, name, distribution=None,
displayname=None, title=None, summary=None,
description=None, version=None,
- status=SeriesStatus.FROZEN, architectures=(),
- packagesets=(), rebuild=False):
+ architectures=(), packagesets=(), rebuild=False):
"""See `IDistroSeries`."""
# XXX StevenK bug=643369 This should be in the security adapter
# This should be allowed if the user is a driver for self.parent
@@ -1925,7 +1924,6 @@
name=name, displayname=displayname, title=title,
summary=summary, description=description,
version=version, parent_series=self, owner=user)
- child.status = status
IStore(self).add(child)
else:
if child.parent_series is not self:
=== modified file 'lib/lp/registry/model/person.py'
--- lib/lp/registry/model/person.py 2010-11-04 19:59:02 +0000
+++ lib/lp/registry/model/person.py 2010-11-05 19:52:03 +0000
@@ -1191,6 +1191,9 @@
# Translate the team name to an ITeam if we were passed a team.
if isinstance(team, (str, unicode)):
team = PersonSet().getByName(team)
+ if team is None:
+ # No team, no membership.
+ return False
if self.id == team.id:
# A team is always a member of itself.
=== modified file 'lib/lp/registry/stories/webservice/xx-derivedistroseries.txt'
--- lib/lp/registry/stories/webservice/xx-derivedistroseries.txt 2010-10-28 19:46:28 +0000
+++ lib/lp/registry/stories/webservice/xx-derivedistroseries.txt 2010-11-05 19:52:03 +0000
@@ -13,9 +13,9 @@
>>> soyuz = factory.makeTeam(name='soyuz-team')
>>> parent = factory.makeDistroSeries()
>>> child = factory.makeDistroSeries(name='child1', parent_series=parent)
- >>> second_child = factory.makeDistroSeries(name='child2',
- ... parent_series=parent)
>>> other = factory.makeDistroSeries()
+ >>> distribution = factory.makeDistribution(name='deribuntu', owner=soyuz)
+ >>> version = "%s.0" % factory.getUniqueInteger()
>>> logout()
>>> from canonical.launchpad.testing.pages import webservice_for_person
>>> from canonical.launchpad.webapp.interfaces import OAuthPermission
@@ -60,10 +60,13 @@
If we call it with all of the arguments, it also works.
- >>> child_name = second_child.name
+ >>> deribuntu = webservice.get('/deribuntu').jsonBody()
+ >>> text = 'The Second Child'
>>> derived = soyuz_webservice.named_post(
... series['self_link'], 'deriveDistroSeries', {},
- ... name=child_name, architectures=('i386',),
+ ... name='child2', displayname=text, title=text, summary=text,
+ ... description=text, version=version,
+ ... distribution=deribuntu['self_link'], architectures=('i386',),
... packagesets=('test1',), rebuild=False)
>>> print derived
HTTP/1.1 200 Ok
=== modified file 'lib/lp/registry/tests/test_person.py'
--- lib/lp/registry/tests/test_person.py 2010-11-03 22:37:55 +0000
+++ lib/lp/registry/tests/test_person.py 2010-11-05 19:52:03 +0000
@@ -223,6 +223,13 @@
{},
removeSecurityProxy(self.user)._inTeam_cache)
+ def test_inTeam_person_string_missing_team(self):
+ # If a check against a string is done, the team lookup is implicit:
+ # treat a missing team as an empty team so that any pages that choose
+ # to do this don't blow up unnecessarily. Similarly feature flags
+ # team: scopes depend on this.
+ self.assertFalse(self.user.inTeam('does-not-exist'))
+
class TestPerson(TestCaseWithFactory):
=== modified file 'lib/lp/scripts/garbo.py'
--- lib/lp/scripts/garbo.py 2010-10-17 22:51:50 +0000
+++ lib/lp/scripts/garbo.py 2010-11-05 19:52:03 +0000
@@ -43,8 +43,8 @@
)
from canonical.launchpad.database.oauth import OAuthNonce
from canonical.launchpad.database.openidconsumer import OpenIDConsumerNonce
-from canonical.launchpad.interfaces import IMasterStore
from canonical.launchpad.interfaces.emailaddress import EmailAddressStatus
+from canonical.launchpad.interfaces.lpstorm import IMasterStore
from canonical.launchpad.utilities.looptuner import TunableLoop
from canonical.launchpad.webapp.interfaces import (
IStoreSelector,
=== modified file 'lib/lp/services/features/tests/test_webapp.py'
--- lib/lp/services/features/tests/test_webapp.py 2010-09-13 00:55:15 +0000
+++ lib/lp/services/features/tests/test_webapp.py 2010-11-05 19:52:03 +0000
@@ -7,7 +7,11 @@
from canonical.testing import layers
from lp.services.features import webapp
-from lp.testing import TestCase
+from lp.testing import (
+ login_as,
+ TestCase,
+ TestCaseWithFactory,
+ )
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
@@ -38,4 +42,23 @@
scopes = webapp.ScopesFromRequest(request)
self.assertTrue(scopes.lookup('pageid:'))
self.assertFalse(scopes.lookup('pageid:foo'))
- self.assertFalse(scopes.lookup('pageid:foo'))
+ self.assertFalse(scopes.lookup('pageid:foo:bar'))
+
+
+class TestDBScopes(TestCaseWithFactory):
+
+ layer = layers.DatabaseFunctionalLayer
+
+ def test_team_scope_outside_team(self):
+ request = LaunchpadTestRequest()
+ scopes = webapp.ScopesFromRequest(request)
+ self.factory.loginAsAnyone()
+ self.assertFalse(scopes.lookup('team:nonexistent'))
+
+ def test_team_scope_in_team(self):
+ request = LaunchpadTestRequest()
+ scopes = webapp.ScopesFromRequest(request)
+ member = self.factory.makePerson()
+ team = self.factory.makeTeam(members=[member])
+ login_as(member)
+ self.assertTrue(scopes.lookup('team:%s' % team.name))
=== modified file 'lib/lp/services/features/webapp.py'
--- lib/lp/services/features/webapp.py 2010-10-25 17:29:07 +0000
+++ lib/lp/services/features/webapp.py 2010-11-05 19:52:03 +0000
@@ -7,7 +7,10 @@
__metaclass__ = type
+from zope.component import getUtility
+
import canonical.config
+from canonical.launchpad.webapp.interfaces import ILaunchBag
from lp.services.features import per_thread
from lp.services.features.flags import FeatureController
from lp.services.features.rulesource import StormFeatureRuleSource
@@ -34,11 +37,16 @@
- pageid:SomeType
- pageid:SomeType:+view
- pageid:SomeType:+view#subselector
+ - team:
+ This scope looks up a team. For instance
+ - team:launchpad-beta-users
"""
if scope_name == 'default':
return True
if scope_name.startswith('pageid:'):
return self._lookup_pageid(scope_name[len('pageid:'):])
+ if scope_name.startswith('team:'):
+ return self._lookup_team(scope_name[len('team:'):])
parts = scope_name.split('.')
if len(parts) == 2:
if parts[0] == 'server':
@@ -65,6 +73,23 @@
return False
return True
+ def _lookup_team(self, team_name):
+ """Lookup a team membership as a scope.
+
+ This will do a two queries, so we probably want to keep the number of
+ team based scopes in use to a small number. (Person.inTeam could be
+ fixed to reduce this to one query).
+
+ teamid scopes are written as 'team:' + the team name to match.
+
+ E.g. the scope 'team:launchpad-beta-users' will match members of
+ the team 'launchpad-beta-users'.
+ """
+ person = getUtility(ILaunchBag).user
+ if person is None:
+ return False
+ return person.inTeam(team_name)
+
def _pageid_to_namespace(self, pageid):
"""Return a list of namespace elements for pageid."""
# Normalise delimiters.
=== modified file 'lib/lp/services/fields/__init__.py'
--- lib/lp/services/fields/__init__.py 2010-10-03 15:30:06 +0000
+++ lib/lp/services/fields/__init__.py 2010-11-05 19:52:03 +0000
@@ -340,7 +340,7 @@
def _get_schema(self):
"""Get the schema here to avoid circular imports."""
- from canonical.launchpad.interfaces import IBug
+ from lp.bugs.interfaces.bug import IBug
return IBug
def _set_schema(self, schema):
@@ -809,7 +809,7 @@
def is_public_person(person):
"""Return True if the person is public."""
- from canonical.launchpad.interfaces import IPerson, PersonVisibility
+ from lp.registry.interfaces.person import IPerson, PersonVisibility
if not IPerson.providedBy(person):
return False
return person.visibility == PersonVisibility.PUBLIC
=== modified file 'lib/lp/services/mail/incoming.py'
--- lib/lp/services/mail/incoming.py 2010-10-15 19:29:58 +0000
+++ lib/lp/services/mail/incoming.py 2010-11-05 19:52:03 +0000
@@ -27,15 +27,14 @@
directlyProvides,
)
-from canonical.launchpad.interfaces import (
- AccountStatus,
+from canonical.launchpad.interfaces.account import AccountStatus
+from canonical.launchpad.interfaces.gpghandler import (
GPGVerificationError,
IGPGHandler,
- ILibraryFileAliasSet,
- IMailBox,
- IPerson,
- IWeaklyAuthenticatedPrincipal,
)
+from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
+from canonical.launchpad.interfaces.mail import IWeaklyAuthenticatedPrincipal
+from canonical.launchpad.interfaces.mailbox import IMailBox
from canonical.launchpad.mail.commands import get_error_message
from canonical.launchpad.mail.handlers import mail_handlers
from canonical.launchpad.mailnotification import (
@@ -51,6 +50,7 @@
)
from canonical.launchpad.webapp.interfaces import IPlacelessAuthUtility
from canonical.librarian.interfaces import UploadFailed
+from lp.registry.interfaces.person import IPerson
from lp.services.mail.sendmail import do_paranoid_envelope_to_validation
from lp.services.mail.signedmessage import signed_message_from_string
=== modified file 'lib/lp/services/mail/mailbox.py'
--- lib/lp/services/mail/mailbox.py 2010-10-03 15:30:06 +0000
+++ lib/lp/services/mail/mailbox.py 2010-11-05 19:52:03 +0000
@@ -10,7 +10,7 @@
from zope.interface import implements
-from canonical.launchpad.interfaces import (
+from canonical.launchpad.interfaces.mailbox import (
IMailBox,
MailBoxError,
)
=== modified file 'lib/lp/services/mail/notificationrecipientset.py'
--- lib/lp/services/mail/notificationrecipientset.py 2010-10-03 15:30:06 +0000
+++ lib/lp/services/mail/notificationrecipientset.py 2010-11-05 19:52:03 +0000
@@ -15,7 +15,7 @@
from zope.security.proxy import isinstance as zope_isinstance
from canonical.launchpad.helpers import emailPeople
-from canonical.launchpad.interfaces import (
+from canonical.launchpad.interfaces.launchpad import (
INotificationRecipientSet,
UnknownRecipientError,
)
=== modified file 'lib/lp/services/mail/signedmessage.py'
--- lib/lp/services/mail/signedmessage.py 2010-10-03 15:30:06 +0000
+++ lib/lp/services/mail/signedmessage.py 2010-11-05 19:52:03 +0000
@@ -15,7 +15,7 @@
from zope.interface import implements
-from canonical.launchpad.interfaces import ISignedMessage
+from canonical.launchpad.interfaces.mail import ISignedMessage
clearsigned_re = re.compile(
=== modified file 'lib/lp/services/mailman/testing/sync.py'
--- lib/lp/services/mailman/testing/sync.py 2010-10-03 15:30:06 +0000
+++ lib/lp/services/mailman/testing/sync.py 2010-11-05 19:52:03 +0000
@@ -30,10 +30,8 @@
login,
logout,
)
-from canonical.launchpad.interfaces import (
- IEmailAddressSet,
- IPersonSet,
- )
+from canonical.launchpad.interfaces.emailaddress import IEmailAddressSet
+from lp.registry.interfaces.person import IPersonSet
class SyncDetails:
=== modified file 'lib/lp/services/scripts/base.py'
--- lib/lp/services/scripts/base.py 2010-11-01 12:56:23 +0000
+++ lib/lp/services/scripts/base.py 2010-11-05 19:52:03 +0000
@@ -16,7 +16,11 @@
from optparse import OptionParser
import os.path
import sys
-from urllib2 import urlopen, HTTPError, URLError
+from urllib2 import (
+ HTTPError,
+ URLError,
+ urlopen,
+ )
from contrib.glock import (
GlobalLock,
@@ -28,7 +32,6 @@
from canonical.config import config
from canonical.database.sqlbase import ISOLATION_LEVEL_DEFAULT
from canonical.launchpad import scripts
-from canonical.launchpad.interfaces import IScriptActivitySet
from canonical.launchpad.scripts.logger import OopsHandler
from canonical.launchpad.webapp.errorlog import globalErrorUtility
from canonical.launchpad.webapp.interaction import (
@@ -36,6 +39,7 @@
setupInteractionByEmail,
)
from canonical.lp import initZopeless
+from lp.services.scripts.interfaces.scriptactivity import IScriptActivitySet
LOCK_PATH = "/var/lock/"
=== modified file 'lib/lp/services/scripts/model/scriptactivity.py'
--- lib/lp/services/scripts/model/scriptactivity.py 2010-10-03 15:30:06 +0000
+++ lib/lp/services/scripts/model/scriptactivity.py 2010-11-05 19:52:03 +0000
@@ -17,7 +17,7 @@
from canonical.database.datetimecol import UtcDateTimeCol
from canonical.database.sqlbase import SQLBase
-from canonical.launchpad.interfaces import (
+from lp.services.scripts.interfaces.scriptactivity import (
IScriptActivity,
IScriptActivitySet,
)
=== modified file 'lib/lp/services/worlddata/model/language.py'
--- lib/lp/services/worlddata/model/language.py 2010-10-03 15:30:06 +0000
+++ lib/lp/services/worlddata/model/language.py 2010-11-05 19:52:03 +0000
@@ -26,7 +26,7 @@
SQLBase,
sqlvalues,
)
-from canonical.launchpad.interfaces import ISlaveStore
+from canonical.launchpad.interfaces.lpstorm import ISlaveStore
from lp.app.errors import NotFoundError
from lp.services.worlddata.interfaces.language import (
ILanguage,
=== modified file 'lib/lp/testing/_webservice.py'
--- lib/lp/testing/_webservice.py 2010-10-03 15:30:06 +0000
+++ lib/lp/testing/_webservice.py 2010-11-05 19:52:03 +0000
@@ -26,13 +26,11 @@
from zope.component import getUtility
import zope.testing.cleanup
-from canonical.launchpad.interfaces import (
- IOAuthConsumerSet,
- IPersonSet,
- )
+from canonical.launchpad.interfaces.oauth import IOAuthConsumerSet
from canonical.launchpad.webapp.adapter import get_request_statements
from canonical.launchpad.webapp.interaction import ANONYMOUS
from canonical.launchpad.webapp.interfaces import OAuthPermission
+from lp.registry.interfaces.person import IPersonSet
from lp.testing._login import (
login,
logout,
=== modified file 'lib/lp/testing/factory.py'
--- lib/lp/testing/factory.py 2010-11-04 19:59:02 +0000
+++ lib/lp/testing/factory.py 2010-11-05 19:52:03 +0000
@@ -68,10 +68,6 @@
Message,
MessageChunk,
)
-from canonical.launchpad.interfaces import (
- IMasterStore,
- IStore,
- )
from canonical.launchpad.interfaces.account import (
AccountCreationRationale,
AccountStatus,
@@ -84,6 +80,10 @@
from canonical.launchpad.interfaces.gpghandler import IGPGHandler
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
from canonical.launchpad.interfaces.librarian import ILibraryFileAliasSet
+from canonical.launchpad.interfaces.lpstorm import (
+ IMasterStore,
+ IStore,
+ )
from canonical.launchpad.interfaces.oauth import IOAuthConsumerSet
from canonical.launchpad.interfaces.temporaryblobstorage import (
ITemporaryStorageManager,
@@ -112,6 +112,10 @@
IBugTrackerSet,
)
from lp.bugs.interfaces.bugwatch import IBugWatchSet
+from lp.bugs.interfaces.cve import (
+ CveStatus,
+ ICveSet,
+ )
from lp.buildmaster.enums import (
BuildFarmJobType,
BuildStatus,
@@ -1633,7 +1637,8 @@
def makeSpecification(self, product=None, title=None, distribution=None,
name=None, summary=None, owner=None,
- status=SpecificationDefinitionStatus.NEW):
+ status=SpecificationDefinitionStatus.NEW,
+ implementation_status=None):
"""Create and return a new, arbitrary Blueprint.
:param product: The product to make the blueprint on. If one is
@@ -1649,7 +1654,7 @@
title = self.getUniqueString('title')
if owner is None:
owner = self.makePerson()
- return getUtility(ISpecificationSet).new(
+ spec = getUtility(ISpecificationSet).new(
name=name,
title=title,
specurl=None,
@@ -1658,6 +1663,11 @@
owner=owner,
product=product,
distribution=distribution)
+ if implementation_status is not None:
+ naked_spec = removeSecurityProxy(spec)
+ naked_spec.implementation_status = implementation_status
+ naked_spec.updateLifecycleStatus(owner)
+ return spec
def makeQuestion(self, target=None, title=None):
"""Create and return a new, arbitrary Question.
@@ -3251,6 +3261,13 @@
consumer, reviewed_by=owner, access_level=access_level)
return request_token.createAccessToken()
+ def makeCVE(self, sequence, description=None,
+ cvestate=CveStatus.CANDIDATE):
+ """Create a new CVE record."""
+ if description is None:
+ description = self.getUniqueString()
+ return getUtility(ICveSet).new(sequence, description, cvestate)
+
# Some factory methods return simple Python types. We don't add
# security wrappers for them, as well as for objects created by
=== modified file 'lib/lp/testing/mail.py'
--- lib/lp/testing/mail.py 2010-10-03 15:30:06 +0000
+++ lib/lp/testing/mail.py 2010-11-05 19:52:03 +0000
@@ -10,7 +10,7 @@
from zope.component import getUtility
-from canonical.launchpad.interfaces import IMailBox
+from canonical.launchpad.interfaces.mailbox import IMailBox
from canonical.launchpad.mail import (
get_msgid,
MailController,
=== modified file 'lib/lp/testing/tests/test_factory.py'
--- lib/lp/testing/tests/test_factory.py 2010-10-25 19:11:46 +0000
+++ lib/lp/testing/tests/test_factory.py 2010-11-05 19:52:03 +0000
@@ -17,6 +17,10 @@
DatabaseFunctionalLayer,
LaunchpadZopelessLayer,
)
+from lp.bugs.interfaces.cve import (
+ CveStatus,
+ ICve,
+ )
from lp.buildmaster.enums import BuildStatus
from lp.code.enums import (
BranchType,
@@ -492,6 +496,24 @@
ssp = self.factory.makeSuiteSourcePackage()
self.assertThat(ssp, ProvidesAndIsProxied(ISuiteSourcePackage))
+ # makeCVE
+ def test_makeCVE_returns_cve(self):
+ cve = self.factory.makeCVE(sequence='2000-1234')
+ self.assertThat(cve, ProvidesAndIsProxied(ICve))
+
+ def test_makeCVE_uses_sequence(self):
+ cve = self.factory.makeCVE(sequence='2000-1234')
+ self.assertEqual('2000-1234', cve.sequence)
+
+ def test_makeCVE_uses_description(self):
+ cve = self.factory.makeCVE(sequence='2000-1234', description='foo')
+ self.assertEqual('foo', cve.description)
+
+ def test_makeCVE_uses_cve_status(self):
+ cve = self.factory.makeCVE(
+ sequence='2000-1234', cvestate=CveStatus.DEPRECATED)
+ self.assertEqual(CveStatus.DEPRECATED, cve.status)
+
class TestFactoryWithLibrarian(TestCaseWithFactory):
=== modified file 'utilities/migrater/deglob.py'
--- utilities/migrater/deglob.py 2010-10-20 20:45:23 +0000
+++ utilities/migrater/deglob.py 2010-11-05 19:52:03 +0000
@@ -5,6 +5,9 @@
#find true path
#replace all occurences.
+import os
+import sys
+
from find import find_matches
@@ -228,10 +231,8 @@
print " %(lineno)4s: %(text)s" % line
-def update_multi_python_globs_to_interfaces():
- root = 'lib'
- types = r'tests'
- pattern = r'from canonical\.launchpad\.interfaces import'
+def update_multi_python_globs_to_interfaces(root='lib', types='tests'):
+ pattern=r'from canonical\.launchpad\.interfaces import'
substitution = True
for summary in find_matches(
root, types, pattern, substitution=substitution,
@@ -241,9 +242,8 @@
print " %(lineno)4s: %(text)s" % line
-def update_python_globs_to_interfaces():
- root = 'lib'
- types = r'tests'
+def update_python_globs_to_interfaces(root='lib', types='tests'):
+ update_multi_python_globs_to_interfaces(root=root, types=types)
globs = r'from \bcanonical\.launchpad\.interfaces import (\w+)$'
interfaces = get_interfaces(types=types, globs=globs)
interface_modules = get_interface_modules(interfaces)
@@ -259,8 +259,12 @@
def main():
- update_multi_python_globs_to_interfaces()
- update_python_globs_to_interfaces()
+ if len(sys.argv) != 3:
+ print 'Usage: %s root_path file_test', os.path.basename(sys.argv[0])
+ sys.exit(1)
+ root = sys.argv[1]
+ types = sys.argv[2]
+ update_python_globs_to_interfaces(root, types)
if __name__ == '__main__':
Follow ups