launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #01176
[Merge] lp:~bac/launchpad/bug-643538-code into lp:launchpad/devel
Brad Crittenden has proposed merging lp:~bac/launchpad/bug-643538-code into lp:launchpad/devel.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
#634538 Python-setuptools patch for gkeyring
https://bugs.launchpad.net/bugs/634538
#643538 Launchpad must state the project's code usage
https://bugs.launchpad.net/bugs/643538
= Summary =
Launchpad must state the project's code usage. The product +code-index page needs to clearly state if the project is using Launchpad. If it isn't it should refer to where it is hosted.
== Proposed fix ==
Conditionally present the correct message. Also moved some of the code summary information into portlets in order to be more like the other application areas.
== Pre-implementation notes ==
Chats with Curtis, Edwin, and Jon.
== Implementation details ==
As above.
== Tests ==
Basically all of the code tests need to run:
bin/test -vvm lp.code
== Demo and Q/A ==
Create a new project and visit:
https://code.launchpad.dev/mynewproject
= Launchpad lint =
The lint issues are pretty much intractable. I cleaned up a lot.
Linting changed files:
lib/lp/code/browser/configure.zcml
lib/canonical/launchpad/icing/style.css
lib/lp/code/templates/product-branches.pt
lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt
lib/lp/code/browser/branch.py
lib/lp/code/browser/tests/test_product.py
lib/lp/registry/browser/product.py
lib/lp/code/browser/tests/test_branch.py
lib/lp/code/stories/branches/xx-product-branches.txt
lib/lp/code/templates/product-portlet-codestatistics-content.pt
lib/lp/registry/browser/pillar.py
lib/lp/code/stories/branches/xx-person-branches.txt
lib/lp/code/templates/product-portlet-codestatistics.pt
lib/lp/code/templates/product-branch-summary.pt
lib/lp/registry/browser/tests/pillar-views.txt
lib/lp/code/stories/branches/xx-private-branch-listings.txt
lib/lp/registry/model/product.py
lib/lp/registry/tests/test_service_usage.py
lib/lp/code/browser/branchlisting.py
./lib/lp/code/stories/branches/xx-private-branch-listings.txt
83: want exceeds 78 characters.
93: want exceeds 78 characters.
./lib/lp/code/browser/branchlisting.py
409: Line exceeds 78 characters.
--
https://code.launchpad.net/~bac/launchpad/bug-643538-code/+merge/36377
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~bac/launchpad/bug-643538-code into lp:launchpad/devel.
=== modified file 'lib/canonical/launchpad/icing/style.css'
--- lib/canonical/launchpad/icing/style.css 2010-05-28 19:47:23 +0000
+++ lib/canonical/launchpad/icing/style.css 2010-09-22 20:23:46 +0000
@@ -550,6 +550,16 @@
color: black !important;
}
+.code-links td.code-count {
+ text-align: right;
+ padding-right: 0.5em;
+}
+
+.code-links td.code-link {
+ text-align: left;
+ margin: 0 0 0 0;
+}
+
/* === Bugs === */
/* The Launchpad Bugs application uses a maroon color: */
@@ -585,6 +595,15 @@
padding-right: 1em;
}
+.bug-links td.bugs-count {
+ text-align: right;
+ padding-right: 0.5em;
+}
+
+.bug-links td.bugs-link {
+ text-align: left;
+}
+
/* --- Blueprints --- */
body.tab-specifications #actions, body.tab-specifications .results {
@@ -652,15 +671,6 @@
margin: 0.5em;
}
-.bug-links td.bugs-count {
- text-align: right;
- padding-right: 0.5em;
-}
-
-.bug-links td.bugs-link {
- text-align: left;
-}
-
/* ====== Content area styles ====== */
/* -- Front pages -- */
=== modified file 'lib/lp/code/browser/branch.py'
--- lib/lp/code/browser/branch.py 2010-08-24 10:45:57 +0000
+++ lib/lp/code/browser/branch.py 2010-09-22 20:23:46 +0000
@@ -16,6 +16,7 @@
'BranchReviewerEditView',
'BranchMergeQueueView',
'BranchMirrorStatusView',
+ 'BranchMirrorMixin',
'BranchNameValidationMixin',
'BranchNavigation',
'BranchEditMenu',
@@ -374,7 +375,39 @@
return Link('+new-recipe', text, enabled=enabled, icon='add')
-class BranchView(LaunchpadView, FeedsMixin):
+class BranchMirrorMixin:
+ """Provide mirror_location property.
+
+ Requires self.branch to be set by the class using this mixin.
+ """
+
+ @property
+ def mirror_location(self):
+ """Check the mirror location to see if it is a private one."""
+ branch = self.branch
+
+ # If the user has edit permissions, then show the actual location.
+ if check_permission('launchpad.Edit', branch):
+ return branch.url
+
+ # XXX: Tim Penhey, 2008-05-30
+ # Instead of a configuration hack we should support the users
+ # specifying whether or not they want the mirror location
+ # hidden or not. Given that this is a database patch,
+ # it isn't going to happen today.
+ # See bug 235916
+ hosts = config.codehosting.private_mirror_hosts.split(',')
+ private_mirror_hosts = [name.strip() for name in hosts]
+
+ uri = URI(branch.url)
+ for private_host in private_mirror_hosts:
+ if uri.underDomain(private_host):
+ return '<private server>'
+
+ return branch.url
+
+
+class BranchView(LaunchpadView, FeedsMixin, BranchMirrorMixin):
feed_types = (
BranchFeedLink,
@@ -387,6 +420,7 @@
label = page_title
def initialize(self):
+ self.branch = self.context
self.notices = []
# Replace our context with a decorated branch, if it is not already
# decorated.
@@ -586,31 +620,6 @@
return url.startswith("http")
@property
- def mirror_location(self):
- """Check the mirror location to see if it is a private one."""
- branch = self.context
-
- # If the user has edit permissions, then show the actual location.
- if check_permission('launchpad.Edit', branch):
- return branch.url
-
- # XXX: Tim Penhey, 2008-05-30
- # Instead of a configuration hack we should support the users
- # specifying whether or not they want the mirror location
- # hidden or not. Given that this is a database patch,
- # it isn't going to happen today.
- # See bug 235916
- hosts = config.codehosting.private_mirror_hosts.split(',')
- private_mirror_hosts = [name.strip() for name in hosts]
-
- uri = URI(branch.url)
- for private_host in private_mirror_hosts:
- if uri.underDomain(private_host):
- return '<private server>'
-
- return branch.url
-
- @property
def show_merge_links(self):
"""Return whether or not merge proposal links should be shown.
=== modified file 'lib/lp/code/browser/branchlisting.py'
--- lib/lp/code/browser/branchlisting.py 2010-08-31 11:11:09 +0000
+++ lib/lp/code/browser/branchlisting.py 2010-09-22 20:23:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Base class view for branch listings."""
@@ -82,6 +82,7 @@
from canonical.launchpad.webapp.batching import TableBatchNavigator
from canonical.launchpad.webapp.breadcrumb import Breadcrumb
from canonical.launchpad.webapp.publisher import LaunchpadView
+from canonical.launchpad.webapp.tales import MenuAPI
from canonical.widgets import LaunchpadDropdownWidget
from lp.blueprints.interfaces.specificationbranch import (
ISpecificationBranchSet,
@@ -92,6 +93,7 @@
PersonActiveReviewsView,
PersonProductActiveReviewsView,
)
+from lp.code.browser.summary import BranchCountSummaryView
from lp.code.enums import (
BranchLifecycleStatus,
BranchLifecycleStatusFilter,
@@ -112,6 +114,7 @@
from lp.code.interfaces.seriessourcepackagebranch import (
IFindOfficialBranchLinks,
)
+from lp.code.browser.branch import BranchMirrorMixin
from lp.registry.browser.product import (
ProductDownloadFileMixin,
SortSeriesMixin,
@@ -124,7 +127,6 @@
IPersonProduct,
IPersonProductFactory,
)
-from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.series import SeriesStatus
from lp.registry.interfaces.sourcepackage import ISourcePackageFactory
@@ -421,7 +423,7 @@
return sorted(links, key=attrgetter('pocket'))
def getDistroDevelSeries(self, distribution):
- """Since distribution.currentseries hits the DB every time, cache it."""
+ """distribution.currentseries hits the DB every time so cache it."""
self._distro_series_map = {}
try:
return self._distro_series_map[distribution]
@@ -777,7 +779,7 @@
"""A branch listing that has no associated product or person."""
field_names = ['lifecycle']
- no_sort_by = (BranchListingSort.DEFAULT,)
+ no_sort_by = (BranchListingSort.DEFAULT, )
no_branch_message = (
'There are no branches that match the current status filter.')
@@ -932,8 +934,8 @@
def active_reviews(self):
text = get_plural_text(
self.active_review_count,
- 'active review or unmerged proposal',
- 'active reviews or unmerged proposals')
+ 'active review',
+ 'active reviews')
return Link('+activereviews', text)
def addbranch(self):
@@ -1022,7 +1024,7 @@
page_title = _('Subscribed')
label_template = 'Bazaar branches subscribed to by %(displayname)s'
- no_sort_by = (BranchListingSort.DEFAULT,)
+ no_sort_by = (BranchListingSort.DEFAULT, )
def _getCollection(self):
return getUtility(IAllBranches).subscribedBy(self.context)
@@ -1122,8 +1124,8 @@
def active_reviews(self):
text = get_plural_text(
self.active_review_count,
- 'active review or unmerged proposal',
- 'active reviews or unmerged proposals')
+ 'active review',
+ 'active reviews')
return Link('+activereviews', text, site='code')
@enabled_with_permission('launchpad.Commercial')
@@ -1140,7 +1142,7 @@
"""A base class for product branch listings."""
show_series_links = True
- no_sort_by = (BranchListingSort.PRODUCT,)
+ no_sort_by = (BranchListingSort.PRODUCT, )
label_template = 'Bazaar branches of %(displayname)s'
def _getCollection(self):
@@ -1180,8 +1182,13 @@
return message % self.context.displayname
+class ProductBranchStatisticsView(BranchCountSummaryView,
+ ProductBranchListingView):
+ """Portlet containing branch statistics."""
+
+
class ProductCodeIndexView(ProductBranchListingView, SortSeriesMixin,
- ProductDownloadFileMixin):
+ ProductDownloadFileMixin, BranchMirrorMixin):
"""Initial view for products on the code virtual host."""
show_set_development_focus = True
@@ -1189,6 +1196,7 @@
def initialize(self):
ProductBranchListingView.initialize(self)
self.product = self.context
+ self.branch = self.development_focus_branch
revision_cache = getUtility(IRevisionCache)
self.revision_cache = revision_cache.inProduct(self.product)
@@ -1240,6 +1248,7 @@
# skip subsequent series where the lifecycle status is Merged or
# Abandoned
sorted_series = self.sorted_active_series_list
+
def show_branch(branch):
if self.selected_lifecycle_status is None:
return True
@@ -1323,6 +1332,14 @@
def committer_text(self):
return get_plural_text(self.committer_count, _('person'), _('people'))
+ @property
+ def configure_codehosting(self):
+ """Get the menu link for configuring code hosting."""
+ series_menu = MenuAPI(self.context.development_focus).overview
+ set_branch = series_menu['set_branch']
+ set_branch.text = 'Configure code hosting'
+ return set_branch
+
class ProductBranchesView(ProductBranchListingView):
"""View for branch listing for a product."""
@@ -1353,7 +1370,7 @@
class ProjectBranchesView(BranchListingView):
"""View for branch listing for a project."""
- no_sort_by = (BranchListingSort.DEFAULT,)
+ no_sort_by = (BranchListingSort.DEFAULT, )
extra_columns = ('author', 'product')
label_template = 'Bazaar branches of %(displayname)s'
show_series_links = True
=== modified file 'lib/lp/code/browser/configure.zcml'
--- lib/lp/code/browser/configure.zcml 2010-09-22 18:37:57 +0000
+++ lib/lp/code/browser/configure.zcml 2010-09-22 20:23:46 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2009-2010 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -961,6 +961,18 @@
permission="zope.Public"
name="+code-index"
template="../templates/product-branches.pt"/>
+ <browser:page
+ for="lp.registry.interfaces.product.IProduct"
+ class="lp.code.browser.branchlisting.ProductBranchStatisticsView"
+ permission="zope.Public"
+ name="+portlet-product-codestatistics"
+ template="../templates/product-portlet-codestatistics.pt"/>
+ <browser:page
+ for="lp.registry.interfaces.product.IProduct"
+ class="lp.code.browser.branchlisting.ProductBranchStatisticsView"
+ permission="zope.Public"
+ name="+portlet-product-codestatistics-content"
+ template="../templates/product-portlet-codestatistics-content.pt"/>
<browser:page
for="lp.registry.interfaces.product.IProduct"
layer="lp.code.publisher.CodeLayer"
=== modified file 'lib/lp/code/browser/tests/test_branch.py'
--- lib/lp/code/browser/tests/test_branch.py 2010-08-20 20:31:18 +0000
+++ lib/lp/code/browser/tests/test_branch.py 2010-09-22 20:23:46 +0000
@@ -45,7 +45,6 @@
)
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.testing import (
- ANONYMOUS,
login,
login_person,
logout,
@@ -78,6 +77,7 @@
branch_type=BranchType.MIRRORED,
url="http://example.com/good/mirror")
view = BranchView(branch, LaunchpadTestRequest())
+ view.initialize()
self.assertTrue(view.user is None)
self.assertEqual(
"http://example.com/good/mirror", view.mirror_location)
@@ -89,6 +89,7 @@
branch_type=BranchType.MIRRORED,
url="http://private.example.com/bzr-mysql/mysql-5.0")
view = BranchView(branch, LaunchpadTestRequest())
+ view.initialize()
self.assertTrue(view.user is None)
self.assertEqual(
"<private server>", view.mirror_location)
@@ -106,6 +107,7 @@
logout()
login('eric@xxxxxxxxxxx')
view = BranchView(branch, LaunchpadTestRequest())
+ view.initialize()
self.assertEqual(view.user, owner)
self.assertEqual(
"http://private.example.com/bzr-mysql/mysql-5.0",
@@ -126,6 +128,7 @@
logout()
login('other@xxxxxxxxxxx')
view = BranchView(branch, LaunchpadTestRequest())
+ view.initialize()
self.assertEqual(view.user, other)
self.assertEqual(
"<private server>", view.mirror_location)
@@ -160,7 +163,7 @@
len(branch.mirror_status_message)
<= branch_view.MAXIMUM_STATUS_MESSAGE_LENGTH,
"branch.mirror_status_message longer than expected: %r"
- % (branch.mirror_status_message,))
+ % (branch.mirror_status_message, ))
self.assertEqual(
branch.mirror_status_message, branch_view.mirror_status_message)
self.assertEqual(
@@ -185,7 +188,7 @@
'whiteboard': '',
'owner': arbitrary_person,
'author': arbitrary_person,
- 'product': arbitrary_product
+ 'product': arbitrary_product,
}
add_view.add_action.success(data)
# Make sure that next_mirror_time is a datetime, not an sqlbuilder
=== modified file 'lib/lp/code/browser/tests/test_product.py'
--- lib/lp/code/browser/tests/test_product.py 2010-08-24 02:16:53 +0000
+++ lib/lp/code/browser/tests/test_product.py 2010-09-22 20:23:46 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for the product view classes and templates."""
@@ -10,20 +10,25 @@
timedelta,
)
import unittest
-
from mechanize import LinkNotFoundError
import pytz
-from zope.component import (
- getMultiAdapter,
- getUtility,
+from zope.component import getUtility
+
+from canonical.launchpad.testing.pages import (
+ extract_text,
+ find_tag_by_id,
)
-
from canonical.launchpad.webapp import canonical_url
-from canonical.launchpad.webapp.servers import LaunchpadTestRequest
from canonical.testing import DatabaseFunctionalLayer
+from lp.app.enums import ServiceUsage
+from lp.code.enums import (
+ BranchType,
+ BranchVisibilityRule,
+ )
from lp.code.interfaces.revision import IRevisionSet
from lp.testing import (
ANONYMOUS,
+ BrowserTestCase,
login,
login_person,
TestCaseWithFactory,
@@ -32,9 +37,8 @@
from lp.testing.views import create_initialized_view
-class TestProductCodeIndexView(TestCaseWithFactory):
- """Tests for the product code home page."""
-
+class ProductTestBase(TestCaseWithFactory):
+ """Common methods for tests herein."""
layer = DatabaseFunctionalLayer
def makeProductAndDevelopmentFocusBranch(self, **branch_args):
@@ -49,6 +53,10 @@
product.development_focus.branch = branch
return product, branch
+
+class TestProductCodeIndexView(ProductTestBase):
+ """Tests for the product code home page."""
+
def getBranchSummaryBrowseLinkForProduct(self, product):
"""Get the 'browse code' link from the product's code home.
@@ -98,13 +106,15 @@
def test_initial_branches_contains_dev_focus_branch(self):
product, branch = self.makeProductAndDevelopmentFocusBranch()
- view = create_initialized_view(product, '+code-index', rootsite='code')
+ view = create_initialized_view(product, '+code-index',
+ rootsite='code')
self.assertIn(branch, view.initial_branches)
def test_initial_branches_does_not_contain_private_dev_focus_branch(self):
product, branch = self.makeProductAndDevelopmentFocusBranch(
private=True)
- view = create_initialized_view(product, '+code-index', rootsite='code')
+ view = create_initialized_view(product, '+code-index',
+ rootsite='code')
self.assertNotIn(branch, view.initial_branches)
def test_committer_count_with_revision_authors(self):
@@ -120,7 +130,8 @@
date_generator=date_generator)
getUtility(IRevisionSet).updateRevisionCacheForBranch(branch)
- view = create_initialized_view(product, '+code-index', rootsite='code')
+ view = create_initialized_view(product, '+code-index',
+ rootsite='code')
self.assertEqual(view.committer_count, 1)
def test_committers_count_private_branch(self):
@@ -138,10 +149,150 @@
date_generator=date_generator)
getUtility(IRevisionSet).updateRevisionCacheForBranch(branch)
- view = create_initialized_view(product, '+code-index', rootsite='code')
+ view = create_initialized_view(product, '+code-index',
+ rootsite='code')
self.assertEqual(view.committer_count, 1)
+class TestProductCodeIndexServiceUsages(ProductTestBase, BrowserTestCase):
+ """Tests for the product code page, especially the usage messasges."""
+
+ def test_external_mirrored(self):
+ # Test that the correct URL is displayed for a mirrored branch.
+ product, branch = self.makeProductAndDevelopmentFocusBranch(
+ branch_type=BranchType.MIRRORED,
+ url="http://example.com/mybranch")
+ self.assertEqual(ServiceUsage.EXTERNAL, product.codehosting_usage)
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ login(ANONYMOUS)
+ content = find_tag_by_id(browser.contents, 'external')
+ text = extract_text(content)
+ expected = ("%(product_title)s hosts its code at %(branch_url)s. "
+ "Launchpad has a mirror of the master branch "
+ "and you can create branches from it." % dict(
+ product_title=product.title,
+ branch_url=branch.url))
+ self.assertTextMatchesExpressionIgnoreWhitespace(expected, text)
+
+ def test_external_remote(self):
+ # Test that a remote branch is shown properly.
+ product, branch = self.makeProductAndDevelopmentFocusBranch(
+ branch_type=BranchType.REMOTE,
+ url="http://example.com/mybranch")
+ self.assertEqual(ServiceUsage.EXTERNAL,
+ product.codehosting_usage)
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ login(ANONYMOUS)
+ content = find_tag_by_id(browser.contents, 'external')
+ text = extract_text(content)
+ expected = ("%(product_title)s hosts its code at %(branch_url)s. "
+ "Launchpad does not have a copy of the remote "
+ "branch." % dict(
+ product_title=product.title,
+ branch_url=branch.url))
+ self.assertTextMatchesExpressionIgnoreWhitespace(expected, text)
+
+ def test_unknown(self):
+ product = self.factory.makeProduct()
+ self.assertEqual(ServiceUsage.UNKNOWN, product.codehosting_usage)
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ login(ANONYMOUS)
+ content = find_tag_by_id(browser.contents, 'unknown')
+ text = extract_text(content)
+ expected = (
+ "Launchpad does not know where %(product_title)s "
+ "hosts its code.*" %
+ dict(product_title=product.title))
+ self.assertTextMatchesExpressionIgnoreWhitespace(expected, text)
+
+ def test_on_launchpad(self):
+ product, branch = self.makeProductAndDevelopmentFocusBranch()
+ self.assertEqual(ServiceUsage.LAUNCHPAD, product.codehosting_usage)
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ login(ANONYMOUS)
+ text = extract_text(find_tag_by_id(
+ browser.contents, 'branch-count-summary'))
+ expected = "1 active branch owned by 1 person.*"
+ self.assertTextMatchesExpressionIgnoreWhitespace(expected, text)
+
+ def test_view_mirror_location(self):
+ url = "http://example.com/mybranch"
+ product, branch = self.makeProductAndDevelopmentFocusBranch(
+ branch_type=BranchType.MIRRORED,
+ url=url)
+ view = create_initialized_view(product, '+code-index',
+ rootsite='code')
+ self.assertEqual(url, view.mirror_location)
+
+
+class TestProductBranchesViewPortlets(ProductTestBase, BrowserTestCase):
+ """Tests for the portlets."""
+
+ def test_portlet_not_shown_for_UNKNOWN(self):
+ # If the BranchUsage is UNKNOWN then the portlets are not shown.
+ product = self.factory.makeProduct()
+ self.assertEqual(ServiceUsage.UNKNOWN, product.codehosting_usage)
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ contents = browser.contents
+ self.assertEqual(None, find_tag_by_id(contents, 'branch-portlet'))
+ self.assertEqual(None, find_tag_by_id(contents, 'privacy'))
+ self.assertEqual(None, find_tag_by_id(contents, 'involvement'))
+ self.assertEqual(None, find_tag_by_id(
+ contents, 'portlet-product-codestatistics'))
+
+ def test_portlets_shown_for_HOSTED(self):
+ # If the BranchUsage is HOSTED then the portlets are shown.
+ product, branch = self.makeProductAndDevelopmentFocusBranch()
+ self.assertEqual(ServiceUsage.LAUNCHPAD, product.codehosting_usage)
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ contents = browser.contents
+ self.assertNotEqual(None, find_tag_by_id(contents, 'branch-portlet'))
+ self.assertNotEqual(None, find_tag_by_id(contents, 'privacy'))
+ self.assertNotEqual(None, find_tag_by_id(contents, 'involvement'))
+ self.assertNotEqual(None, find_tag_by_id(
+ contents, 'portlet-product-codestatistics'))
+
+ def test_portlets_shown_for_EXTERNAL(self):
+ # If the BranchUsage is HOSTED then the portlets are shown.
+ url = "http://example.com/mybranch"
+ product, branch = self.makeProductAndDevelopmentFocusBranch(
+ branch_type=BranchType.MIRRORED,
+ url=url)
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ contents = browser.contents
+ self.assertNotEqual(None, find_tag_by_id(contents, 'branch-portlet'))
+ self.assertNotEqual(None, find_tag_by_id(contents, 'privacy'))
+ self.assertNotEqual(None, find_tag_by_id(contents, 'involvement'))
+ self.assertNotEqual(None, find_tag_by_id(
+ contents, 'portlet-product-codestatistics'))
+
+ def test_is_private(self):
+ team_owner = self.factory.makePerson()
+ team = self.factory.makeTeam(team_owner)
+ product = self.factory.makeProduct(owner=team_owner)
+ branch = self.factory.makeProductBranch(product=product)
+ login_person(product.owner)
+ product.development_focus.branch = branch
+ product.setBranchVisibilityTeamPolicy(
+ team, BranchVisibilityRule.PRIVATE)
+ view = create_initialized_view(
+ product, '+code-index', rootsite='code', principal=product.owner)
+ text = extract_text(find_tag_by_id(view.render(), 'privacy'))
+ expected = ("New branches you create for %(name)s are private "
+ "initially.*" % dict(name=product.displayname))
+ self.assertTextMatchesExpressionIgnoreWhitespace(expected, text)
+
+ def test_is_public(self):
+ product = self.factory.makeProduct()
+ branch = self.factory.makeProductBranch(product=product)
+ login_person(product.owner)
+ product.development_focus.branch = branch
+ browser = self.getUserBrowser(canonical_url(product, rootsite='code'))
+ text = extract_text(find_tag_by_id(browser.contents, 'privacy'))
+ expected = ("New branches you create for %(name)s are public "
+ "initially.*" % dict(name=product.displayname))
+ self.assertTextMatchesExpressionIgnoreWhitespace(expected, text)
+
+
def test_suite():
return unittest.TestLoader().loadTestsFromName(__name__)
-
=== modified file 'lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt'
--- lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt 2010-01-14 23:39:06 +0000
+++ lib/lp/code/stories/branches/xx-branchmergeproposal-listings.txt 2010-09-22 20:23:46 +0000
@@ -63,11 +63,11 @@
>>> browser.open('http://code.launchpad.dev/fooix')
>>> print_tag_with_id(browser.contents, 'merge-counts')
- 3 active reviews or unmerged proposals
-
-The 'active reviews or unmerged proposals' text links to the active reviews page.
-
- >>> browser.getLink('active reviews or unmerged proposals').click()
+ 3 active reviews
+
+The 'active reviews' text links to the active reviews page.
+
+ >>> browser.getLink('active reviews').click()
>>> print browser.title
Active code reviews for Fooix...
@@ -94,7 +94,7 @@
>>> browser.open('http://code.launchpad.dev/~albert')
>>> print_tag_with_id(browser.contents, 'page-summary')
1 owned branch, 1 registered branch, 1 subscribed branch
- 1 active review or unmerged proposal
+ 1 active review
The person's active reviews also lists all of their currently requested
reviews.
=== modified file 'lib/lp/code/stories/branches/xx-person-branches.txt'
--- lib/lp/code/stories/branches/xx-person-branches.txt 2010-05-27 02:19:27 +0000
+++ lib/lp/code/stories/branches/xx-person-branches.txt 2010-09-22 20:23:46 +0000
@@ -101,7 +101,7 @@
>>> eric_browser.open('http://code.launchpad.dev/~eric')
>>> print_tag_with_id(eric_browser.contents, 'page-summary')
1 owned branch, 1 registered branch, 1 subscribed branch
- 0 active reviews or unmerged proposals
+ 0 active reviews
Now we'll create another branch, and unsubscribe the owner from it.
@@ -113,4 +113,4 @@
>>> eric_browser.open('http://code.launchpad.dev/~eric')
>>> print_tag_with_id(eric_browser.contents, 'page-summary')
2 owned branches, 2 registered branches, 1 subscribed branch
- 0 active reviews or unmerged proposals
+ 0 active reviews
=== modified file 'lib/lp/code/stories/branches/xx-private-branch-listings.txt'
--- lib/lp/code/stories/branches/xx-private-branch-listings.txt 2010-09-03 00:25:07 +0000
+++ lib/lp/code/stories/branches/xx-private-branch-listings.txt 2010-09-22 20:23:46 +0000
@@ -1,4 +1,5 @@
-= Private Branch Listings =
+Private Branch Listings
+=======================
All pages that show branch listings to users should only show branches
that the user is allowed to see.
@@ -15,7 +16,8 @@
... reset_all_branch_last_modified)
>>> reset_all_branch_last_modified()
-== Additional sample data ==
+Additional sample data
+----------------------
Adding a private branch that is only visible by No Privileges Person
(and Launchpad administrators).
@@ -37,7 +39,8 @@
>>> logout()
-== The code home page ==
+The code home page
+------------------
The code home page shows lists of recently imported, changed, and
registered branches.
@@ -60,7 +63,8 @@
Logged in users should only be able to see public branches, and private
branches that they are subscribed to or are the owner of.
- >>> no_priv_browser = setupBrowser(auth='Basic no-priv@xxxxxxxxxxxxx:test')
+ >>> no_priv_browser = setupBrowser(
+ ... auth='Basic no-priv@xxxxxxxxxxxxx:test')
>>> print_recently_registered_branches(no_priv_browser)
'...~no-priv/landscape/testing-branch...<span...class="sprite private"...'
'...~mark/+junk/testdoc...'
@@ -91,7 +95,8 @@
'...~name12/gnome-terminal/scanned...'
-== Landscape code listing page ==
+Landscape code listing page
+---------------------------
One of the most obvious places to hide private branches are the code
listing tab.
@@ -103,12 +108,13 @@
... # So print the text shown in the application summary.
... if table is None:
... print extract_text(find_tag_by_id(
- ... browser.contents, 'application-summary'))
+ ... browser.contents, 'branch-summary'))
... else:
... for row in table.tbody.fetch('tr'):
... print extract_text(row)
>>> print_landscape_code_listing(anon_browser)
+ Launchpad does not know where The Landscape Project hosts its code...
There are no branches for The Landscape Project in Launchpad...
>>> print_landscape_code_listing(no_priv_browser)
@@ -124,7 +130,8 @@
lp://dev/~no-priv/landscape/testing-branch Development ...
-== Person code listing pages ==
+Person code listing pages
+-------------------------
The person code listings is the other obvious place to filter out the
viewable branches.
@@ -173,7 +180,8 @@
>>> print_person_code_listing(landscape_dev_browser, '/+ownedbranches')
Total of 10 branches listed
lp://dev/~name12/landscape/feature-x Development ...
- >>> print_person_code_listing(landscape_dev_browser, '/+registeredbranches')
+ >>> print_person_code_listing(landscape_dev_browser,
+ ... '/+registeredbranches')
Total of 11 branches listed
lp://dev/~landscape-developers/landscape/trunk Development ...
lp://dev/~name12/landscape/feature-x Development ...
@@ -190,7 +198,8 @@
lp://dev/~name12/landscape/feature-x Development ...
-== Bug branch links ==
+Bug branch links
+----------------
When a private branch is linked to a bug, the bug branch link is only
visible to those that would be able to see the branch.
@@ -227,7 +236,8 @@
No bug branch links
-== Branches set as primary branches for product series ==
+Branches set as primary branches for product series
+---------------------------------------------------
When a branch is set as the user branch for product series, the details
must be visible to those that are entitled to see it, but hidden from
=== modified file 'lib/lp/code/stories/branches/xx-product-branches.txt'
--- lib/lp/code/stories/branches/xx-product-branches.txt 2010-08-19 14:22:01 +0000
+++ lib/lp/code/stories/branches/xx-product-branches.txt 2010-09-22 20:23:46 +0000
@@ -34,9 +34,10 @@
If there are not any branches, a helpful message is shown.
>>> def get_summary(browser):
- ... return find_tag_by_id(browser.contents, 'application-summary')
+ ... return find_tag_by_id(browser.contents, 'branch-summary')
>>> summary = get_summary(browser)
>>> print extract_text(summary)
+ Launchpad does not know where The Gnome Panel Applets hosts its code.
There are no branches for Gnome Applets in Launchpad.
If there are Bazaar branches of Gnome Applets in a publicly
accessible location, Launchpad can act as a mirror of the branch
@@ -44,7 +45,7 @@
Launchpad can also act as a primary location for Bazaar branches of
Gnome Applets. Read more.
Launchpad can import code from CVS, Subversion, Mercurial or Git
- into Bazaar branches. Read more.
+ into Bazaar branches. Read more...
The 'Read more' links go to the help wiki.
@@ -53,6 +54,7 @@
https://help.launchpad.net/Code/MirroredBranches
https://help.launchpad.net/Code/UploadingABranch
https://help.launchpad.net/VcsImports
+ https://help.launchpad.net/Code
Link to the product downloads
@@ -63,6 +65,7 @@
>>> browser.open('http://code.launchpad.dev/netapplet')
>>> print extract_text(get_summary(browser))
+ Launchpad does not know where Network Applet hosts its code...
There are no branches for NetApplet in Launchpad.
...
There are download files available for NetApplet.
@@ -83,8 +86,6 @@
>>> browser.open('http://code.launchpad.dev/evolution')
>>> summary = get_summary(browser)
>>> print extract_text(get_summary(browser))
- 3 active branches ...
- 0 active reviews or unmerged proposals
You can get a copy of the development focus branch using the
command:
bzr branch lp://dev/evolution
@@ -124,7 +125,8 @@
Firstly lets associate release--0.9.1 with the 1.0 series.
>>> admin_browser.open('http://launchpad.dev/firefox/1.0/+linkbranch')
- >>> admin_browser.getControl('Branch').value = '~mark/firefox/release--0.9.1'
+ >>> admin_browser.getControl('Branch').value = (
+ ... '~mark/firefox/release--0.9.1')
>>> admin_browser.getControl('Update').click()
>>> browser.open('http://code.launchpad.dev/firefox')
@@ -155,32 +157,54 @@
lp://dev/~mark/firefox/release-0.8 Development ...
-Floating buttons
-================
+Involvement portlet
+===================
-There are two buttons that show on the right hand side of the screen
-for project branch listings. 'Register a branch' and 'Import a branch'.
+There are several links in the side portlet: 'Register a branch',
+'Import a branch', 'Configure code hosting', and 'Define branch
+visibility'. The links are only shown if the user has permission to
+perform the task.
>>> from zope.component import getUtility
>>> from lp.registry.interfaces.product import IProductSet
- >>> login('admin@xxxxxxxxxxxxx')
+ >>> from lp.testing.sampledata import ADMIN_EMAIL
+ >>> login(ADMIN_EMAIL)
>>> product = getUtility(IProductSet).getByName('firefox')
>>> old_branch = product.development_focus.branch
>>> product.development_focus.branch = None
>>> logout()
>>> def print_links(browser):
- ... links = find_tag_by_id(browser.contents, 'floating-links')
+ ... links = find_tag_by_id(browser.contents, 'involvement')
... for link in links.findAll('a'):
... print extract_text(link)
- >>> browser.open('http://code.launchpad.dev/firefox')
- >>> print_links(browser)
+ >>> admin_browser.open('http://code.launchpad.dev/firefox')
+ >>> print_links(admin_browser)
+ Register a branch
+ Import a branch
+ Configure code hosting
+ Define branch visibility
+
+The owner of the project sees the links for the activities he can
+perform, everything except defining branch visibility.
+
+ >>> owner_browser = setupBrowser(auth='Basic test@xxxxxxxxxxxxx:test')
+ >>> owner_browser.open('http://code.launchpad.dev/firefox')
+ >>> print_links(owner_browser)
+ Register a branch
+ Import a branch
+ Configure code hosting
+
+And a regular user can only register and import branches.
+
+ >>> user_browser.open('http://code.launchpad.dev/firefox')
+ >>> print_links(user_browser)
Register a branch
Import a branch
If the product specifies that it officially uses Launchpad code, then
the 'Import a branch' button is still shown.
- >>> login('admin@xxxxxxxxxxxxx')
+ >>> login(ADMIN_EMAIL)
>>> product.development_focus.branch = old_branch
>>> logout()
>>> browser.open('http://code.launchpad.dev/firefox')
@@ -189,34 +213,45 @@
Import a branch
-Nice wording of summary numbers
-===============================
+The statistics portlet
+======================
The text that is shown giving a summary of the number of branches
shows correct singular and plural forms.
- >>> def print_summary(product):
+ >>> def get_stats_portlet(browser):
+ ... return find_tag_by_id(
+ ... browser.contents,
+ ... 'portlet-product-codestatistics')
+ >>> def print_portlet(product):
... browser.open('http://code.launchpad.dev/%s' % product)
- ... print extract_text(get_summary(browser))
-
- >>> print_summary('gnome-terminal')
- 8 active branches owned by 1 person and 2 teams, 0 commits in the last month
- ...
+ ... print extract_text(get_stats_portlet(browser))
+
+ >>> print_portlet('gnome-terminal')
+ 0 active reviews
+ 8 active branches owned by 1 person and 2 teams
+ 0 commits in the last month
+
>>> from lp.testing import ANONYMOUS, login, logout
>>> login(ANONYMOUS)
>>> fooix = factory.makeProduct('fooix')
>>> ignored = factory.makeProductBranch(fooix)
>>> ignored = factory.makeProductBranch(fooix)
>>> logout()
- >>> print_summary('fooix')
- 2 active branches owned by 2 people, 0 commits in the last month
- ...
- >>> print_summary('evolution')
- 3 active branches owned by 1 person and 1 team, 0 commits in the last month
- ...
- >>> print_summary('iso-codes')
- 1 active branch owned by 1 person, 0 commits in the last month
- ...
+ >>> print_portlet('fooix')
+ 0 active reviews
+ 2 active branches owned by 2 people
+ 0 commits in the last month
+
+ >>> print_portlet('evolution')
+ 0 active reviews
+ 3 active branches owned by 1 person and 1 team
+ 0 commits in the last month
+
+ >>> print_portlet('iso-codes')
+ 0 active reviews
+ 1 active branch owned by 1 person
+ 0 commits in the last month
Product has Branches, but none initially visible
@@ -231,10 +266,10 @@
>>> admin_browser.getControl('Abandoned').click()
>>> admin_browser.getControl('Change Branch').click()
- >>> browser.open('http://code.launchpad.dev/iso-codes')
- >>> print extract_text(get_summary(browser))
- 0 active branches, 0 commits in the last month
- 0 active reviews or unmerged proposals
+ >>> print_portlet('iso-codes')
+ 0 active reviews
+ 0 active branches
+ 0 commits in the last month
>>> message = find_tag_by_id(browser.contents, 'no-branch-message')
>>> print extract_text(message)
=== modified file 'lib/lp/code/templates/product-branch-summary.pt'
--- lib/lp/code/templates/product-branch-summary.pt 2010-08-13 16:09:45 +0000
+++ lib/lp/code/templates/product-branch-summary.pt 2010-09-22 20:23:46 +0000
@@ -7,7 +7,37 @@
lang="en"
dir="ltr"
i18n:domain="launchpad"
- id="application-summary">
+ id="branch-summary">
+
+ <div id="unknown" tal:condition="context/codehosting_usage/enumvalue:UNKNOWN">
+ <p>
+ <strong>
+ Launchpad does not know where <tal:project_title replace="context/title" />
+ hosts its code.
+ </strong>
+ </p>
+ </div>
+
+ <div id="external"
+ tal:condition="context/codehosting_usage/enumvalue:EXTERNAL">
+ <p>
+ <strong>
+ <tal:project_title replace="context/title" /> hosts its code at
+ <a tal:attributes="href view/mirror_location"><tal:mirror replace="view/mirror_location"/></a>.
+ </strong>
+ </p>
+ <p tal:condition="context/homepageurl">
+ You can learn more at the project's
+ <a tal:attributes="href context/homepageurl">web page</a>.
+ </p>
+ <p tal:condition="view/branch/branch_type/enumvalue:MIRRORED">
+ Launchpad has a mirror of the master branch and you can create branches
+ from it.
+ </p>
+ <p tal:condition="view/branch/branch_type/enumvalue:REMOTE">
+ Launchpad does not have a copy of the remote branch.
+ </p>
+ </div>
<tal:no-branches condition="not: view/branch_count">
There are no branches for <tal:project-name replace="context/displayname"/>
@@ -34,15 +64,6 @@
</tal:no-branches>
<tal:has-branches condition="view/branch_count">
- <p tal:replace="structure context/@@+count-summary"/>
- <p id="merge-counts"
- tal:define="menu context/menu:branches">
- <strong class="count" tal:content="menu/active_review_count">5</strong>
- <tal:link
- define="link menu/active_reviews"
- replace="structure link/render"
- />
- </p>
<div tal:condition="view/has_development_focus_branch"
style="margin: 1em 0"
tal:define="config modules/canonical.config/config;
@@ -60,6 +81,30 @@
</div>
</tal:has-branches>
+
+ <div tal:condition="context/codehosting_usage/enumvalue:UNKNOWN">
+ <div
+ tal:condition="not: context/codehosting_usage/enumvalue:LAUNCHPAD"
+ tal:define="configure_codehosting view/configure_codehosting |
+ nothing">
+ <p style="margin-top: 10px;">
+ <a class="sprite maybe"
+ href="https://help.launchpad.net/Code">Getting started
+ with code hosting in Launchpad</a>.</p>
+
+ <p tal:condition="context/required:launchpad.Edit"
+ id="no-code-edit"
+ >
+ <a tal:condition="configure_codehosting"
+ tal:replace="structure configure_codehosting/fmt:link"/>
+ </p>
+ <p tal:define="menu context/menu:branches;
+ link menu/branch_visibility"
+ tal:condition="link/enabled"
+ tal:content="structure link/render"></p>
+ </div>
+ </div>
+
<p tal:condition="view/latest_release_with_download_files">
<img src="/@@/download"/> There are
<a tal:define="rooturl modules/canonical.launchpad.webapp.vhosts/allvhosts/configs/mainsite/rooturl"
=== modified file 'lib/lp/code/templates/product-branches.pt'
--- lib/lp/code/templates/product-branches.pt 2009-09-17 00:27:40 +0000
+++ lib/lp/code/templates/product-branches.pt 2010-09-22 20:23:46 +0000
@@ -3,40 +3,66 @@
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"
+ metal:use-macro="view/macro:page/main_side"
i18n:domain="launchpad"
>
<body>
-<div metal:fill-slot="main">
-
- <div style="float:right" id="floating-links"
- tal:define="menu context/menu:branches">
- <div tal:define="link menu/branch_add"
- tal:condition="link/enabled"
- tal:content="structure link/render" />
- <div tal:define="link menu/code_import"
- tal:condition="link/enabled"
- tal:content="structure link/render" />
- <div tal:define="link menu/branch_visibility"
- tal:condition="link/enabled"
- tal:content="structure link/render" />
- </div>
-
- <div id="private-policy" tal:condition="view/new_branches_are_private"
- class="informational message">
- New branches you create for <tal:name replace="context/displayname"/>
- are <strong>private</strong> initially.
- </div>
-
- <tal:branch-summary content="structure context/@@+branch-summary" />
-
- <tal:has-branches condition="view/branch_count"
- define="branches view/branches">
- <tal:branchlisting content="structure branches/@@+branch-listing" />
- </tal:has-branches>
-</div>
+ <metal:side fill-slot="side" tal:define="context_menu context/menu:context">
+ <div id="branch-portlet"
+ tal:condition="not: context/codehosting_usage/enumvalue:UNKNOWN">
+ <div id="privacy"
+ tal:define="are_private view/new_branches_are_private"
+ tal:attributes="class python: are_private and 'first portlet private' or 'first portlet public'">
+ <div tal:condition="not:view/new_branches_are_private" id="privacy-text">
+ <p>
+ New branches you create for <tal:name replace="context/displayname"/>
+ are <strong>public</strong> initially.
+ </p>
+ </div>
+ <div tal:condition="view/new_branches_are_private" id="privacy-text">
+ <p>
+ New branches you create for <tal:name replace="context/displayname"/>
+ are <strong>private</strong> initially.
+ </p>
+ </div>
+ </div>
+
+ <div id="involvement" class="portlet"
+ tal:define="menu context/menu:branches">
+ <ul class="involvement">
+ <li style="border: none">
+ <a href="+addbranch" class="menu-link-addbranch sprite code">
+ Register a branch
+ </a>
+ </li>
+ </ul>
+ <p tal:define="link menu/code_import"
+ tal:condition="link/enabled"
+ tal:content="structure link/render"></p>
+ <p tal:define="configure_codehosting view/configure_codehosting | nothing"
+ tal:condition="configure_codehosting"
+ tal:replace="structure configure_codehosting/fmt:link"></p>
+ <p tal:define="link menu/branch_visibility"
+ tal:condition="link/enabled"
+ tal:content="structure link/render"></p>
+ </div>
+
+ <div tal:replace="structure context/@@+portlet-product-codestatistics" />
+ </div>
+ </metal:side>
+
+ <tal:main metal:fill-slot="main">
+
+ <tal:branch-summary content="structure context/@@+branch-summary" />
+
+ <tal:has-branches condition="view/branch_count"
+ define="branches view/branches">
+ <tal:branchlisting content="structure branches/@@+branch-listing" />
+ </tal:has-branches>
+
+ </tal:main>
</body>
</html>
=== added file 'lib/lp/code/templates/product-portlet-codestatistics-content.pt'
--- lib/lp/code/templates/product-portlet-codestatistics-content.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/code/templates/product-portlet-codestatistics-content.pt 2010-09-22 20:23:46 +0000
@@ -0,0 +1,60 @@
+<tal:portlet-product-codestatistics-content
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal">
+ <tal:comment condition="nothing">
+ The view/*_count|nothing expressions below are so that this
+ template can be rendered by a view that does not have count
+ information available.
+ </tal:comment>
+
+ <tr tal:define="menu context/menu:branches" class="code-links"
+ id="merge-counts">
+ <td class="code-count"
+ tal:define="count menu/active_review_count"
+ tal:content="count" />
+ <td>
+ <tal:link
+ define="link menu/active_reviews"
+ replace="structure link/render"
+ />
+ </td>
+ </tr>
+
+ <tr class="code-links" id="branch-count-summary">
+ <td class="code-count"
+ tal:define="count view/branch_count"
+ tal:content="count" />
+ <td>
+ <tal:branches replace="view/branch_text">branches</tal:branches
+ ><tal:has-branches condition="view/branch_count">
+ owned by
+ <tal:individuals condition="view/person_owner_count">
+ <tal:owners content="view/person_owner_count">42</tal:owners>
+ <tal:people replace="view/person_text">people</tal:people
+ ></tal:individuals
+ ><tal:teams condition="view/team_owner_count">
+ <tal:individuals condition="view/person_owner_count">
+ and
+ </tal:individuals>
+ <tal:toc content="view/team_owner_count">1</tal:toc>
+ <tal:people replace="view/team_text">team</tal:people
+ ></tal:teams></tal:has-branches>
+ </td>
+ </tr>
+
+ <tr class="code-links">
+ <td class="code-count"
+ tal:define="count view/commit_count"
+ tal:content="count" />
+ <td>
+ <tal:commits replace="view/commit_text">commits</tal:commits>
+ <tal:has-committers condition="view/committer_count">
+ by
+ <tal:cc content="view/committer_count">4</tal:cc>
+ <tal:people replace="view/committer_text">people</tal:people>
+ </tal:has-committers>
+ in the last month
+ </td>
+ </tr>
+
+</tal:portlet-product-codestatistics-content>
=== added file 'lib/lp/code/templates/product-portlet-codestatistics.pt'
--- lib/lp/code/templates/product-portlet-codestatistics.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/code/templates/product-portlet-codestatistics.pt 2010-09-22 20:23:46 +0000
@@ -0,0 +1,11 @@
+<div
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ class="portlet" id="portlet-product-codestatistics">
+
+ <table class="code-links">
+ <tbody id="portlet-product-codestatistics"
+ tal:content="structure context/@@+portlet-product-codestatistics-content" />
+ </table>
+</div>
=== modified file 'lib/lp/registry/browser/pillar.py'
--- lib/lp/registry/browser/pillar.py 2010-09-13 12:09:30 +0000
+++ lib/lp/registry/browser/pillar.py 2010-09-22 20:23:46 +0000
@@ -79,7 +79,7 @@
def submit_code(self):
if self.pillar.codehosting_usage in [
- ServiceUsage.LAUNCHPAD,
+ ServiceUsage.LAUNCHPAD,
ServiceUsage.EXTERNAL,
]:
enabled = True
=== modified file 'lib/lp/registry/browser/product.py'
--- lib/lp/registry/browser/product.py 2010-09-15 16:03:14 +0000
+++ lib/lp/registry/browser/product.py 2010-09-22 20:23:46 +0000
@@ -468,8 +468,6 @@
# Add the branch configuration in separately.
set_branch = series_menu['set_branch']
set_branch.text = 'Configure project branch'
- set_branch.configured = (
- )
config_list.append(
dict(link=set_branch,
configured=config_statuses['configure_codehosting']))
@@ -478,11 +476,8 @@
@property
def registration_completeness(self):
"""The percent complete for registration."""
- configured = 0
config_statuses = self.configuration_states
- for key, value in config_statuses.items():
- if value:
- configured += 1
+ configured = sum([1 for v in config_statuses.values() if v])
scale = 100
done = int(float(configured) / len(config_statuses) * scale)
undone = scale - done
@@ -1405,9 +1400,9 @@
def setUpFields(self):
super(ProductConfigureBase, self).setUpFields()
if self.usage_fieldname is not None:
- # The usage fields are shared among pillars. But when referring to
- # an individual object in Launchpad it is better to call it by its
- # real name, i.e. 'project' instead of 'pillar'.
+ # The usage fields are shared among pillars. But when referring
+ # to an individual object in Launchpad it is better to call it by
+ # its real name, i.e. 'project' instead of 'pillar'.
usage_field = self.form_fields.get(self.usage_fieldname)
if usage_field:
usage_field.custom_widget = CustomWidgetFactory(
=== modified file 'lib/lp/registry/browser/tests/pillar-views.txt'
--- lib/lp/registry/browser/tests/pillar-views.txt 2010-09-16 13:30:18 +0000
+++ lib/lp/registry/browser/tests/pillar-views.txt 2010-09-22 20:23:46 +0000
@@ -182,6 +182,17 @@
>>> print view.codehosting_usage.name
LAUNCHPAD
+ >>> from lp.code.enums import BranchType
+ >>> remote = factory.makeProduct()
+ >>> branch = factory.makeProductBranch(product=remote,
+ ... branch_type=BranchType.REMOTE)
+ >>> remote.official_codehosting
+ False
+ >>> view = create_view(remote, '+get-involved')
+ >>> print view.codehosting_usage.name
+ UNKNOWN
+
+
Project groups cannot make links to register a branch, so
official_codehosting is always false.
=== modified file 'lib/lp/registry/model/product.py'
--- lib/lp/registry/model/product.py 2010-09-14 15:04:06 +0000
+++ lib/lp/registry/model/product.py 2010-09-22 20:23:46 +0000
@@ -49,9 +49,6 @@
SQLBase,
sqlvalues,
)
-from canonical.launchpad.components.decoratedresultset import (
- DecoratedResultSet,
- )
from canonical.launchpad.interfaces.launchpad import (
IHasIcon,
IHasLogo,
@@ -404,7 +401,8 @@
return ServiceUsage.UNKNOWN
elif self.development_focus.branch.branch_type == BranchType.HOSTED:
return ServiceUsage.LAUNCHPAD
- elif self.development_focus.branch.branch_type == BranchType.MIRRORED:
+ elif self.development_focus.branch.branch_type in (
+ BranchType.MIRRORED, BranchType.REMOTE):
return ServiceUsage.EXTERNAL
return ServiceUsage.NOT_APPLICABLE
=== modified file 'lib/lp/registry/tests/test_service_usage.py'
--- lib/lp/registry/tests/test_service_usage.py 2010-09-21 14:47:26 +0000
+++ lib/lp/registry/tests/test_service_usage.py 2010-09-22 20:23:46 +0000
@@ -8,6 +8,7 @@
from canonical.testing import DatabaseFunctionalLayer
from lp.app.enums import ServiceUsage
+from lp.code.enums import BranchType
from lp.testing import (
login_person,
TestCaseWithFactory,
@@ -56,13 +57,6 @@
True,
self.target.official_answers)
- def test_codehosting_usage(self):
- # Only test get for codehosting; this has no setter because the
- # state is derived from other data.
- self.assertEqual(
- ServiceUsage.UNKNOWN,
- self.target.codehosting_usage)
-
def test_translations_usage_no_data(self):
# By default, we don't know anything about a target
self.assertEqual(
@@ -194,6 +188,42 @@
super(TestProductUsageEnums, self).setUp()
self.target = self.factory.makeProduct()
+ def test_codehosting_unknown(self):
+ # A default product has UNKNOWN usage.
+ self.assertEqual(
+ ServiceUsage.UNKNOWN,
+ self.target.codehosting_usage)
+
+ def test_codehosting_mirrored_branch(self):
+ # A mirrored branch is EXTERNAL.
+ login_person(self.target.owner)
+ self.target.development_focus.branch = self.factory.makeProductBranch(
+ product=self.target,
+ branch_type=BranchType.MIRRORED)
+ self.assertEqual(
+ ServiceUsage.EXTERNAL,
+ self.target.codehosting_usage)
+
+ def test_codehosting_remote_branch(self):
+ # A remote branch is EXTERNAL.
+ login_person(self.target.owner)
+ self.target.development_focus.branch = self.factory.makeProductBranch(
+ product=self.target,
+ branch_type=BranchType.REMOTE)
+ self.assertEqual(
+ ServiceUsage.EXTERNAL,
+ self.target.codehosting_usage)
+
+ def test_codehosting_hosted_branch(self):
+ # A branch on Launchpad is HOSTED.
+ login_person(self.target.owner)
+ self.target.development_focus.branch = self.factory.makeProductBranch(
+ product=self.target,
+ branch_type=BranchType.HOSTED)
+ self.assertEqual(
+ ServiceUsage.LAUNCHPAD,
+ self.target.codehosting_usage)
+
class TestProductSeriesUsageEnums(
TestCaseWithFactory,
Follow ups