launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28845
[Merge] ~cjwatson/launchpad:black-code into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:black-code into launchpad:master.
Commit message:
lp.code: Apply black
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/427106
--
The attached diff has been truncated due to its size.
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:black-code into launchpad:master.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 1a2bd19..1ccbf84 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -70,3 +70,5 @@ c606443bdb2f342593c9a7c9437cb70c01f85f29
95cf83968d59453397dfbdcbe30556fc8004479a
# apply black to lp.charms
a6bed71f3d2fdbceae20c2d435c993e8bededdce
+# apply black to lp.code
+94d8e9842b7c92f3f9b7f514fb49ebdc9af7e413
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d9fb758..c17b9a5 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -49,6 +49,7 @@ repos:
|bugs
|buildmaster
|charms
+ |code
)/
- repo: https://github.com/PyCQA/isort
rev: 5.9.2
@@ -74,6 +75,7 @@ repos:
|bugs
|buildmaster
|charms
+ |code
)/
- id: isort
alias: isort-black
@@ -89,6 +91,7 @@ repos:
|bugs
|buildmaster
|charms
+ |code
)/
- repo: https://github.com/PyCQA/flake8
rev: 3.9.2
diff --git a/lib/lp/code/adapters/branch.py b/lib/lp/code/adapters/branch.py
index 614eb2d..fd62fa5 100644
--- a/lib/lp/code/adapters/branch.py
+++ b/lib/lp/code/adapters/branch.py
@@ -7,7 +7,7 @@ __all__ = [
"BranchDelta",
"BranchMergeProposalDelta",
"BranchMergeProposalNoPreviewDiffDelta",
- ]
+]
from contextlib import contextmanager
@@ -17,13 +17,9 @@ from lazr.lifecycle.objectdelta import ObjectDelta
from zope.event import notify
from zope.interface import implementer
-from lp.code.interfaces.branch import (
- IBranch,
- IBranchDelta,
- )
+from lp.code.interfaces.branch import IBranch, IBranchDelta
from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
-
# XXX: thumper 2006-12-20: This needs to be extended
# to cover bugs and specs linked and unlinked, as
# well as landing target when it is added to the UI
@@ -33,15 +29,23 @@ from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
class BranchDelta:
"""See IBranchDelta."""
- delta_values = ('name', 'title', 'url', 'lifecycle_status')
+ delta_values = ("name", "title", "url", "lifecycle_status")
- new_values = ('summary', 'whiteboard')
+ new_values = ("summary", "whiteboard")
interface = IBranch
- def __init__(self, branch, user,
- name=None, title=None, summary=None, url=None,
- whiteboard=None, lifecycle_status=None):
+ def __init__(
+ self,
+ branch,
+ user,
+ name=None,
+ title=None,
+ summary=None,
+ url=None,
+ whiteboard=None,
+ lifecycle_status=None,
+ ):
self.branch = branch
self.user = user
@@ -78,15 +82,15 @@ class BranchMergeProposalDelta:
"""Represent changes made to a BranchMergeProposal."""
delta_values = (
- 'registrant',
- 'queue_status',
- )
+ "registrant",
+ "queue_status",
+ )
new_values = (
- 'commit_message',
- 'whiteboard',
- 'description',
- 'preview_diff',
- )
+ "commit_message",
+ "whiteboard",
+ "description",
+ "preview_diff",
+ )
interface = IBranchMergeProposal
def __init__(self, **kwargs):
@@ -127,11 +131,14 @@ class BranchMergeProposalDelta:
merge_proposal_snapshot = klass.snapshot(merge_proposal)
yield
merge_proposal_delta = klass.construct(
- merge_proposal_snapshot, merge_proposal)
+ merge_proposal_snapshot, merge_proposal
+ )
if merge_proposal_delta is not None:
merge_proposal_event = ObjectModifiedEvent(
- merge_proposal, merge_proposal_snapshot,
- list(vars(merge_proposal_delta)))
+ merge_proposal,
+ merge_proposal_snapshot,
+ list(vars(merge_proposal_delta)),
+ )
notify(merge_proposal_event)
@@ -142,5 +149,7 @@ class BranchMergeProposalNoPreviewDiffDelta(BranchMergeProposalDelta):
"""
new_values = tuple(
- name for name in BranchMergeProposalDelta.new_values
- if name != "preview_diff")
+ name
+ for name in BranchMergeProposalDelta.new_values
+ if name != "preview_diff"
+ )
diff --git a/lib/lp/code/adapters/branchcollection.py b/lib/lp/code/adapters/branchcollection.py
index cfc4d89..14900ec 100644
--- a/lib/lp/code/adapters/branchcollection.py
+++ b/lib/lp/code/adapters/branchcollection.py
@@ -4,14 +4,14 @@
"""Adapters for different objects to branch collections."""
__all__ = [
- 'branch_collection_for_distribution',
- 'branch_collection_for_distro_series',
- 'branch_collection_for_person',
- 'branch_collection_for_product',
- 'branch_collection_for_project_group',
- 'branch_collection_for_source_package',
- 'branch_collection_for_distro_source_package',
- ]
+ "branch_collection_for_distribution",
+ "branch_collection_for_distro_series",
+ "branch_collection_for_person",
+ "branch_collection_for_product",
+ "branch_collection_for_project_group",
+ "branch_collection_for_source_package",
+ "branch_collection_for_distro_source_package",
+]
from zope.component import getUtility
@@ -59,4 +59,5 @@ def branch_collection_for_source_package(source_package):
def branch_collection_for_distro_source_package(distro_source_package):
"""Adapt a distro_source_package to a branch collection."""
return getUtility(IAllBranches).inDistributionSourcePackage(
- distro_source_package)
+ distro_source_package
+ )
diff --git a/lib/lp/code/adapters/gitcollection.py b/lib/lp/code/adapters/gitcollection.py
index 0a7b1ab..c0bb456 100644
--- a/lib/lp/code/adapters/gitcollection.py
+++ b/lib/lp/code/adapters/gitcollection.py
@@ -4,16 +4,16 @@
"""Adapters for different objects to Git repository collections."""
__all__ = [
- 'git_collection_for_distribution',
- 'git_collection_for_distro_source_package',
- 'git_collection_for_oci_project',
- 'git_collection_for_person',
- 'git_collection_for_person_distro_source_package',
- 'git_collection_for_person_oci_project',
- 'git_collection_for_person_product',
- 'git_collection_for_project',
- 'git_collection_for_project_group',
- ]
+ "git_collection_for_distribution",
+ "git_collection_for_distro_source_package",
+ "git_collection_for_oci_project",
+ "git_collection_for_person",
+ "git_collection_for_person_distro_source_package",
+ "git_collection_for_person_oci_project",
+ "git_collection_for_person_product",
+ "git_collection_for_project",
+ "git_collection_for_project_group",
+]
from zope.component import getUtility
@@ -39,7 +39,8 @@ def git_collection_for_distribution(distribution):
def git_collection_for_distro_source_package(distro_source_package):
"""Adapt a distro_source_package to a Git repository collection."""
return getUtility(IAllGitRepositories).inDistributionSourcePackage(
- distro_source_package)
+ distro_source_package
+ )
def git_collection_for_oci_project(oci_project):
@@ -64,13 +65,15 @@ def git_collection_for_person_distro_source_package(person_dsp):
collection."""
collection = getUtility(IAllGitRepositories).ownedBy(person_dsp.person)
collection = collection.inDistributionSourcePackage(
- person_dsp.distro_source_package)
+ person_dsp.distro_source_package
+ )
return collection
def git_collection_for_person_oci_project(person_oci_project):
"""Adapt a PersonOCIProject to a Git repository collection."""
collection = getUtility(IAllGitRepositories).ownedBy(
- person_oci_project.person)
+ person_oci_project.person
+ )
collection = collection.inOCIProject(person_oci_project.oci_project)
return collection
diff --git a/lib/lp/code/adapters/gitrepository.py b/lib/lp/code/adapters/gitrepository.py
index 085d51a..1687bf3 100644
--- a/lib/lp/code/adapters/gitrepository.py
+++ b/lib/lp/code/adapters/gitrepository.py
@@ -5,7 +5,7 @@
__all__ = [
"GitRepositoryDelta",
- ]
+]
from lazr.lifecycle.objectdelta import ObjectDelta
from zope.interface import implementer
@@ -13,21 +13,22 @@ from zope.interface import implementer
from lp.code.interfaces.gitrepository import (
IGitRepository,
IGitRepositoryDelta,
- )
+)
@implementer(IGitRepositoryDelta)
class GitRepositoryDelta:
"""See `IGitRepositoryDelta`."""
- delta_values = ('name', 'git_identity')
+ delta_values = ("name", "git_identity")
new_values = ()
interface = IGitRepository
- def __init__(self, repository, user, name=None, git_identity=None,
- activities=None):
+ def __init__(
+ self, repository, user, name=None, git_identity=None, activities=None
+ ):
self.repository = repository
self.user = user
@@ -44,8 +45,11 @@ class GitRepositoryDelta:
"""
delta = ObjectDelta(old_repository, new_repository)
delta.recordNewAndOld(klass.delta_values)
- activities = list(new_repository.getActivity(
- changed_after=old_repository.date_last_modified))
+ activities = list(
+ new_repository.getActivity(
+ changed_after=old_repository.date_last_modified
+ )
+ )
if delta.changes or activities:
changes = delta.changes
changes["repository"] = new_repository
diff --git a/lib/lp/code/adapters/revisioncache.py b/lib/lp/code/adapters/revisioncache.py
index 6cd8c08..7406adb 100644
--- a/lib/lp/code/adapters/revisioncache.py
+++ b/lib/lp/code/adapters/revisioncache.py
@@ -4,14 +4,14 @@
"""Adapters for different objects to a revision cache."""
__all__ = [
- 'revision_cache_for_distribution',
- 'revision_cache_for_distro_series',
- 'revision_cache_for_person',
- 'revision_cache_for_product',
- 'revision_cache_for_project_group',
- 'revision_cache_for_source_package',
- 'revision_cache_for_distro_source_package',
- ]
+ "revision_cache_for_distribution",
+ "revision_cache_for_distro_series",
+ "revision_cache_for_person",
+ "revision_cache_for_product",
+ "revision_cache_for_project_group",
+ "revision_cache_for_source_package",
+ "revision_cache_for_distro_source_package",
+]
from zope.component import getUtility
@@ -52,4 +52,5 @@ def revision_cache_for_source_package(source_package):
def revision_cache_for_distro_source_package(distro_source_package):
"""Adapt a distro_source_package to a revision cache."""
return getUtility(IRevisionCache).inDistributionSourcePackage(
- distro_source_package)
+ distro_source_package
+ )
diff --git a/lib/lp/code/adapters/tests/test_branch.py b/lib/lp/code/adapters/tests/test_branch.py
index b3292b5..4d83ca1 100644
--- a/lib/lp/code/adapters/tests/test_branch.py
+++ b/lib/lp/code/adapters/tests/test_branch.py
@@ -7,11 +7,7 @@ from lazr.lifecycle.event import ObjectModifiedEvent
from lp.code.adapters.branch import BranchMergeProposalDelta
from lp.code.enums import BranchMergeProposalStatus
-from lp.testing import (
- EventRecorder,
- login,
- TestCase,
- )
+from lp.testing import EventRecorder, TestCase, login
from lp.testing.factory import LaunchpadObjectFactory
from lp.testing.layers import LaunchpadFunctionalLayer
@@ -22,45 +18,52 @@ class TestBranchMergeProposalDelta(TestCase):
def setUp(self):
TestCase.setUp(self)
- login('foo.bar@xxxxxxxxxxxxx')
+ login("foo.bar@xxxxxxxxxxxxx")
self.factory = LaunchpadObjectFactory()
def test_snapshot(self):
"""Test that the snapshot method produces a reasonable snapshot"""
merge_proposal = self.factory.makeBranchMergeProposal()
- merge_proposal.commit_message = 'foo'
- merge_proposal.whiteboard = 'bar'
+ merge_proposal.commit_message = "foo"
+ merge_proposal.whiteboard = "bar"
snapshot = BranchMergeProposalDelta.snapshot(merge_proposal)
- self.assertEqual('foo', snapshot.commit_message)
- self.assertEqual('bar', snapshot.whiteboard)
+ self.assertEqual("foo", snapshot.commit_message)
+ self.assertEqual("bar", snapshot.whiteboard)
def test_noModification(self):
"""When there are no modifications, no delta should be returned."""
merge_proposal = self.factory.makeBranchMergeProposal()
old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal)
delta = BranchMergeProposalDelta.construct(
- old_merge_proposal, merge_proposal)
+ old_merge_proposal, merge_proposal
+ )
assert delta is None
def test_Modification(self):
"""When there are modifications, the delta reflects them."""
registrant = self.factory.makePerson(
- displayname='Baz Qux', email='baz.qux@xxxxxxxxxxx')
+ displayname="Baz Qux", email="baz.qux@xxxxxxxxxxx"
+ )
merge_proposal = self.factory.makeBranchMergeProposal(
- registrant=registrant)
+ registrant=registrant
+ )
old_merge_proposal = BranchMergeProposalDelta.snapshot(merge_proposal)
- merge_proposal.commit_message = 'Change foo into bar.'
- merge_proposal.description = 'Set the description.'
+ merge_proposal.commit_message = "Change foo into bar."
+ merge_proposal.description = "Set the description."
merge_proposal.markAsMerged()
delta = BranchMergeProposalDelta.construct(
- old_merge_proposal, merge_proposal)
+ old_merge_proposal, merge_proposal
+ )
assert delta is not None
- self.assertEqual('Change foo into bar.', delta.commit_message)
- self.assertEqual('Set the description.', delta.description)
+ self.assertEqual("Change foo into bar.", delta.commit_message)
+ self.assertEqual("Set the description.", delta.description)
self.assertEqual(
- {'old': BranchMergeProposalStatus.WORK_IN_PROGRESS,
- 'new': BranchMergeProposalStatus.MERGED},
- delta.queue_status)
+ {
+ "old": BranchMergeProposalStatus.WORK_IN_PROGRESS,
+ "new": BranchMergeProposalStatus.MERGED,
+ },
+ delta.queue_status,
+ )
def test_monitor(self):
"""\
@@ -83,5 +86,5 @@ class TestBranchMergeProposalDelta(TestCase):
self.assertIsInstance(event, ObjectModifiedEvent)
self.assertEqual(merge_proposal, event.object)
self.assertContentEqual(
- ["commit_message", "whiteboard"],
- event.edited_fields)
+ ["commit_message", "whiteboard"], event.edited_fields
+ )
diff --git a/lib/lp/code/adapters/tests/test_branchcollection.py b/lib/lp/code/adapters/tests/test_branchcollection.py
index ed66a77..a2fdbaa 100644
--- a/lib/lp/code/adapters/tests/test_branchcollection.py
+++ b/lib/lp/code/adapters/tests/test_branchcollection.py
@@ -26,6 +26,7 @@ class TestPersonProduct(TestCaseWithFactory):
self.factory.makeProductBranch(product=product)
self.factory.makeBranch(owner=person)
person_product_branch = self.factory.makeProductBranch(
- owner=person, product=product)
+ owner=person, product=product
+ )
branches = IBranchCollection(person_product).getBranches()
self.assertEqual([person_product_branch], [b for b in branches])
diff --git a/lib/lp/code/adapters/tests/test_gitrepository.py b/lib/lp/code/adapters/tests/test_gitrepository.py
index 9983274..94282d4 100644
--- a/lib/lp/code/adapters/tests/test_gitrepository.py
+++ b/lib/lp/code/adapters/tests/test_gitrepository.py
@@ -6,10 +6,7 @@ from testtools.matchers import MatchesStructure
from zope.interface import providedBy
from lp.code.adapters.gitrepository import GitRepositoryDelta
-from lp.testing import (
- person_logged_in,
- TestCaseWithFactory,
- )
+from lp.testing import TestCaseWithFactory, person_logged_in
from lp.testing.layers import LaunchpadFunctionalLayer
@@ -22,7 +19,8 @@ class TestGitRepositoryDelta(TestCaseWithFactory):
repository = self.factory.makeGitRepository(name="foo")
old_repository = Snapshot(repository, providing=providedBy(repository))
delta = GitRepositoryDelta.construct(
- old_repository, repository, repository.owner)
+ old_repository, repository, repository.owner
+ )
self.assertIsNone(delta)
def test_modification(self):
@@ -30,18 +28,23 @@ class TestGitRepositoryDelta(TestCaseWithFactory):
owner = self.factory.makePerson(name="person")
project = self.factory.makeProduct(name="project")
repository = self.factory.makeGitRepository(
- owner=owner, target=project, name="foo")
+ owner=owner, target=project, name="foo"
+ )
old_repository = Snapshot(repository, providing=providedBy(repository))
with person_logged_in(repository.owner):
repository.setName("bar", repository.owner)
delta = GitRepositoryDelta.construct(old_repository, repository, owner)
self.assertIsNotNone(delta)
- self.assertThat(delta, MatchesStructure.byEquality(
- name={
- "old": "foo",
- "new": "bar",
+ self.assertThat(
+ delta,
+ MatchesStructure.byEquality(
+ name={
+ "old": "foo",
+ "new": "bar",
},
- git_identity={
- "old": "lp:~person/project/+git/foo",
- "new": "lp:~person/project/+git/bar",
- }))
+ git_identity={
+ "old": "lp:~person/project/+git/foo",
+ "new": "lp:~person/project/+git/bar",
+ },
+ ),
+ )
diff --git a/lib/lp/code/browser/bazaar.py b/lib/lp/code/browser/bazaar.py
index 3c98b5f..9a65327 100644
--- a/lib/lp/code/browser/bazaar.py
+++ b/lib/lp/code/browser/bazaar.py
@@ -4,9 +4,9 @@
"""View support classes for the bazaar application."""
__all__ = [
- 'BazaarApplicationView',
- 'BazaarProductView',
- ]
+ "BazaarApplicationView",
+ "BazaarProductView",
+]
from datetime import datetime
@@ -14,25 +14,19 @@ import breezy
from zope.component import getUtility
from lp.code.enums import CodeImportReviewStatus
-from lp.code.interfaces.branch import (
- IBranchCloud,
- IBranchSet,
- )
+from lp.code.interfaces.branch import IBranchCloud, IBranchSet
from lp.code.interfaces.branchcollection import IAllBranches
from lp.code.interfaces.codeimport import ICodeImportSet
from lp.registry.interfaces.product import IProductSet
from lp.services.config import config
from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
- canonical_url,
- LaunchpadView,
- )
+from lp.services.webapp import LaunchpadView, canonical_url
from lp.services.webapp.authorization import precache_permission_for_objects
class BazaarApplicationView(LaunchpadView):
- page_title = 'Launchpad Branches'
+ page_title = "Launchpad Branches"
@property
def branch_count(self):
@@ -45,8 +39,11 @@ class BazaarApplicationView(LaunchpadView):
@property
def import_count(self):
- return getUtility(ICodeImportSet).search(
- review_status=CodeImportReviewStatus.REVIEWED).count()
+ return (
+ getUtility(ICodeImportSet)
+ .search(review_status=CodeImportReviewStatus.REVIEWED)
+ .count()
+ )
@property
def brz_version(self):
@@ -58,42 +55,55 @@ class BazaarApplicationView(LaunchpadView):
# Until there is an API to do this nicely, shove the launchpad.view
# permission into the request cache directly.
precache_permission_for_objects(
- self.request, 'launchpad.View', branches)
+ self.request, "launchpad.View", branches
+ )
return branches
@cachedproperty
def recently_changed_branches(self):
"""Return the five most recently changed branches."""
return self._precacheViewPermissions(
- list(getUtility(IBranchSet).getRecentlyChangedBranches(
- 5, visible_by_user=self.user)))
+ list(
+ getUtility(IBranchSet).getRecentlyChangedBranches(
+ 5, visible_by_user=self.user
+ )
+ )
+ )
@cachedproperty
def recently_imported_branches(self):
"""Return the five most recently imported branches."""
return self._precacheViewPermissions(
- list(getUtility(IBranchSet).getRecentlyImportedBranches(
- 5, visible_by_user=self.user)))
+ list(
+ getUtility(IBranchSet).getRecentlyImportedBranches(
+ 5, visible_by_user=self.user
+ )
+ )
+ )
@cachedproperty
def recently_registered_branches(self):
"""Return the five most recently registered branches."""
return self._precacheViewPermissions(
- list(getUtility(IBranchSet).getRecentlyRegisteredBranches(
- 5, visible_by_user=self.user)))
+ list(
+ getUtility(IBranchSet).getRecentlyRegisteredBranches(
+ 5, visible_by_user=self.user
+ )
+ )
+ )
@cachedproperty
def short_product_tag_cloud(self):
"""Show a preview of the product tag cloud."""
return BazaarProductView(None, None).products(
- num_products=config.launchpad.code_homepage_product_cloud_size)
+ num_products=config.launchpad.code_homepage_product_cloud_size
+ )
class ProductInfo:
-
def __init__(self, name, commits, author_count, size, elapsed):
self.name = name
- self.url = '/' + name
+ self.url = "/" + name
self.commits = commits
self.author_count = author_count
self.size = size
@@ -132,8 +142,7 @@ class ProductInfo:
elif self.elapsed_since_commit.days == 1:
commit = "last commit one day old"
else:
- commit = (
- "last commit %d days old" % self.elapsed_since_commit.days)
+ commit = "last commit %d days old" % self.elapsed_since_commit.days
return "%s by %s, %s" % (size, who, commit)
@@ -151,7 +160,7 @@ class BazaarProjectsRedirect(LaunchpadView):
class BazaarProductView(LaunchpadView):
"""Browser class for products gettable with Bazaar."""
- page_title = 'Projects with active branches'
+ page_title = "Projects with active branches"
def _make_distribution_map(self, values, percentile_map):
"""Given some values and a map of percentiles to other values, return
@@ -160,6 +169,7 @@ class BazaarProductView(LaunchpadView):
There *must* be a percentile_map entry for 1.0.
"""
+
def constrained_minimum(xs, a):
"""Return the smallest value of 'xs' strictly bigger than 'a'."""
return min(x for x in xs if x > a)
@@ -179,20 +189,20 @@ class BazaarProductView(LaunchpadView):
# is the first item of the tuple returned, and is guaranteed to be
# unique by the sql query.
product_info = sorted(
- getUtility(IBranchCloud).getProductsWithInfo(num_products))
+ getUtility(IBranchCloud).getProductsWithInfo(num_products)
+ )
if len(product_info) == 0:
return
now = datetime.today()
counts = sorted(list(zip(*product_info))[1])
size_mapping = {
- 0.2: 'smallest',
- 0.4: 'small',
- 0.6: 'medium',
- 0.8: 'large',
- 1.0: 'largest',
- }
- num_commits_to_size = self._make_distribution_map(
- counts, size_mapping)
+ 0.2: "smallest",
+ 0.4: "small",
+ 0.6: "medium",
+ 0.8: "large",
+ 1.0: "largest",
+ }
+ num_commits_to_size = self._make_distribution_map(counts, size_mapping)
for name, commits, author_count, last_revision_date in product_info:
size = num_commits_to_size[commits]
diff --git a/lib/lp/code/browser/branch.py b/lib/lp/code/browser/branch.py
index 0bdeb46..1442323 100644
--- a/lib/lp/code/browser/branch.py
+++ b/lib/lp/code/browser/branch.py
@@ -4,65 +4,51 @@
"""Branch views."""
__all__ = [
- 'BranchBreadcrumb',
- 'BranchContextMenu',
- 'BranchDeletionView',
- 'BranchEditStatusView',
- 'BranchEditView',
- 'BranchEditWhiteboardView',
- 'BranchReviewerEditView',
- 'BranchMirrorStatusView',
- 'BranchMirrorMixin',
- 'BranchNavigation',
- 'BranchEditMenu',
- 'BranchUpgradeView',
- 'BranchURL',
- 'BranchView',
- 'CodeEditOwnerMixin',
- 'RegisterBranchMergeProposalView',
- ]
+ "BranchBreadcrumb",
+ "BranchContextMenu",
+ "BranchDeletionView",
+ "BranchEditStatusView",
+ "BranchEditView",
+ "BranchEditWhiteboardView",
+ "BranchReviewerEditView",
+ "BranchMirrorStatusView",
+ "BranchMirrorMixin",
+ "BranchNavigation",
+ "BranchEditMenu",
+ "BranchUpgradeView",
+ "BranchURL",
+ "BranchView",
+ "CodeEditOwnerMixin",
+ "RegisterBranchMergeProposalView",
+]
from datetime import datetime
+import pytz
+import simplejson
from lazr.lifecycle.event import ObjectModifiedEvent
from lazr.lifecycle.snapshot import Snapshot
from lazr.restful.fields import Reference
-from lazr.restful.interface import (
- copy_field,
- use_template,
- )
+from lazr.restful.interface import copy_field, use_template
from lazr.uri import URI
-import pytz
-import simplejson
from zope.component import getUtility
from zope.event import notify
from zope.formlib import form
from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.widgets import TextAreaWidget
-from zope.interface import (
- implementer,
- Interface,
- providedBy,
- )
+from zope.interface import Interface, implementer, providedBy
from zope.publisher.interfaces import NotFound
from zope.publisher.interfaces.browser import IBrowserPublisher
-from zope.schema import (
- Bool,
- Choice,
- Text,
- )
-from zope.schema.vocabulary import (
- SimpleTerm,
- SimpleVocabulary,
- )
+from zope.schema import Bool, Choice, Text
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from lp import _
from lp.app.browser.informationtype import InformationTypePortletMixin
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.browser.lazrjs import EnumChoiceWidget
from lp.app.enums import InformationType
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
@@ -72,14 +58,11 @@ from lp.app.widgets.suggestion import TargetBranchWidget
from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
from lp.bugs.interfaces.bug import IBugSet
from lp.bugs.interfaces.bugbranch import IBugBranch
-from lp.bugs.interfaces.bugtask import (
- IBugTaskSet,
- UNRESOLVED_BUGTASK_STATUSES,
- )
+from lp.bugs.interfaces.bugtask import UNRESOLVED_BUGTASK_STATUSES, IBugTaskSet
from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
from lp.code.browser.branchmergeproposal import (
latest_proposals_for_each_branch,
- )
+)
from lp.code.browser.branchref import BranchRef
from lp.code.browser.codeimport import CodeImportTargetMixin
from lp.code.browser.decorations import DecoratedBranch
@@ -92,11 +75,8 @@ from lp.code.errors import (
BranchTargetError,
CannotUpgradeBranch,
InvalidBranchMergeProposal,
- )
-from lp.code.interfaces.branch import (
- IBranch,
- IBranchSet,
- )
+)
+from lp.code.interfaces.branch import IBranch, IBranchSet
from lp.code.interfaces.branchcollection import IAllBranches
from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
from lp.code.interfaces.branchtarget import IBranchTarget
@@ -108,50 +88,41 @@ from lp.services import searchbuilder
from lp.services.config import config
from lp.services.database.constants import UTC_NOW
from lp.services.features import getFeatureFlag
-from lp.services.feeds.browser import (
- BranchFeedLink,
- FeedsMixin,
- )
-from lp.services.helpers import (
- english_list,
- truncate_text,
- )
+from lp.services.feeds.browser import BranchFeedLink, FeedsMixin
+from lp.services.helpers import english_list, truncate_text
from lp.services.job.interfaces.job import JobStatus
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
NavigationMenu,
+ canonical_url,
+ enabled_with_permission,
stepthrough,
stepto,
- )
+)
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.services.webapp.breadcrumb import NameBreadcrumb
from lp.services.webapp.escaping import structured
from lp.services.webapp.interfaces import ICanonicalUrlData
from lp.services.webapp.publisher import DataDownloadView
from lp.services.webhooks.browser import WebhookTargetNavigationMixin
-from lp.snappy.browser.hassnaps import (
- HasSnapsMenuMixin,
- HasSnapsViewMixin,
- )
+from lp.snappy.browser.hassnaps import HasSnapsMenuMixin, HasSnapsViewMixin
from lp.translations.interfaces.translationtemplatesbuild import (
ITranslationTemplatesBuildSource,
- )
+)
@implementer(ICanonicalUrlData)
class BranchURL:
"""Branch URL creation rules."""
- rootsite = 'code'
+ rootsite = "code"
inside = None
def __init__(self, branch):
@@ -163,7 +134,6 @@ class BranchURL:
class BranchBreadcrumb(NameBreadcrumb):
-
@property
def inside(self):
return self.context.target.components[-1]
@@ -229,6 +199,7 @@ class BranchNavigation(WebhookTargetNavigationMixin, Navigation):
def traverse_translation_templates_build(self, id_string):
"""Traverses to a `TranslationTemplatesBuild`."""
from lp.soyuz.browser.build import get_build_by_id_str
+
ttb = get_build_by_id_str(ITranslationTemplatesBuildSource, id_string)
if ttb is None or ttb.branch != self.context:
return None
@@ -239,103 +210,117 @@ class BranchEditMenu(NavigationMenu):
"""Edit menu for IBranch."""
usedfor = IBranch
- facet = 'branches'
- title = 'Edit branch'
- links = (
- 'edit', 'reviewer', 'edit_whiteboard', 'webhooks', 'delete')
+ facet = "branches"
+ title = "Edit branch"
+ links = ("edit", "reviewer", "edit_whiteboard", "webhooks", "delete")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
- text = 'Change branch details'
- return Link('+edit', text, icon='edit')
+ text = "Change branch details"
+ return Link("+edit", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def delete(self):
- text = 'Delete branch'
- return Link('+delete', text, icon='trash-icon')
+ text = "Delete branch"
+ return Link("+delete", text, icon="trash-icon")
- @enabled_with_permission('launchpad.AnyPerson')
+ @enabled_with_permission("launchpad.AnyPerson")
def edit_whiteboard(self):
- text = 'Edit whiteboard'
+ text = "Edit whiteboard"
enabled = self.context.branch_type == BranchType.IMPORTED
- return Link(
- '+whiteboard', text, icon='edit', enabled=enabled)
+ return Link("+whiteboard", text, icon="edit", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def reviewer(self):
- text = 'Set branch reviewer'
- return Link('+reviewer', text, icon='edit')
+ text = "Set branch reviewer"
+ return Link("+reviewer", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def webhooks(self):
- text = 'Manage webhooks'
+ text = "Manage webhooks"
return Link(
- '+webhooks', text, icon='edit',
- enabled=bool(getFeatureFlag('webhooks.new.enabled')))
+ "+webhooks",
+ text,
+ icon="edit",
+ enabled=bool(getFeatureFlag("webhooks.new.enabled")),
+ )
class BranchContextMenu(ContextMenu, HasRecipesMenuMixin, HasSnapsMenuMixin):
"""Context menu for branches."""
usedfor = IBranch
- facet = 'branches'
+ facet = "branches"
links = [
- 'add_subscriber', 'browse_revisions', 'create_recipe', 'create_snap',
- 'link_bug', 'link_blueprint', 'register_merge', 'source',
- 'subscription', 'edit_status', 'edit_import', 'upgrade_branch',
- 'view_recipes', 'view_snaps', 'visibility']
+ "add_subscriber",
+ "browse_revisions",
+ "create_recipe",
+ "create_snap",
+ "link_bug",
+ "link_blueprint",
+ "register_merge",
+ "source",
+ "subscription",
+ "edit_status",
+ "edit_import",
+ "upgrade_branch",
+ "view_recipes",
+ "view_snaps",
+ "visibility",
+ ]
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit_status(self):
- text = 'Change branch status'
- return Link('+edit-status', text, icon='edit')
+ text = "Change branch status"
+ return Link("+edit-status", text, icon="edit")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def visibility(self):
"""Return the 'Set information type' Link."""
- text = 'Change information type'
- return Link('+edit-information-type', text)
+ text = "Change information type"
+ return Link("+edit-information-type", text)
def browse_revisions(self):
"""Return a link to the branch's revisions on codebrowse."""
- text = 'All revisions'
+ text = "All revisions"
enabled = self.context.code_is_browseable
- url = self.context.getCodebrowseUrl('changes')
+ url = self.context.getCodebrowseUrl("changes")
return Link(url, text, enabled=enabled)
- @enabled_with_permission('launchpad.AnyPerson')
+ @enabled_with_permission("launchpad.AnyPerson")
def subscription(self):
if self.context.hasSubscription(self.user):
- url = '+edit-subscription'
- text = 'Edit your subscription'
- icon = 'edit'
+ url = "+edit-subscription"
+ text = "Edit your subscription"
+ icon = "edit"
else:
- url = '+subscribe'
- text = 'Subscribe yourself'
- icon = 'add'
+ url = "+subscribe"
+ text = "Subscribe yourself"
+ icon = "add"
return Link(url, text, icon=icon)
- @enabled_with_permission('launchpad.AnyPerson')
+ @enabled_with_permission("launchpad.AnyPerson")
def add_subscriber(self):
- text = 'Subscribe someone else'
- return Link('+addsubscriber', text, icon='add')
+ text = "Subscribe someone else"
+ return Link("+addsubscriber", text, icon="add")
def register_merge(self):
- text = 'Propose for merging'
+ text = "Propose for merging"
enabled = (
- self.context.target.supports_merge_proposals and
- not self.context.branch_type == BranchType.IMPORTED)
- return Link('+register-merge', text, icon='add', enabled=enabled)
+ self.context.target.supports_merge_proposals
+ and not self.context.branch_type == BranchType.IMPORTED
+ )
+ return Link("+register-merge", text, icon="add", enabled=enabled)
def link_bug(self):
- text = 'Link a bug report'
- return Link('+linkbug', text, icon='add')
+ text = "Link a bug report"
+ return Link("+linkbug", text, icon="add")
def link_blueprint(self):
if list(self.context.getSpecificationLinks(self.user)):
- text = 'Link to another blueprint'
+ text = "Link to another blueprint"
else:
- text = 'Link to a blueprint'
+ text = "Link to a blueprint"
# XXX: JonathanLange 2009-05-13 spec=package-branches: Actually,
# distroseries can also have blueprints, so it makes sense to
# associate package-branches with them.
@@ -343,33 +328,35 @@ class BranchContextMenu(ContextMenu, HasRecipesMenuMixin, HasSnapsMenuMixin):
# Since the blueprints are only related to products, there is no
# point showing this link if the branch is junk.
enabled = self.context.product is not None
- return Link('+linkblueprint', text, icon='add', enabled=enabled)
+ return Link("+linkblueprint", text, icon="add", enabled=enabled)
def source(self):
"""Return a link to the branch's file listing on codebrowse."""
- text = 'Browse the code'
+ text = "Browse the code"
enabled = self.context.code_is_browseable
- url = self.context.getCodebrowseUrl('files')
- return Link(url, text, icon='info', enabled=enabled)
+ url = self.context.getCodebrowseUrl("files")
+ return Link(url, text, icon="info", enabled=enabled)
def edit_import(self):
- text = 'Edit import source or review import'
+ text = "Edit import source or review import"
enabled = (
- self.context.branch_type == BranchType.IMPORTED and
- check_permission('launchpad.Edit', self.context.code_import))
- return Link('+edit-import', text, icon='edit', enabled=enabled)
+ self.context.branch_type == BranchType.IMPORTED
+ and check_permission("launchpad.Edit", self.context.code_import)
+ )
+ return Link("+edit-import", text, icon="edit", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def upgrade_branch(self):
enabled = self.context.needs_upgrading
return Link(
- '+upgrade', 'Upgrade this branch', icon='edit', enabled=enabled)
+ "+upgrade", "Upgrade this branch", icon="edit", enabled=enabled
+ )
def create_recipe(self):
# You can't create a recipe for a private branch.
enabled = not self.context.private
- text = 'Create packaging recipe'
- return Link('+new-recipe', text, enabled=enabled, icon='add')
+ text = "Create packaging recipe"
+ return Link("+new-recipe", text, enabled=enabled, icon="add")
class BranchMirrorMixin:
@@ -384,7 +371,7 @@ class BranchMirrorMixin:
branch = self.branch
# If the user has edit permissions, then show the actual location.
- if branch.url is None or check_permission('launchpad.Edit', branch):
+ if branch.url is None or check_permission("launchpad.Edit", branch):
return branch.url
# XXX: Tim Penhey, 2008-05-30, bug 235916
@@ -392,23 +379,27 @@ class BranchMirrorMixin:
# 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.
- hosts = config.codehosting.private_mirror_hosts.split(',')
+ 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 "<private server>"
return branch.url
-class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
- LaunchpadView, HasSnapsViewMixin, CodeImportTargetMixin):
+class BranchView(
+ InformationTypePortletMixin,
+ FeedsMixin,
+ BranchMirrorMixin,
+ LaunchpadView,
+ HasSnapsViewMixin,
+ CodeImportTargetMixin,
+):
- feed_types = (
- BranchFeedLink,
- )
+ feed_types = (BranchFeedLink,)
@property
def page_title(self):
@@ -426,7 +417,8 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
authorised_people = [self.branch.owner]
if self.user is not None:
precache_permission_for_objects(
- self.request, "launchpad.LimitedView", authorised_people)
+ self.request, "launchpad.LimitedView", authorised_people
+ )
# Replace our context with a decorated branch, if it is not already
# decorated.
if not isinstance(self.context, DecoratedBranch):
@@ -457,10 +449,11 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
def has_metadata(self):
"""Return whether there is branch metadata to display."""
return (
- self.context.branch_format or
- self.context.repository_format or
- self.context.control_format or
- self.context.stacked_on)
+ self.context.branch_format
+ or self.context.repository_format
+ or self.context.control_format
+ or self.context.stacked_on
+ )
@property
def is_empty_directory(self):
@@ -492,12 +485,14 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
branch = self.context
if branch.branch_type != BranchType.HOSTED:
return False
- return check_permission('launchpad.Edit', branch)
+ return check_permission("launchpad.Edit", branch)
def user_can_download(self):
"""Whether the user can download this branch."""
- return (self.context.branch_type != BranchType.REMOTE and
- self.context.revision_count > 0)
+ return (
+ self.context.branch_type != BranchType.REMOTE
+ and self.context.revision_count > 0
+ )
@cachedproperty
def landing_targets(self):
@@ -515,8 +510,11 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
def landing_candidates(self):
"""Return a decorated list of landing candidates."""
candidates = self.context.getPrecachedLandingCandidates(self.user)
- return [proposal for proposal in candidates
- if check_permission('launchpad.View', proposal)]
+ return [
+ proposal
+ for proposal in candidates
+ if check_permission("launchpad.View", proposal)
+ ]
@property
def recipes_link(self):
@@ -524,17 +522,18 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
count = self.context.recipes.count()
if count == 0:
# Nothing to link to.
- return 'No recipes using this branch.'
+ return "No recipes using this branch."
elif count == 1:
# Link to the single recipe.
return structured(
'<a href="%s">1 recipe</a> using this branch.',
- canonical_url(self.context.recipes.one())).escapedtext
+ canonical_url(self.context.recipes.one()),
+ ).escapedtext
else:
# Link to a recipe listing.
return structured(
- '<a href="+recipes">%s recipes</a> using this branch.',
- count).escapedtext
+ '<a href="+recipes">%s recipes</a> using this branch.', count
+ ).escapedtext
@property
def is_imported(self):
@@ -553,11 +552,11 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
def _getBranchCountText(self, count):
"""Help to show user friendly text."""
if count == 0:
- return 'No branches'
+ return "No branches"
elif count == 1:
- return '1 branch'
+ return "1 branch"
else:
- return '%s branches' % count
+ return "%s branches" % count
@cachedproperty
def dependent_branch_count_text(self):
@@ -571,15 +570,21 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
@cachedproperty
def dependent_branches(self):
- return [branch for branch in self.context.dependent_branches
- if check_permission('launchpad.View', branch)]
+ return [
+ branch
+ for branch in self.context.dependent_branches
+ if check_permission("launchpad.View", branch)
+ ]
@cachedproperty
def no_merges(self):
"""Return true if there are no pending merges"""
- return (len(self.landing_targets) +
- len(self.landing_candidates) +
- len(self.dependent_branches) == 0)
+ return (
+ len(self.landing_targets)
+ + len(self.landing_candidates)
+ + len(self.dependent_branches)
+ == 0
+ )
@property
def show_rescan_link(self):
@@ -598,14 +603,14 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
status_filter = searchbuilder.any(*UNRESOLVED_BUGTASK_STATUSES)
else:
status_filter = None
- return list(self.context.getLinkedBugTasks(
- self.user, status_filter))
+ return list(self.context.getLinkedBugTasks(self.user, status_filter))
@cachedproperty
def revision_info(self):
collection = getUtility(IAllBranches).visibleByUser(self.user)
return collection.getExtendedRevisionDetails(
- self.user, self.context.latest_revisions)
+ self.user, self.context.latest_revisions
+ )
@property
def show_merge_links(self):
@@ -631,8 +636,11 @@ class BranchView(InformationTypePortletMixin, FeedsMixin, BranchMirrorMixin,
def status_widget(self):
"""The config to configure the ChoiceSource JS widget."""
return EnumChoiceWidget(
- self.context.branch, IBranch['lifecycle_status'],
- header='Change status to', css_class_prefix='branchstatus')
+ self.context.branch,
+ IBranch["lifecycle_status"],
+ header="Change status to",
+ css_class_prefix="branchstatus",
+ )
@property
def spec_links(self):
@@ -645,7 +653,7 @@ class BranchRescanView(LaunchpadEditFormView):
field_names = []
- @action('Rescan', name='rescan')
+ @action("Rescan", name="rescan")
def rescan(self, action, data):
self.context.unscan(rescan=True)
self.request.response.addNotification("Branch scan scheduled")
@@ -679,17 +687,19 @@ class BranchEditFormView(LaunchpadEditFormView):
InformationType.USERDATA,
InformationType.PROPRIETARY,
InformationType.EMBARGOED,
- )
+ )
# XXX Once Branch Visibility Policies are removed, we only want to
# show Private (USERDATA) if the branch is linked to such a bug.
hidden_types = (
# InformationType.USERDATA,
- )
+ )
if set(allowed_types).intersection(hidden_types):
params = BugTaskSearchParams(
- user=self.user, linked_branches=self.context.id,
- information_type=hidden_types)
+ user=self.user,
+ linked_branches=self.context.id,
+ information_type=hidden_types,
+ )
if not getUtility(IBugTaskSet).searchBugIds(params).is_empty():
shown_types += hidden_types
@@ -711,29 +721,40 @@ class BranchEditFormView(LaunchpadEditFormView):
attribute, but in this case we actually want the user to be
able to edit it.
"""
- use_template(IBranch, include=[
- 'name',
- 'url',
- 'description',
- 'lifecycle_status',
- 'whiteboard',
- ])
+
+ use_template(
+ IBranch,
+ include=[
+ "name",
+ "url",
+ "description",
+ "lifecycle_status",
+ "whiteboard",
+ ],
+ )
information_type = copy_field(
- IBranch['information_type'], readonly=False,
- vocabulary=InformationTypeVocabulary(types=info_types))
- reviewer = copy_field(IBranch['reviewer'], required=True)
- owner = copy_field(IBranch['owner'], readonly=False)
+ IBranch["information_type"],
+ readonly=False,
+ vocabulary=InformationTypeVocabulary(types=info_types),
+ )
+ reviewer = copy_field(IBranch["reviewer"], required=True)
+ owner = copy_field(IBranch["owner"], readonly=False)
target = Reference(
- title=_('Branch target'), required=True,
+ title=_("Branch target"),
+ required=True,
schema=IBranchTarget,
- description=_('The project (if any) this branch pertains to. '
- 'If no project is specified, then it is a personal '
- 'branch'))
+ description=_(
+ "The project (if any) this branch pertains to. "
+ "If no project is specified, then it is a personal "
+ "branch"
+ ),
+ )
+
return BranchEditSchema
@property
def page_title(self):
- return 'Edit %s' % self.context.displayname
+ return "Edit %s" % self.context.displayname
@property
def label(self):
@@ -744,8 +765,11 @@ class BranchEditFormView(LaunchpadEditFormView):
"""See `LaunchpadFormView`"""
return {self.schema: self.context}
- @action('Change Branch', name='change',
- failure=LaunchpadFormView.ajax_failure_handler)
+ @action(
+ "Change Branch",
+ name="change",
+ failure=LaunchpadFormView.ajax_failure_handler,
+ )
def change_action(self, action, data):
# If the owner or product has changed, add an explicit notification.
# We take our own snapshot here to make sure that the snapshot records
@@ -754,50 +778,58 @@ class BranchEditFormView(LaunchpadEditFormView):
# sent in updateContextFromData.
changed = False
branch_before_modification = Snapshot(
- self.context, providing=providedBy(self.context))
- if 'owner' in data:
- new_owner = data.pop('owner')
+ self.context, providing=providedBy(self.context)
+ )
+ if "owner" in data:
+ new_owner = data.pop("owner")
if new_owner != self.context.owner:
self.context.setOwner(new_owner, self.user)
changed = True
self.request.response.addNotification(
"The branch owner has been changed to %s (%s)"
- % (new_owner.displayname, new_owner.name))
- if 'private' in data:
+ % (new_owner.displayname, new_owner.name)
+ )
+ if "private" in data:
# Read only for display.
- data.pop('private')
+ data.pop("private")
# We must process information type before target so that the any new
# information type is valid for the target.
- if 'information_type' in data:
- information_type = data.pop('information_type')
+ if "information_type" in data:
+ information_type = data.pop("information_type")
self.context.transitionToInformationType(
- information_type, self.user)
- if 'target' in data:
- target = data.pop('target')
- existing_junk = self.context.target.name == '+junk'
- same_junk_status = target == '+junk' and existing_junk
- if target == '+junk':
+ information_type, self.user
+ )
+ if "target" in data:
+ target = data.pop("target")
+ existing_junk = self.context.target.name == "+junk"
+ same_junk_status = target == "+junk" and existing_junk
+ if target == "+junk":
target = None
if not same_junk_status or (
- target is not None and target != self.context.target):
+ target is not None and target != self.context.target
+ ):
try:
self.context.setTarget(self.user, project=target)
except BranchTargetError as e:
- self.setFieldError('target', e.args[0])
+ self.setFieldError("target", e.args[0])
return
changed = True
if target:
self.request.response.addNotification(
"The branch target has been changed to %s (%s)"
- % (target.displayname, target.name))
+ % (target.displayname, target.name)
+ )
else:
self.request.response.addNotification(
"This branch is now a personal branch for %s (%s)"
- % (self.context.owner.displayname,
- self.context.owner.name))
- if 'reviewer' in data:
- reviewer = data.pop('reviewer')
+ % (
+ self.context.owner.displayname,
+ self.context.owner.name,
+ )
+ )
+ if "reviewer" in data:
+ reviewer = data.pop("reviewer")
if reviewer != self.context.code_reviewer:
if reviewer == self.context.owner:
# Clear the reviewer if set to the same as the owner.
@@ -813,15 +845,19 @@ class BranchEditFormView(LaunchpadEditFormView):
# Notify the object has changed with the snapshot that was taken
# earler.
field_names = [
- form_field.__name__ for form_field in self.form_fields]
- notify(ObjectModifiedEvent(
- self.context, branch_before_modification, field_names))
+ form_field.__name__ for form_field in self.form_fields
+ ]
+ notify(
+ ObjectModifiedEvent(
+ self.context, branch_before_modification, field_names
+ )
+ )
# Only specify that the context was modified if there
# was in fact a change.
self.context.date_last_modified = UTC_NOW
if self.request.is_ajax:
- return ''
+ return ""
@property
def next_url(self):
@@ -838,19 +874,19 @@ class BranchEditFormView(LaunchpadEditFormView):
class BranchEditWhiteboardView(BranchEditFormView):
"""A view for editing the whiteboard only."""
- field_names = ['whiteboard']
+ field_names = ["whiteboard"]
class BranchEditStatusView(BranchEditFormView):
"""A view for editing the lifecycle status only."""
- field_names = ['lifecycle_status']
+ field_names = ["lifecycle_status"]
class BranchEditInformationTypeView(BranchEditFormView):
"""A view for editing the information type only."""
- field_names = ['information_type']
+ field_names = ["information_type"]
class BranchMirrorStatusView(LaunchpadFormView):
@@ -873,8 +909,9 @@ class BranchMirrorStatusView(LaunchpadFormView):
return False
else:
celebs = getUtility(ILaunchpadCelebrities)
- return (self.user.inTeam(self.context.owner) or
- self.user.inTeam(celebs.admin))
+ return self.user.inTeam(self.context.owner) or self.user.inTeam(
+ celebs.admin
+ )
@property
def mirror_of_ssh(self):
@@ -882,7 +919,7 @@ class BranchMirrorStatusView(LaunchpadFormView):
if not self.context.url:
return False # not a mirror branch
uri = URI(self.context.url)
- return uri.scheme in ('sftp', 'bzr+ssh')
+ return uri.scheme in ("sftp", "bzr+ssh")
@property
def in_mirror_queue(self):
@@ -907,8 +944,9 @@ class BranchMirrorStatusView(LaunchpadFormView):
message = self.context.mirror_status_message
if len(message) <= self.MAXIMUM_STATUS_MESSAGE_LENGTH:
return message
- return truncate_text(
- message, self.MAXIMUM_STATUS_MESSAGE_LENGTH) + ' ...'
+ return (
+ truncate_text(message, self.MAXIMUM_STATUS_MESSAGE_LENGTH) + " ..."
+ )
@property
def show_mirror_failure(self):
@@ -923,7 +961,7 @@ class BranchMirrorStatusView(LaunchpadFormView):
def next_url(self):
return canonical_url(self.context)
- @action('Try again', name='try-again')
+ @action("Try again", name="try-again")
def retry(self, action, data):
self.context.requestMirror()
@@ -936,7 +974,7 @@ class BranchDeletionView(LaunchpadFormView):
@property
def page_title(self):
- return 'Delete branch %s' % self.context.displayname
+ return "Delete branch %s" % self.context.displayname
label = page_title
@@ -948,8 +986,9 @@ class BranchDeletionView(LaunchpadFormView):
"""
reqs = []
for item, (operation, reason) in self.context.deletionRequirements(
- eager_load=True).items():
- allowed = check_permission('launchpad.Edit', item)
+ eager_load=True
+ ).items():
+ allowed = check_permission("launchpad.Edit", item)
reqs.append((item, operation, reason, allowed))
return reqs
@@ -961,9 +1000,9 @@ class BranchDeletionView(LaunchpadFormView):
def stacked_branches_text(self):
"""Cache a count of the branches stacked on this."""
if self.stacked_branches_count == 1:
- return _('branch')
+ return _("branch")
else:
- return _('branches')
+ return _("branches")
def all_permitted(self):
"""Return True if all deletion requirements are permitted, else False.
@@ -973,11 +1012,24 @@ class BranchDeletionView(LaunchpadFormView):
# Not permitted if there are any branches stacked on this.
if self.stacked_branches_count > 0:
return False
- return len([item for item, action, reason, allowed in
- self.display_deletion_requirements if not allowed]) == 0
+ return (
+ len(
+ [
+ item
+ for item, action, reason, allowed in (
+ self.display_deletion_requirements
+ )
+ if not allowed
+ ]
+ )
+ == 0
+ )
- @action('Delete', name='delete_branch',
- condition=lambda x, y: x.all_permitted())
+ @action(
+ "Delete",
+ name="delete_branch",
+ condition=lambda x, y: x.all_permitted(),
+ )
def delete_branch_action(self, action, data):
branch = self.context
if self.all_permitted():
@@ -989,7 +1041,8 @@ class BranchDeletionView(LaunchpadFormView):
self.request.response.addNotification(message)
else:
self.request.response.addNotification(
- "This branch cannot be deleted.")
+ "This branch cannot be deleted."
+ )
self.next_url = canonical_url(branch)
@property
@@ -999,19 +1052,24 @@ class BranchDeletionView(LaunchpadFormView):
The keys are 'delete' and 'alter'; the values are dicts of
'item', 'reason' and 'allowed'.
"""
- row_dict = {'delete': [], 'alter': [], 'break_link': []}
- for item, operation, reason, allowed in (
- self.display_deletion_requirements):
+ row_dict = {"delete": [], "alter": [], "break_link": []}
+ for (
+ item,
+ operation,
+ reason,
+ allowed,
+ ) in self.display_deletion_requirements:
if IBugBranch.providedBy(item):
- operation = 'break_link'
+ operation = "break_link"
elif ISpecificationBranch.providedBy(item):
- operation = 'break_link'
+ operation = "break_link"
elif IProductSeries.providedBy(item):
- operation = 'break_link'
- row = {'item': item,
- 'reason': reason,
- 'allowed': allowed,
- }
+ operation = "break_link"
+ row = {
+ "item": item,
+ "reason": reason,
+ "allowed": allowed,
+ }
row_dict[operation].append(row)
return row_dict
@@ -1028,7 +1086,7 @@ class BranchUpgradeView(LaunchpadFormView):
@property
def page_title(self):
- return 'Upgrade branch %s' % self.context.displayname
+ return "Upgrade branch %s" % self.context.displayname
@property
def next_url(self):
@@ -1036,7 +1094,7 @@ class BranchUpgradeView(LaunchpadFormView):
cancel_url = next_url
- @action('Upgrade', name='upgrade_branch')
+ @action("Upgrade", name="upgrade_branch")
def upgrade_branch_action(self, action, data):
try:
self.context.requestUpgrade(self.user)
@@ -1052,16 +1110,20 @@ class CodeEditOwnerMixin:
# If the user can administer the relevant object type, then they
# should be able to assign the ownership of the object to any valid
# person or team.
- if check_permission('launchpad.Admin', self.context):
- owner_field = self.schema['owner']
+ if check_permission("launchpad.Admin", self.context):
+ owner_field = self.schema["owner"]
any_owner_choice = Choice(
- __name__='owner', title=owner_field.title,
+ __name__="owner",
+ title=owner_field.title,
description=self.any_owner_description,
- required=True, vocabulary='ValidPersonOrTeam')
+ required=True,
+ vocabulary="ValidPersonOrTeam",
+ )
any_owner_field = form.Fields(
- any_owner_choice, render_context=self.render_context)
+ any_owner_choice, render_context=self.render_context
+ )
# Replace the normal owner field with a more permissive vocab.
- self.form_fields = self.form_fields.omit('owner')
+ self.form_fields = self.form_fields.omit("owner")
self.form_fields = any_owner_field + self.form_fields
else:
# For normal users, there is an edge case with package branches
@@ -1071,18 +1133,23 @@ class CodeEditOwnerMixin:
if not self.user.inTeam(self.context.owner):
vocab = UserTeamsParticipationPlusSelfVocabulary()
owner = self.context.owner
- terms = [SimpleTerm(
- owner, owner.name, owner.unique_displayname)]
+ terms = [
+ SimpleTerm(owner, owner.name, owner.unique_displayname)
+ ]
terms.extend([term for term in vocab])
- owner_field = self.schema['owner']
+ owner_field = self.schema["owner"]
owner_choice = Choice(
- __name__='owner', title=owner_field.title,
+ __name__="owner",
+ title=owner_field.title,
description=owner_field.description,
- required=True, vocabulary=SimpleVocabulary(terms))
+ required=True,
+ vocabulary=SimpleVocabulary(terms),
+ )
new_owner_field = form.Fields(
- owner_choice, render_context=self.render_context)
+ owner_choice, render_context=self.render_context
+ )
# Replace the normal owner field with a more permissive vocab.
- self.form_fields = self.form_fields.omit('owner')
+ self.form_fields = self.form_fields.omit("owner")
self.form_fields = new_owner_field + self.form_fields
@@ -1091,12 +1158,12 @@ class BranchEditView(CodeEditOwnerMixin, BranchEditFormView):
@property
def field_names(self):
- field_names = ['owner', 'name']
+ field_names = ["owner", "name"]
if not self.context.sourcepackagename:
- field_names.append('target')
- field_names.extend([
- 'information_type', 'url', 'description',
- 'lifecycle_status'])
+ field_names.append("target")
+ field_names.extend(
+ ["information_type", "url", "description", "lifecycle_status"]
+ )
return field_names
custom_widget_target = BranchTargetWidget
@@ -1105,15 +1172,16 @@ class BranchEditView(CodeEditOwnerMixin, BranchEditFormView):
any_owner_description = _(
"As an administrator you are able to assign this branch to any "
- "person or team.")
+ "person or team."
+ )
def setUpFields(self):
super().setUpFields()
branch = self.context
if branch.branch_type in (BranchType.HOSTED, BranchType.IMPORTED):
- self.form_fields = self.form_fields.omit('url')
+ self.form_fields = self.form_fields.omit("url")
- def _setBranchExists(self, existing_branch, field_name='name'):
+ def _setBranchExists(self, existing_branch, field_name="name"):
owner = existing_branch.owner
if owner == self.user:
prefix = "You already have"
@@ -1121,43 +1189,49 @@ class BranchEditView(CodeEditOwnerMixin, BranchEditFormView):
prefix = "%s already has" % owner.displayname
message = structured(
"%s a branch for <em>%s</em> called <em>%s</em>.",
- prefix, existing_branch.target.displayname,
- existing_branch.name)
+ prefix,
+ existing_branch.target.displayname,
+ existing_branch.name,
+ )
self.setFieldError(field_name, message)
def validate(self, data):
# Check that we're not moving a team branch to the +junk
# pseudo project.
- if 'name' in data:
+ if "name" in data:
# Only validate if the name has changed or the owner has changed.
- owner = data['owner']
- if ((data['name'] != self.context.name) or
- (owner != self.context.owner)):
+ owner = data["owner"]
+ if (data["name"] != self.context.name) or (
+ owner != self.context.owner
+ ):
# We only allow moving within the same branch target for now.
namespace = self.context.target.getNamespace(owner)
try:
namespace.validateMove(
- self.context, self.user, name=data['name'])
+ self.context, self.user, name=data["name"]
+ )
except BranchCreationForbidden:
self.addError(
- "%s is not allowed to own branches in %s." % (
- owner.displayname, self.context.target.displayname))
+ "%s is not allowed to own branches in %s."
+ % (owner.displayname, self.context.target.displayname)
+ )
except BranchExists as e:
self._setBranchExists(e.existing_branch)
# If the branch is a MIRRORED branch, then the url
# must be supplied, and if HOSTED the url must *not*
# be supplied.
- url = data.get('url')
+ url = data.get("url")
if self.context.branch_type == BranchType.MIRRORED:
if url is None:
# If the url is not set due to url validation errors,
# there will be an error set for it.
- error = self.getFieldError('url')
+ error = self.getFieldError("url")
if not error:
self.setFieldError(
- 'url',
- 'Branch URLs are required for Mirrored branches.')
+ "url",
+ "Branch URLs are required for Mirrored branches.",
+ )
else:
# We don't care about whether the URL is set for REMOTE branches,
# and the URL field is not shown for IMPORT or HOSTED branches.
@@ -1167,64 +1241,82 @@ class BranchEditView(CodeEditOwnerMixin, BranchEditFormView):
class BranchReviewerEditView(BranchEditFormView):
"""The view to set the review team."""
- field_names = ['reviewer']
+ field_names = ["reviewer"]
@property
def initial_values(self):
- return {'reviewer': self.context.code_reviewer}
+ return {"reviewer": self.context.code_reviewer}
class RegisterProposalSchema(Interface):
"""The schema to define the form for registering a new merge proposal."""
+
target_branch = Choice(
- title=_('Target branch'),
- vocabulary='Branch', required=True, readonly=True,
+ title=_("Target branch"),
+ vocabulary="Branch",
+ required=True,
+ readonly=True,
description=_(
- "The branch that the source branch will be merged into."))
+ "The branch that the source branch will be merged into."
+ ),
+ )
prerequisite_branch = Choice(
- title=_('Prerequisite branch'),
- vocabulary='Branch', required=False, readonly=False,
+ title=_("Prerequisite branch"),
+ vocabulary="Branch",
+ required=False,
+ readonly=False,
description=_(
- 'A branch that should be merged before this one. (Its changes'
- ' will not be shown in the diff.)'))
+ "A branch that should be merged before this one. (Its changes"
+ " will not be shown in the diff.)"
+ ),
+ )
comment = Text(
- title=_('Description of the change'), required=False,
- description=_('Describe what changes your branch introduces, '
- 'what bugs it fixes, or what features it implements. '
- 'Ideally include rationale and how to test. '
- 'You do not need to repeat information from the commit '
- 'message here.'))
+ title=_("Description of the change"),
+ required=False,
+ description=_(
+ "Describe what changes your branch introduces, "
+ "what bugs it fixes, or what features it implements. "
+ "Ideally include rationale and how to test. "
+ "You do not need to repeat information from the commit "
+ "message here."
+ ),
+ )
- reviewer = copy_field(
- ICodeReviewVoteReference['reviewer'], required=False)
+ reviewer = copy_field(ICodeReviewVoteReference["reviewer"], required=False)
review_type = copy_field(
- ICodeReviewVoteReference['review_type'],
- description='Lowercase keywords describing the type of review you '
- 'would like to be performed.')
+ ICodeReviewVoteReference["review_type"],
+ description="Lowercase keywords describing the type of review you "
+ "would like to be performed.",
+ )
- commit_message = IBranchMergeProposal['commit_message']
+ commit_message = IBranchMergeProposal["commit_message"]
needs_review = Bool(
- title=_("Needs review"), required=True, default=True,
- description=_(
- "Is the proposal ready for review now?"))
+ title=_("Needs review"),
+ required=True,
+ default=True,
+ description=_("Is the proposal ready for review now?"),
+ )
class RegisterBranchMergeProposalView(LaunchpadFormView):
"""The view to register new branch merge proposals."""
+
schema = RegisterProposalSchema
for_input = True
custom_widget_target_branch = TargetBranchWidget
custom_widget_commit_message = CustomWidgetFactory(
- TextAreaWidget, cssClass='comment-text')
+ TextAreaWidget, cssClass="comment-text"
+ )
custom_widget_comment = CustomWidgetFactory(
- TextAreaWidget, cssClass='comment-text')
+ TextAreaWidget, cssClass="comment-text"
+ )
- page_title = label = 'Propose branch for merging'
+ page_title = label = "Propose branch for merging"
@property
def cancel_url(self):
@@ -1233,63 +1325,77 @@ class RegisterBranchMergeProposalView(LaunchpadFormView):
def initialize(self):
"""Show a 404 if the branch target doesn't support proposals."""
if not self.context.target.supports_merge_proposals:
- raise NotFound(self.context, '+register-merge')
+ raise NotFound(self.context, "+register-merge")
LaunchpadFormView.initialize(self)
- @action('Propose Merge', name='register',
- failure=LaunchpadFormView.ajax_failure_handler)
+ @action(
+ "Propose Merge",
+ name="register",
+ failure=LaunchpadFormView.ajax_failure_handler,
+ )
def register_action(self, action, data):
"""Register the new branch merge proposal."""
registrant = self.user
source_branch = self.context
- target_branch = data['target_branch']
- prerequisite_branch = data.get('prerequisite_branch')
+ target_branch = data["target_branch"]
+ prerequisite_branch = data.get("prerequisite_branch")
review_requests = []
- reviewer = data.get('reviewer')
- review_type = data.get('review_type')
+ reviewer = data.get("reviewer")
+ review_type = data.get("review_type")
if reviewer is None:
reviewer = target_branch.code_reviewer
if reviewer is not None:
review_requests.append((reviewer, review_type))
- branch_names = [branch.unique_name
- for branch in [source_branch, target_branch]]
+ branch_names = [
+ branch.unique_name for branch in [source_branch, target_branch]
+ ]
visibility_info = getUtility(IBranchSet).getBranchVisibilityInfo(
- self.user, reviewer, branch_names)
- visible_branches = list(visibility_info['visible_branches'])
+ self.user, reviewer, branch_names
+ )
+ visible_branches = list(visibility_info["visible_branches"])
if self.request.is_ajax and len(visible_branches) < 2:
self.request.response.setStatus(400, "Branch Visibility")
- self.request.response.setHeader(
- 'Content-Type', 'application/json')
- return simplejson.dumps({
- 'person_name': visibility_info['person_name'],
- 'branches_to_check': branch_names,
- 'visible_branches': visible_branches,
- })
+ self.request.response.setHeader("Content-Type", "application/json")
+ return simplejson.dumps(
+ {
+ "person_name": visibility_info["person_name"],
+ "branches_to_check": branch_names,
+ "visible_branches": visible_branches,
+ }
+ )
try:
proposal = source_branch.addLandingTarget(
- registrant=registrant, merge_target=target_branch,
+ registrant=registrant,
+ merge_target=target_branch,
merge_prerequisite=prerequisite_branch,
- needs_review=data['needs_review'],
- description=data.get('comment'),
+ needs_review=data["needs_review"],
+ description=data.get("comment"),
review_requests=review_requests,
- commit_message=data.get('commit_message'))
+ commit_message=data.get("commit_message"),
+ )
if len(visible_branches) < 2:
- invisible_branches = [branch.unique_name
- for branch in [source_branch, target_branch]
- if branch.unique_name not in visible_branches]
+ invisible_branches = [
+ branch.unique_name
+ for branch in [source_branch, target_branch]
+ if branch.unique_name not in visible_branches
+ ]
self.request.response.addNotification(
- 'To ensure visibility, %s is now subscribed to: %s'
- % (visibility_info['person_name'],
- english_list(invisible_branches)))
+ "To ensure visibility, %s is now subscribed to: %s"
+ % (
+ visibility_info["person_name"],
+ english_list(invisible_branches),
+ )
+ )
# Success so we do a client redirect to the new mp page.
if self.request.is_ajax:
self.request.response.setStatus(201)
self.request.response.setHeader(
- 'Location', canonical_url(proposal))
+ "Location", canonical_url(proposal)
+ )
return None
else:
self.next_url = canonical_url(proposal)
@@ -1298,7 +1404,7 @@ class RegisterBranchMergeProposalView(LaunchpadFormView):
def validate(self, data):
source_branch = self.context
- target_branch = data.get('target_branch')
+ target_branch = data.get("target_branch")
# Make sure that the target branch is different from the context.
if target_branch is None:
@@ -1308,15 +1414,17 @@ class RegisterBranchMergeProposalView(LaunchpadFormView):
pass
elif source_branch == target_branch:
self.setFieldError(
- 'target_branch',
- "The target branch cannot be the same as the source branch.")
+ "target_branch",
+ "The target branch cannot be the same as the source branch.",
+ )
else:
# Make sure that the target_branch is in the same project.
if not target_branch.isBranchMergeable(source_branch):
self.setFieldError(
- 'target_branch',
- "This branch is not mergeable into %s." %
- target_branch.bzr_identity)
+ "target_branch",
+ "This branch is not mergeable into %s."
+ % target_branch.bzr_identity,
+ )
@implementer(IBrowserPublisher)
diff --git a/lib/lp/code/browser/branchlisting.py b/lib/lp/code/browser/branchlisting.py
index e4c0c47..f3cb9bd 100644
--- a/lib/lp/code/browser/branchlisting.py
+++ b/lib/lp/code/browser/branchlisting.py
@@ -4,58 +4,46 @@
"""Base class view for branch listings."""
__all__ = [
- 'BranchBadges',
- 'BranchListingView',
- 'DistributionBranchListingView',
- 'DistributionSourcePackageBranchesView',
- 'DistroSeriesBranchListingView',
- 'GroupedDistributionSourcePackageBranchesView',
- 'PersonBranchesMenu',
- 'PersonBranchesView',
- 'PersonCodeSummaryView',
- 'PersonTeamBranchesView',
- 'ProductBranchListingView',
- 'ProductBranchesMenu',
- 'ProductBranchesView',
- 'ProjectBranchesView',
- 'RecentlyChangedBranchesView',
- 'RecentlyImportedBranchesView',
- 'RecentlyRegisteredBranchesView',
- 'SourcePackageBranchesView',
- ]
+ "BranchBadges",
+ "BranchListingView",
+ "DistributionBranchListingView",
+ "DistributionSourcePackageBranchesView",
+ "DistroSeriesBranchListingView",
+ "GroupedDistributionSourcePackageBranchesView",
+ "PersonBranchesMenu",
+ "PersonBranchesView",
+ "PersonCodeSummaryView",
+ "PersonTeamBranchesView",
+ "ProductBranchListingView",
+ "ProductBranchesMenu",
+ "ProductBranchesView",
+ "ProjectBranchesView",
+ "RecentlyChangedBranchesView",
+ "RecentlyImportedBranchesView",
+ "RecentlyRegisteredBranchesView",
+ "SourcePackageBranchesView",
+]
from operator import attrgetter
from urllib.parse import parse_qs
from lazr.delegates import delegate_to
-from lazr.enum import (
- EnumeratedType,
- Item,
- )
+from lazr.enum import EnumeratedType, Item
from storm.expr import Desc
from zope.browserpage import ViewPageTemplateFile
from zope.component import getUtility
from zope.formlib import form
-from zope.interface import (
- implementer,
- Interface,
- )
+from zope.interface import Interface, implementer
from zope.schema import Choice
from lp import _
-from lp.app.browser.badge import (
- Badge,
- HasBadgeBase,
- )
+from lp.app.browser.badge import Badge, HasBadgeBase
from lp.app.browser.launchpadform import LaunchpadFormView
-from lp.app.enums import (
- PRIVATE_INFORMATION_TYPES,
- ServiceUsage,
- )
+from lp.app.enums import PRIVATE_INFORMATION_TYPES, ServiceUsage
from lp.app.widgets.itemswidgets import LaunchpadDropdownWidget
from lp.blueprints.interfaces.specificationbranch import (
ISpecificationBranchSet,
- )
+)
from lp.bugs.interfaces.bugbranch import IBugBranchSet
from lp.code.browser.branch import BranchMirrorMixin
from lp.code.browser.branchmergeproposallisting import ActiveReviewsView
@@ -65,14 +53,14 @@ from lp.code.enums import (
BranchLifecycleStatusFilter,
BranchListingSort,
BranchType,
- )
+)
from lp.code.interfaces.branch import (
- BzrIdentityMixin,
DEFAULT_BRANCH_STATUS_IN_LISTING,
+ BzrIdentityMixin,
IBranch,
IBranchBatchNavigator,
IBranchListingQueryOptimiser,
- )
+)
from lp.code.interfaces.branchcollection import IAllBranches
from lp.code.interfaces.branchnamespace import IBranchNamespacePolicy
from lp.code.interfaces.branchtarget import IBranchTarget
@@ -81,19 +69,16 @@ from lp.code.interfaces.revision import IRevisionSet
from lp.code.interfaces.revisioncache import IRevisionCache
from lp.code.interfaces.seriessourcepackagebranch import (
IFindOfficialBranchLinks,
- )
+)
from lp.registry.browser.product import (
ProductDownloadFileMixin,
SortSeriesMixin,
- )
-from lp.registry.interfaces.person import (
- IPerson,
- IPersonSet,
- )
+)
+from lp.registry.interfaces.person import IPerson, IPersonSet
from lp.registry.interfaces.personproduct import (
IPersonProduct,
IPersonProductFactory,
- )
+)
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.series import SeriesStatus
from lp.registry.interfaces.sourcepackage import ISourcePackageFactory
@@ -108,17 +93,13 @@ from lp.services.feeds.browser import (
ProductRevisionsFeedLink,
ProjectBranchesFeedLink,
ProjectRevisionsFeedLink,
- )
+)
from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
- ApplicationMenu,
- canonical_url,
- Link,
- )
+from lp.services.webapp import ApplicationMenu, Link, canonical_url
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.services.webapp.batching import TableBatchNavigator
from lp.services.webapp.publisher import LaunchpadView
@@ -133,13 +114,14 @@ class BranchBadges(HasBadgeBase):
def getBadge(self, badge_name):
"""See `IHasBadges`."""
if badge_name == "warning":
- return Badge('/@@/warning', '/@@/warning-large', '',
- 'Branch has errors')
+ return Badge(
+ "/@@/warning", "/@@/warning-large", "", "Branch has errors"
+ )
else:
return super().getBadge(badge_name)
-@delegate_to(IBranch, context='context')
+@delegate_to(IBranch, context="context")
class BranchListingItem(BzrIdentityMixin, BranchBadges):
"""A decorated branch.
@@ -148,9 +130,16 @@ class BranchListingItem(BzrIdentityMixin, BranchBadges):
prefetched by the view and decorate the branch.
"""
- def __init__(self, branch, last_commit, show_bug_badge,
- show_blueprint_badge, show_mp_badge,
- associated_product_series, suite_source_packages):
+ def __init__(
+ self,
+ branch,
+ last_commit,
+ show_bug_badge,
+ show_blueprint_badge,
+ show_mp_badge,
+ associated_product_series,
+ suite_source_packages,
+ ):
BranchBadges.__init__(self, branch)
self.last_commit = last_commit
self.show_bug_badge = show_bug_badge
@@ -169,8 +158,11 @@ class BranchListingItem(BzrIdentityMixin, BranchBadges):
@property
def active_series(self):
- return [series for series in self.associated_product_series
- if series.status != SeriesStatus.OBSOLETE]
+ return [
+ series
+ for series in self.associated_product_series
+ if series.status != SeriesStatus.OBSOLETE
+ ]
def isBugBadgeVisible(self):
return self.show_bug_badge
@@ -201,33 +193,40 @@ class BranchListingItem(BzrIdentityMixin, BranchBadges):
@property
def revision_codebrowse_link(self):
return self.context.getCodebrowseUrl(
- 'revision', str(self.context.revision_count))
+ "revision", str(self.context.revision_count)
+ )
def __repr__(self):
# For testing purposes.
- return '<BranchListingItem %r (%d)>' % (self.unique_name, self.id)
+ return "<BranchListingItem %r (%d)>" % (self.unique_name, self.id)
class PersonBranchCategory(EnumeratedType):
"""Choices for filtering lists of branches related to people."""
- OWNED = Item("""
+ OWNED = Item(
+ """
Owned
Show branches owned by the person or team.
- """)
+ """
+ )
- SUBSCRIBED = Item("""
+ SUBSCRIBED = Item(
+ """
Subscribed
Show branches subscribed to by the person or team.
- """)
+ """
+ )
- REGISTERED = Item("""
+ REGISTERED = Item(
+ """
Registered
Show branches registered by the person or team.
- """)
+ """
+ )
class IBranchListingFilter(Interface):
@@ -235,28 +234,36 @@ class IBranchListingFilter(Interface):
# Stats and status attributes
lifecycle = Choice(
- title=_('Lifecycle Filter'), vocabulary=BranchLifecycleStatusFilter,
+ title=_("Lifecycle Filter"),
+ vocabulary=BranchLifecycleStatusFilter,
default=BranchLifecycleStatusFilter.CURRENT,
description=_(
- "The author's assessment of the branch's maturity. "
- " Mature: recommend for production use."
- " Development: useful work that is expected to be merged eventually."
- " Experimental: not recommended for merging yet, and maybe ever."
- " Merged: integrated into mainline, of historical interest only."
- " Abandoned: no longer considered relevant by the author."
- " New: unspecified maturity."))
+ "The author's assessment of the branch's maturity. "
+ " Mature: recommend for production use."
+ " Development: useful work that is expected to be merged "
+ "eventually."
+ " Experimental: not recommended for merging yet, and maybe ever."
+ " Merged: integrated into mainline, of historical interest only."
+ " Abandoned: no longer considered relevant by the author."
+ " New: unspecified maturity."
+ ),
+ )
sort_by = Choice(
- title=_('ordered by'), vocabulary=BranchListingSort,
- default=BranchListingSort.LIFECYCLE)
+ title=_("ordered by"),
+ vocabulary=BranchListingSort,
+ default=BranchListingSort.LIFECYCLE,
+ )
class IPersonBranchListingFilter(IBranchListingFilter):
"""The schema for the branch listing filtering/ordering form for people."""
category = Choice(
- title=_('Category'), vocabulary=PersonBranchCategory,
- default=PersonBranchCategory.OWNED)
+ title=_("Category"),
+ vocabulary=PersonBranchCategory,
+ default=PersonBranchCategory.OWNED,
+ )
class BranchListingItemsMixin:
@@ -286,7 +293,8 @@ class BranchListingItemsMixin:
def product_series_map(self):
"""Return a map from branch id to a list of product series."""
series_resultset = self._query_optimiser.getProductSeriesForBranches(
- self._visible_branch_ids)
+ self._visible_branch_ids
+ )
result = {}
for series in series_resultset:
# Some products may be proprietary or embargoed, and users
@@ -317,7 +325,8 @@ class BranchListingItemsMixin:
"""Return a map from branch id to a list of package links."""
query_optimiser = self._query_optimiser
links = query_optimiser.getOfficialSourcePackageLinksForBranches(
- self._visible_branch_ids)
+ self._visible_branch_ids
+ )
result = {}
for link in links:
result.setdefault(link.branch.id, []).append(link)
@@ -328,9 +337,11 @@ class BranchListingItemsMixin:
If there is more than one, they are sorted by pocket.
"""
- links = [link.suite_sourcepackage for link in
- self.official_package_links_map.get(branch.id, [])]
- return sorted(links, key=attrgetter('pocket'))
+ links = [
+ link.suite_sourcepackage
+ for link in self.official_package_links_map.get(branch.id, [])
+ ]
+ return sorted(links, key=attrgetter("pocket"))
def getDistroDevelSeries(self, distribution):
"""distribution.currentseries hits the DB every time so cache it."""
@@ -344,15 +355,20 @@ class BranchListingItemsMixin:
@cachedproperty
def branch_ids_with_bug_links(self):
"""Return a set of branch ids that should show bug badges."""
- return set(getUtility(IBugBranchSet).getBranchesWithVisibleBugs(
- self.visible_branches_for_view, self.view_user))
+ return set(
+ getUtility(IBugBranchSet).getBranchesWithVisibleBugs(
+ self.visible_branches_for_view, self.view_user
+ )
+ )
@cachedproperty
def branch_ids_with_spec_links(self):
"""Return a set of branch ids that should show blueprint badges."""
spec_branches = getUtility(
- ISpecificationBranchSet).getSpecificationBranchesForBranches(
- self.visible_branches_for_view, self.view_user)
+ ISpecificationBranchSet
+ ).getSpecificationBranchesForBranches(
+ self.visible_branches_for_view, self.view_user
+ )
return {spec_branch.branch.id for spec_branch in spec_branches}
@cachedproperty
@@ -372,24 +388,28 @@ class BranchListingItemsMixin:
"""Return a set of branch ids that should show blueprint badges."""
revisionset = getUtility(IRevisionSet)
revisions = revisionset.getTipRevisionsForBranches(
- self.visible_branches_for_view)
+ self.visible_branches_for_view
+ )
if revisions is None:
revisions = []
# Key the revisions by revision id.
revision_map = {
- revision.revision_id: revision for revision in revisions}
+ revision.revision_id: revision for revision in revisions
+ }
# Cache display information for authors of branches' respective
# last revisions.
getUtility(IPersonSet).getPrecachedPersonsFromIDs(
[revision.revision_author.person_id for revision in revisions],
- need_icon=True)
+ need_icon=True,
+ )
# Return a dict keyed on branch id.
return {
branch.id: revision_map.get(branch.last_scanned_id)
- for branch in self.visible_branches_for_view}
+ for branch in self.visible_branches_for_view
+ }
def _createItem(self, branch):
last_commit = self.tip_revisions[branch.id]
@@ -399,8 +419,14 @@ class BranchListingItemsMixin:
associated_product_series = self.getProductSeries(branch)
suite_source_packages = self.getSuiteSourcePackages(branch)
return BranchListingItem(
- branch, last_commit, show_bug_badge, show_blueprint_badge,
- show_mp_badge, associated_product_series, suite_source_packages)
+ branch,
+ last_commit,
+ show_bug_badge,
+ show_blueprint_badge,
+ show_mp_badge,
+ associated_product_series,
+ suite_source_packages,
+ )
def decoratedBranches(self, branches):
"""Return the decorated branches for the branches passed in."""
@@ -408,15 +434,19 @@ class BranchListingItemsMixin:
@implementer(IBranchBatchNavigator)
-class BranchListingBatchNavigator(TableBatchNavigator,
- BranchListingItemsMixin):
+class BranchListingBatchNavigator(
+ TableBatchNavigator, BranchListingItemsMixin
+):
"""Batch up the branch listings."""
def __init__(self, view):
TableBatchNavigator.__init__(
- self, view.getVisibleBranchesForUser(), view.request,
+ self,
+ view.getVisibleBranchesForUser(),
+ view.request,
columns_to_show=view.extra_columns,
- size=config.launchpad.branchlisting_batch_size)
+ size=config.launchpad.branchlisting_batch_size,
+ )
BranchListingItemsMixin.__init__(self, view.user)
self.view = view
self.column_count = 4 + len(view.extra_columns)
@@ -429,7 +459,7 @@ class BranchListingBatchNavigator(TableBatchNavigator,
def visible_branches_for_view(self):
branches = list(self.currentBatch())
request = self.view.request
- precache_permission_for_objects(request, 'launchpad.View', branches)
+ precache_permission_for_objects(request, "launchpad.View", branches)
return branches
@cachedproperty
@@ -450,8 +480,9 @@ class BranchListingBatchNavigator(TableBatchNavigator,
class BranchListingView(LaunchpadFormView, FeedsMixin):
"""A base class for views of branch listings."""
+
schema = IBranchListingFilter
- field_names = ['lifecycle', 'sort_by']
+ field_names = ["lifecycle", "sort_by"]
development_focus_branch = None
show_set_development_focus = False
custom_widget_lifecycle = LaunchpadDropdownWidget
@@ -486,16 +517,17 @@ class BranchListingView(LaunchpadFormView, FeedsMixin):
ProductRevisionsFeedLink,
PersonBranchesFeedLink,
PersonRevisionsFeedLink,
- )
+ )
table_only_template = ViewPageTemplateFile(
- '../templates/branches-table-include.pt')
+ "../templates/branches-table-include.pt"
+ )
@property
def template(self):
- query_string = self.request.get('QUERY_STRING') or ''
+ query_string = self.request.get("QUERY_STRING") or ""
query_params = parse_qs(query_string)
- render_table_only = 'batch_request' in query_params
+ render_table_only = "batch_request" in query_params
if render_table_only:
return self.table_only_template
else:
@@ -503,11 +535,11 @@ class BranchListingView(LaunchpadFormView, FeedsMixin):
@property
def initial_values(self):
- return {'lifecycle': BranchLifecycleStatusFilter.CURRENT}
+ return {"lifecycle": BranchLifecycleStatusFilter.CURRENT}
@cachedproperty
def selected_lifecycle_status(self):
- widget = self.widgets['lifecycle']
+ widget = self.widgets["lifecycle"]
if widget.hasValidInput():
lifecycle_filter = widget.getInputValue()
@@ -519,7 +551,7 @@ class BranchListingView(LaunchpadFormView, FeedsMixin):
elif lifecycle_filter == BranchLifecycleStatusFilter.CURRENT:
return DEFAULT_BRANCH_STATUS_IN_LISTING
else:
- return (BranchLifecycleStatus.items[lifecycle_filter.name], )
+ return (BranchLifecycleStatus.items[lifecycle_filter.name],)
def branches(self):
"""All branches related to this target, sorted for display."""
@@ -554,8 +586,9 @@ class BranchListingView(LaunchpadFormView, FeedsMixin):
# the whole collection count (it might be expensive to compute if the
# total number of branches is huge).
return (
- len(self.branches().visible_branches_for_view) == 0 and
- not self.branch_count)
+ len(self.branches().visible_branches_for_view) == 0
+ and not self.branch_count
+ )
def _branches(self, lifecycle_status, sort_by=None):
"""Return a sequence of branches.
@@ -579,19 +612,23 @@ class BranchListingView(LaunchpadFormView, FeedsMixin):
def no_branch_message(self):
"""This may also be overridden in derived classes to provide
context relevant messages if there are no branches returned."""
- if (self.selected_lifecycle_status is not None
- and self.hasAnyBranchesVisibleByUser()):
+ if (
+ self.selected_lifecycle_status is not None
+ and self.hasAnyBranchesVisibleByUser()
+ ):
message = (
- 'There are branches related to %s but none of them match the '
- 'current filter criteria for this page. '
- 'Try filtering on "Any Status".')
+ "There are branches related to %s but none of them match the "
+ "current filter criteria for this page. "
+ 'Try filtering on "Any Status".'
+ )
else:
message = (
- 'There are no branches related to %s '
- 'in Launchpad today. You can use Launchpad as a registry for '
- 'Bazaar branches, and encourage broader community '
- 'participation in your project using '
- 'distributed version control.')
+ "There are no branches related to %s "
+ "in Launchpad today. You can use Launchpad as a registry for "
+ "Bazaar branches, and encourage broader community "
+ "participation in your project using "
+ "distributed version control."
+ )
return message % self.context.displayname
@property
@@ -605,53 +642,63 @@ class BranchListingView(LaunchpadFormView, FeedsMixin):
"""
# This is pretty painful.
# First we find the items which are not excluded for this view.
- vocab_items = [item for item in BranchListingSort.items.items
- if item not in self.no_sort_by]
+ vocab_items = [
+ item
+ for item in BranchListingSort.items.items
+ if item not in self.no_sort_by
+ ]
# Finding the value of the lifecycle_filter widget is awkward as we do
# this when the widgets are being set up. We go digging in the
# request.
- lifecycle_field = IBranchListingFilter['lifecycle']
- name = self.prefix + '.' + lifecycle_field.__name__
+ lifecycle_field = IBranchListingFilter["lifecycle"]
+ name = self.prefix + "." + lifecycle_field.__name__
form_value = self.request.form.get(name)
if form_value is not None:
try:
status_filter = BranchLifecycleStatusFilter.getTermByToken(
- form_value).value
+ form_value
+ ).value
except LookupError:
# We explicitly support bogus values in field.lifecycle --
# they are treated the same as "CURRENT", which includes more
# than one lifecycle.
pass
else:
- if status_filter not in (BranchLifecycleStatusFilter.ALL,
- BranchLifecycleStatusFilter.CURRENT):
+ if status_filter not in (
+ BranchLifecycleStatusFilter.ALL,
+ BranchLifecycleStatusFilter.CURRENT,
+ ):
vocab_items.remove(BranchListingSort.LIFECYCLE)
return vocab_items
@property
def sort_by_field(self):
"""The zope.schema field for the 'sort_by' widget."""
- orig_field = IBranchListingFilter['sort_by']
+ orig_field = IBranchListingFilter["sort_by"]
values = self.branch_listing_sort_values
- return Choice(__name__=orig_field.__name__,
- title=orig_field.title,
- required=True, values=values, default=values[0])
+ return Choice(
+ __name__=orig_field.__name__,
+ title=orig_field.title,
+ required=True,
+ values=values,
+ default=values[0],
+ )
@property
def sort_by(self):
"""The value of the `sort_by` widget, or None if none was present."""
- widget = self.widgets['sort_by']
+ widget = self.widgets["sort_by"]
if widget.hasValidInput():
return widget.getInputValue()
else:
# If a derived view has specified a default sort_by, use that.
- return self.initial_values.get('sort_by')
+ return self.initial_values.get("sort_by")
def setUpWidgets(self, context=None):
"""Set up the 'sort_by' widget with only the applicable choices."""
fields = []
for field_name in self.field_names:
- if field_name == 'sort_by':
+ if field_name == "sort_by":
field = form.FormField(self.sort_by_field)
else:
field = self.form_fields[field_name]
@@ -688,12 +735,13 @@ class BranchListingView(LaunchpadFormView, FeedsMixin):
class NoContextBranchListingView(BranchListingView):
"""A branch listing that has no associated product or person."""
- field_names = ['lifecycle']
- no_sort_by = (BranchListingSort.DEFAULT, )
+ field_names = ["lifecycle"]
+ no_sort_by = (BranchListingSort.DEFAULT,)
no_branch_message = (
- 'There are no branches that match the current status filter.')
- extra_columns = ('author', 'product', 'date_created')
+ "There are no branches that match the current status filter."
+ )
+ extra_columns = ("author", "product", "date_created")
def _branches(self, lifecycle_status):
"""Return a sequence of branches.
@@ -707,17 +755,19 @@ class NoContextBranchListingView(BranchListingView):
collection = collection.withLifecycleStatus(*lifecycle_status)
collection = collection.visibleByUser(self.user)
return collection.getBranches(eager_load=False).order_by(
- self._branch_order)
+ self._branch_order
+ )
class RecentlyRegisteredBranchesView(NoContextBranchListingView):
"""A batched view of branches orded by registration date."""
- page_title = 'Recently registered branches'
+ page_title = "Recently registered branches"
@property
def _branch_order(self):
from lp.code.model.branch import Branch
+
return Desc(Branch.date_created), Desc(Branch.id)
def _getCollection(self):
@@ -727,41 +777,47 @@ class RecentlyRegisteredBranchesView(NoContextBranchListingView):
class RecentlyImportedBranchesView(NoContextBranchListingView):
"""A batched view of imported branches ordered by last modifed time."""
- page_title = 'Recently imported branches'
- extra_columns = ('product', 'date_created')
+ page_title = "Recently imported branches"
+ extra_columns = ("product", "date_created")
@property
def _branch_order(self):
from lp.code.model.branch import Branch
+
return Desc(Branch.date_last_modified), Desc(Branch.id)
def _getCollection(self):
- return (getUtility(IAllBranches)
- .withBranchType(BranchType.IMPORTED)
- .scanned())
+ return (
+ getUtility(IAllBranches)
+ .withBranchType(BranchType.IMPORTED)
+ .scanned()
+ )
class RecentlyChangedBranchesView(NoContextBranchListingView):
"""Batched view of non-imported branches ordered by last modified time."""
- page_title = 'Recently changed branches'
+ page_title = "Recently changed branches"
@property
def _branch_order(self):
from lp.code.model.branch import Branch
+
return Desc(Branch.date_last_modified), Desc(Branch.id)
def _getCollection(self):
- return (getUtility(IAllBranches)
- .withBranchType(BranchType.HOSTED, BranchType.MIRRORED)
- .scanned())
+ return (
+ getUtility(IAllBranches)
+ .withBranchType(BranchType.HOSTED, BranchType.MIRRORED)
+ .scanned()
+ )
class PersonBranchesMenu(ApplicationMenu):
usedfor = IPerson
- facet = 'branches'
- links = ['branches', 'active_reviews', 'source_package_recipes', 'snaps']
+ facet = "branches"
+ links = ["branches", "active_reviews", "source_package_recipes", "snaps"]
@property
def person(self):
@@ -773,26 +829,27 @@ class PersonBranchesMenu(ApplicationMenu):
return self.context
def branches(self):
- return Link(
- canonical_url(self.context, rootsite='code'), 'Branches')
+ return Link(canonical_url(self.context, rootsite="code"), "Branches")
def active_reviews(self):
- return Link('+activereviews', 'Active reviews')
+ return Link("+activereviews", "Active reviews")
def source_package_recipes(self):
return Link(
- '+recipes', 'Source package recipes',
- enabled=IPerson.providedBy(self.context))
+ "+recipes",
+ "Source package recipes",
+ enabled=IPerson.providedBy(self.context),
+ )
def snaps(self):
enabled = IPerson.providedBy(self.context)
- return Link('+snaps', 'Snap packages', enabled=enabled)
+ return Link("+snaps", "Snap packages", enabled=enabled)
class PersonProductBranchesMenu(PersonBranchesMenu):
usedfor = IPersonProduct
- links = ['branches', 'active_reviews', 'source_package_recipes', 'snaps']
+ links = ["branches", "active_reviews", "source_package_recipes", "snaps"]
@property
def person(self):
@@ -814,13 +871,13 @@ class PersonBaseBranchListingView(BranchListingView):
@property
def initial_values(self):
values = super().initial_values
- values['sort_by'] = BranchListingSort.MOST_RECENTLY_CHANGED_FIRST
+ values["sort_by"] = BranchListingSort.MOST_RECENTLY_CHANGED_FIRST
return values
def _getCollection(self):
category = PersonBranchCategory.OWNED
- if self.widgets['category'].hasValidInput():
- category = self.widgets['category'].getInputValue()
+ if self.widgets["category"].hasValidInput():
+ category = self.widgets["category"].getInputValue()
if category == PersonBranchCategory.OWNED:
return getUtility(IAllBranches).ownedBy(self.person)
elif category == PersonBranchCategory.SUBSCRIBED:
@@ -833,7 +890,7 @@ class PersonBranchesView(PersonBaseBranchListingView):
"""View for branch listing for a person's branches."""
schema = IPersonBranchListingFilter
- field_names = ['category', 'lifecycle', 'sort_by']
+ field_names = ["category", "lifecycle", "sort_by"]
custom_widget_category = LaunchpadDropdownWidget
no_sort_by = (BranchListingSort.DEFAULT, BranchListingSort.OWNER)
@@ -841,16 +898,19 @@ class PersonBranchesView(PersonBaseBranchListingView):
@property
def no_branch_message(self):
- if (self.selected_lifecycle_status is not None
- and self.hasAnyBranchesVisibleByUser()):
+ if (
+ self.selected_lifecycle_status is not None
+ and self.hasAnyBranchesVisibleByUser()
+ ):
message = (
- 'There are branches related to %s but none of them match the '
- 'current filter criteria for this page. '
- 'Try filtering on "Any Status".')
+ "There are branches related to %s but none of them match the "
+ "current filter criteria for this page. "
+ 'Try filtering on "Any Status".'
+ )
else:
message = (
- 'There are no branches related to %s '
- 'in Launchpad today.')
+ "There are no branches related to %s " "in Launchpad today."
+ )
return message % self.context.displayname
@@ -858,8 +918,10 @@ class PersonProductBranchesView(PersonBranchesView):
"""Branch listing for a person's branches of a product."""
no_sort_by = (
- BranchListingSort.DEFAULT, BranchListingSort.OWNER,
- BranchListingSort.PRODUCT)
+ BranchListingSort.DEFAULT,
+ BranchListingSort.OWNER,
+ BranchListingSort.PRODUCT,
+ )
@property
def person(self):
@@ -868,22 +930,26 @@ class PersonProductBranchesView(PersonBranchesView):
@property
def label(self):
- return 'Branches of %s' % self.context.product.displayname
+ return "Branches of %s" % self.context.product.displayname
@property
def no_branch_message(self):
"""Provide a more appropriate message for no branches."""
- if (self.selected_lifecycle_status is not None
- and self.hasAnyBranchesVisibleByUser()):
+ if (
+ self.selected_lifecycle_status is not None
+ and self.hasAnyBranchesVisibleByUser()
+ ):
message = (
- 'There are branches of %s for %s but none of them '
- 'match the current filter criteria for this page. '
- 'Try filtering on "Any Status".')
+ "There are branches of %s for %s but none of them "
+ "match the current filter criteria for this page. "
+ 'Try filtering on "Any Status".'
+ )
else:
- message = (
- 'There are no branches of %s for %s in Launchpad today.')
+ message = "There are no branches of %s for %s in Launchpad today."
return message % (
- self.context.product.displayname, self.context.person.displayname)
+ self.context.product.displayname,
+ self.context.person.displayname,
+ )
def _getCollection(self):
coll = super()._getCollection()
@@ -904,7 +970,7 @@ class PersonTeamBranchesView(LaunchpadView):
want a particular url formatter for a PersonProduct, we have the url
separately.
"""
- return {'team': team, 'url_provider': team}
+ return {"team": team, "url_provider": team}
@property
def person(self):
@@ -913,8 +979,11 @@ class PersonTeamBranchesView(LaunchpadView):
@cachedproperty
def teams_with_branches(self):
teams = self._getCollection().getTeamsWithBranches(self.person)
- return [self._createItem(team) for team in teams
- if check_permission('launchpad.View', team)]
+ return [
+ self._createItem(team)
+ for team in teams
+ if check_permission("launchpad.View", team)
+ ]
class PersonProductTeamBranchesView(PersonTeamBranchesView):
@@ -922,15 +991,20 @@ class PersonProductTeamBranchesView(PersonTeamBranchesView):
def _getCollection(self):
"""Use a collection restricted on on the product."""
- return getUtility(IAllBranches).visibleByUser(self.user).inProduct(
- self.context.product)
+ return (
+ getUtility(IAllBranches)
+ .visibleByUser(self.user)
+ .inProduct(self.context.product)
+ )
def _createItem(self, team):
"""Return a tuple of the team, and the thing to get the URL from."""
return {
- 'team': team,
- 'url_provider': getUtility(IPersonProductFactory).create(
- team, self.context.product)}
+ "team": team,
+ "url_provider": getUtility(IPersonProductFactory).create(
+ team, self.context.product
+ ),
+ }
@property
def person(self):
@@ -953,14 +1027,14 @@ class PersonProductCodeSummaryView(PersonCodeSummaryView):
class ProductBranchesMenu(ApplicationMenu):
usedfor = IProduct
- facet = 'branches'
+ facet = "branches"
links = [
- 'active_reviews',
- 'code_import',
- ]
+ "active_reviews",
+ "code_import",
+ ]
extra_attributes = [
- 'active_review_count',
- ]
+ "active_review_count",
+ ]
@cachedproperty
def active_review_count(self):
@@ -970,21 +1044,20 @@ class ProductBranchesMenu(ApplicationMenu):
def active_reviews(self):
text = get_plural_text(
- self.active_review_count,
- 'Active review',
- 'Active reviews')
- return Link('+activereviews', text, site='code')
+ self.active_review_count, "Active review", "Active reviews"
+ )
+ return Link("+activereviews", text, site="code")
def code_import(self):
- text = 'Import a branch'
- return Link('+new-import', text, icon='add', site='code')
+ text = "Import a branch"
+ return Link("+new-import", text, icon="add", site="code")
class ProductBranchListingView(BranchListingView):
"""A base class for product branch listings."""
show_series_links = True
- no_sort_by = (BranchListingSort.PRODUCT, )
+ no_sort_by = (BranchListingSort.PRODUCT,)
def _getCollection(self):
return getUtility(IAllBranches).inProduct(self.context)
@@ -994,27 +1067,31 @@ class ProductBranchListingView(BranchListingView):
dev_focus_branch = self.context.development_focus.branch
if dev_focus_branch is None:
return None
- elif check_permission('launchpad.View', dev_focus_branch):
+ elif check_permission("launchpad.View", dev_focus_branch):
return dev_focus_branch
else:
return None
@property
def no_branch_message(self):
- if (self.selected_lifecycle_status is not None
- and self.hasAnyBranchesVisibleByUser()):
+ if (
+ self.selected_lifecycle_status is not None
+ and self.hasAnyBranchesVisibleByUser()
+ ):
message = (
- 'There are branches registered for %s '
- 'but none of them match the current filter criteria '
- 'for this page. Try filtering on "Any Status".')
+ "There are branches registered for %s "
+ "but none of them match the current filter criteria "
+ 'for this page. Try filtering on "Any Status".'
+ )
else:
message = (
- 'There are no branches registered for %s '
- 'in Launchpad today. We recommend you visit '
- 'www.bazaar-vcs.org '
- 'for more information about how you can use the Bazaar '
- 'revision control system to improve community participation '
- 'in this project.')
+ "There are no branches registered for %s "
+ "in Launchpad today. We recommend you visit "
+ "www.bazaar-vcs.org "
+ "for more information about how you can use the Bazaar "
+ "revision control system to improve community participation "
+ "in this project."
+ )
return message % self.context.displayname
def can_configure_branches(self):
@@ -1022,8 +1099,9 @@ class ProductBranchListingView(BranchListingView):
return check_permission("launchpad.Edit", self.context)
-class ProductBranchStatisticsView(BranchCountSummaryView,
- ProductBranchListingView):
+class ProductBranchStatisticsView(
+ BranchCountSummaryView, ProductBranchListingView
+):
"""Portlet containing branch statistics."""
@property
@@ -1037,9 +1115,12 @@ class ProductBranchStatisticsView(BranchCountSummaryView,
return text.capitalize()
-class ProductBranchSummaryView(ProductBranchListingView, SortSeriesMixin,
- ProductDownloadFileMixin, BranchMirrorMixin):
-
+class ProductBranchSummaryView(
+ ProductBranchListingView,
+ SortSeriesMixin,
+ ProductDownloadFileMixin,
+ BranchMirrorMixin,
+):
def initialize(self):
ProductBranchListingView.initialize(self)
revision_cache = getUtility(IRevisionCache)
@@ -1064,20 +1145,23 @@ class ProductBranchSummaryView(ProductBranchListingView, SortSeriesMixin,
"""The owners of branches."""
# Listify the owners, there really shouldn't be that many for any
# one project.
- return list(getUtility(IPersonSet).getPeopleWithBranches(
- product=self.context))
+ return list(
+ getUtility(IPersonSet).getPeopleWithBranches(product=self.context)
+ )
@cachedproperty
def person_owner_count(self):
"""The number of individual people who own branches."""
- return len([person for person in self._branch_owners
- if not person.is_team])
+ return len(
+ [person for person in self._branch_owners if not person.is_team]
+ )
@cachedproperty
def team_owner_count(self):
"""The number of teams who own branches."""
- return len([person for person in self._branch_owners
- if person.is_team])
+ return len(
+ [person for person in self._branch_owners if person.is_team]
+ )
@property
def has_development_focus_branch(self):
@@ -1086,34 +1170,40 @@ class ProductBranchSummaryView(ProductBranchListingView, SortSeriesMixin,
@property
def branch_text(self):
- return get_plural_text(self.branch_count, _('branch'), _('branches'))
+ return get_plural_text(self.branch_count, _("branch"), _("branches"))
@property
def person_text(self):
return get_plural_text(
- self.person_owner_count, _('person'), _('people'))
+ self.person_owner_count, _("person"), _("people")
+ )
@property
def team_text(self):
- return get_plural_text(self.team_owner_count, _('team'), _('teams'))
+ return get_plural_text(self.team_owner_count, _("team"), _("teams"))
@property
def commit_text(self):
- return get_plural_text(self.commit_count, _('commit'), _('commits'))
+ return get_plural_text(self.commit_count, _("commit"), _("commits"))
@property
def committer_text(self):
- return get_plural_text(self.committer_count, _('person'), _('people'))
+ return get_plural_text(self.committer_count, _("person"), _("people"))
@property
def external_visible(self):
return (
self.context.codehosting_usage == ServiceUsage.EXTERNAL
- and self.branch)
+ and self.branch
+ )
-class ProductBranchesView(ProductBranchListingView, SortSeriesMixin,
- ProductDownloadFileMixin, BranchMirrorMixin):
+class ProductBranchesView(
+ ProductBranchListingView,
+ SortSeriesMixin,
+ ProductDownloadFileMixin,
+ BranchMirrorMixin,
+):
"""Initial view for products on the code virtual host."""
show_set_development_focus = True
@@ -1122,9 +1212,9 @@ class ProductBranchesView(ProductBranchListingView, SortSeriesMixin,
@property
def initial_values(self):
return {
- 'lifecycle': BranchLifecycleStatusFilter.CURRENT,
- 'sort_by': BranchListingSort.DEFAULT,
- }
+ "lifecycle": BranchLifecycleStatusFilter.CURRENT,
+ "sort_by": BranchListingSort.DEFAULT,
+ }
def _getSeriesBranches(self):
"""Get the series branches for the product, dev focus first."""
@@ -1138,22 +1228,26 @@ class ProductBranchesView(ProductBranchListingView, SortSeriesMixin,
if self.selected_lifecycle_status is None:
return True
else:
- return (branch.lifecycle_status in
- self.selected_lifecycle_status)
+ return (
+ branch.lifecycle_status in self.selected_lifecycle_status
+ )
+
# The series will always have at least one series, that of the
# development focus.
dev_focus_branch = sorted_series[0].branch
- if not check_permission('launchpad.View', dev_focus_branch):
+ if not check_permission("launchpad.View", dev_focus_branch):
dev_focus_branch = None
result = []
if dev_focus_branch is not None and show_branch(dev_focus_branch):
result.append(dev_focus_branch)
for series in sorted_series[1:]:
branch = series.branch
- if (branch is not None and
- branch not in result and
- check_permission('launchpad.View', branch) and
- show_branch(branch)):
+ if (
+ branch is not None
+ and branch not in result
+ and check_permission("launchpad.View", branch)
+ and show_branch(branch)
+ ):
result.append(branch)
return result
@@ -1163,7 +1257,8 @@ class ProductBranchesView(ProductBranchListingView, SortSeriesMixin,
series_branches = self._getSeriesBranches()
branch_query = super()._branches(
self.selected_lifecycle_status,
- sort_by=BranchListingSort.MOST_RECENTLY_CHANGED_FIRST)
+ sort_by=BranchListingSort.MOST_RECENTLY_CHANGED_FIRST,
+ )
# We don't want the initial branch listing to be batched, so only get
# the batch size - the number of series_branches.
batch_size = config.launchpad.branchlisting_batch_size
@@ -1171,8 +1266,10 @@ class ProductBranchesView(ProductBranchListingView, SortSeriesMixin,
# We want to make sure that the series branches do not appear
# in our branch list.
branches = [
- branch for branch in branch_query[:max_branches_from_query]
- if branch not in series_branches]
+ branch
+ for branch in branch_query[:max_branches_from_query]
+ if branch not in series_branches
+ ]
series_branches.extend(branches)
return series_branches
@@ -1192,8 +1289,8 @@ class ProductBranchesView(ProductBranchListingView, SortSeriesMixin,
class ProjectBranchesView(BranchListingView):
"""View for branch listing for a project."""
- no_sort_by = (BranchListingSort.DEFAULT, )
- extra_columns = ('author', 'product')
+ no_sort_by = (BranchListingSort.DEFAULT,)
+ extra_columns = ("author", "product")
show_series_links = True
def _getCollection(self):
@@ -1201,20 +1298,24 @@ class ProjectBranchesView(BranchListingView):
@property
def no_branch_message(self):
- if (self.selected_lifecycle_status is not None
- and self.hasAnyBranchesVisibleByUser()):
+ if (
+ self.selected_lifecycle_status is not None
+ and self.hasAnyBranchesVisibleByUser()
+ ):
message = (
- 'There are branches registered for %s '
- 'but none of them match the current filter criteria '
- 'for this page. Try filtering on "Any Status".')
+ "There are branches registered for %s "
+ "but none of them match the current filter criteria "
+ 'for this page. Try filtering on "Any Status".'
+ )
else:
message = (
- 'There are no branches registered for %s '
- 'in Launchpad today. We recommend you visit '
- 'www.bazaar-vcs.org '
- 'for more information about how you can use the Bazaar '
- 'revision control system to improve community participation '
- 'in this project group.')
+ "There are no branches registered for %s "
+ "in Launchpad today. We recommend you visit "
+ "www.bazaar-vcs.org "
+ "for more information about how you can use the Bazaar "
+ "revision control system to improve community participation "
+ "in this project group."
+ )
return message % self.context.displayname
@@ -1226,7 +1327,7 @@ class BaseSourcePackageBranchesView(BranchListingView):
@property
def initial_values(self):
values = super().initial_values
- values['sort_by'] = BranchListingSort.MOST_RECENTLY_CHANGED_FIRST
+ values["sort_by"] = BranchListingSort.MOST_RECENTLY_CHANGED_FIRST
return values
@@ -1237,7 +1338,8 @@ class DistributionSourcePackageBranchesView(BaseSourcePackageBranchesView):
def _getCollection(self):
return getUtility(IAllBranches).inDistributionSourcePackage(
- self.context)
+ self.context
+ )
class DistributionBranchListingView(BaseSourcePackageBranchesView):
@@ -1254,14 +1356,15 @@ class DistroSeriesBranchListingView(BaseSourcePackageBranchesView):
@property
def label(self):
- return 'Branches for %s' % self.context.displayname
+ return "Branches for %s" % self.context.displayname
def _getCollection(self):
return getUtility(IAllBranches).inDistroSeries(self.context)
-class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
- BranchListingItemsMixin):
+class GroupedDistributionSourcePackageBranchesView(
+ LaunchpadView, BranchListingItemsMixin
+):
"""A view that groups branches into distro series."""
def __init__(self, context, request):
@@ -1270,15 +1373,19 @@ class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
def getBranchCollection(self):
"""See `BranchListingItemsMixin`."""
- return getUtility(IAllBranches).inDistributionSourcePackage(
- self.context).visibleByUser(self.user)
+ return (
+ getUtility(IAllBranches)
+ .inDistributionSourcePackage(self.context)
+ .visibleByUser(self.user)
+ )
def _getBranchDict(self):
"""Return a dict of branches grouped by distroseries."""
branches = {}
# We're only interested in active branches.
collection = self.getBranchCollection().withLifecycleStatus(
- *DEFAULT_BRANCH_STATUS_IN_LISTING)
+ *DEFAULT_BRANCH_STATUS_IN_LISTING
+ )
for branch in collection.getBranches(eager_load=False):
branches.setdefault(branch.distroseries, []).append(branch)
return branches
@@ -1295,8 +1402,10 @@ class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
# Remember it is possible that the linked branch is not visible by the
# user. Unlikely, but possible.
visible_links = [
- link for link in links
- if check_permission('launchpad.View', link.branch)]
+ link
+ for link in links
+ if check_permission("launchpad.View", link.branch)
+ ]
# Sort into distroseries.
distro_links = {}
for link in visible_links:
@@ -1305,7 +1414,7 @@ class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
# is linked to more than one pocket. Best here means smaller value.
official_branches = {}
for key, value in distro_links.items():
- ordered = sorted(value, key=attrgetter('pocket'))
+ ordered = sorted(value, key=attrgetter("pocket"))
seen_branches = set()
branches = []
for link in ordered:
@@ -1320,9 +1429,10 @@ class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
# Sort the branches by the last modified date, and ignore any that are
# official.
ordered_branches = sorted(
- (branch for branch in branches
- if branch not in official_branches),
- key=attrgetter('date_last_modified'), reverse=True)
+ (branch for branch in branches if branch not in official_branches),
+ key=attrgetter("date_last_modified"),
+ reverse=True,
+ )
num_branches = len(ordered_branches)
num_official = len(official_branches)
# We want to show at most five branches, with (at most) the most
@@ -1349,7 +1459,8 @@ class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
if series in all_branches:
branches, more_count = self._getSeriesBranches(
official_branches.get(series, []),
- all_branches.get(series, []))
+ all_branches.get(series, []),
+ )
series_branches[series] = (branches, more_count)
return series_branches
@@ -1400,18 +1511,22 @@ class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
if series in series_branches_map:
branches, more_count = series_branches_map[series]
sourcepackage = sp_factory.new(
- self.context.sourcepackagename, series)
+ self.context.sourcepackagename, series
+ )
num_branches = len(branches) + more_count
num_branches_text = get_plural_text(
- num_branches, "branch", "branches")
+ num_branches, "branch", "branches"
+ )
count_string = "%s %s" % (num_branches, num_branches_text)
result.append(
- {'distroseries': series,
- 'branches': self.decoratedBranches(branches),
- 'more-branch-count': more_count,
- 'package': sourcepackage,
- 'total-count-string': count_string,
- })
+ {
+ "distroseries": series,
+ "branches": self.decoratedBranches(branches),
+ "more-branch-count": more_count,
+ "package": sourcepackage,
+ "total-count-string": count_string,
+ }
+ )
return result
@property
@@ -1421,10 +1536,9 @@ class GroupedDistributionSourcePackageBranchesView(LaunchpadView,
class SourcePackageBranchesView(BranchListingView):
-
@property
def label(self):
- return 'Branches for %s' % self.context.distroseries.displayname
+ return "Branches for %s" % self.context.distroseries.displayname
# XXX: JonathanLange 2009-03-03 spec=package-branches: This page has no
# menu yet -- do we need one?
@@ -1452,9 +1566,9 @@ class SourcePackageBranchesView(BranchListingView):
if not series.active:
continue
if distribution.currentseries == series:
- dev_focus_css = 'sourcepackage-dev-focus'
+ dev_focus_css = "sourcepackage-dev-focus"
else:
- dev_focus_css = 'sourcepackage-not-dev-focus'
+ dev_focus_css = "sourcepackage-not-dev-focus"
package = SourcePackage(our_sourcepackagename, series)
# XXX: JonathanLange 2009-05-13 bug=376295: This approach is
# inefficient. We should instead do something like:
@@ -1469,10 +1583,12 @@ class SourcePackageBranchesView(BranchListingView):
# generally less than 5.
num_branches = self._numBranchesInPackage(package)
num_branches_text = get_plural_text(
- num_branches, "branch", "branches")
+ num_branches, "branch", "branches"
+ )
yield dict(
series_name=series.displayname,
package=package,
- num_branches='%s %s' % (num_branches, num_branches_text),
+ num_branches="%s %s" % (num_branches, num_branches_text),
dev_focus_css=dev_focus_css,
- linked=(series != our_series))
+ linked=(series != our_series),
+ )
diff --git a/lib/lp/code/browser/branchmergeproposal.py b/lib/lp/code/browser/branchmergeproposal.py
index fcaddeb..c93790c 100644
--- a/lib/lp/code/browser/branchmergeproposal.py
+++ b/lib/lp/code/browser/branchmergeproposal.py
@@ -4,73 +4,52 @@
"""Views, navigation and actions for BranchMergeProposals."""
__all__ = [
- 'BranchMergeCandidateView',
- 'BranchMergeProposalActionNavigationMenu',
- 'BranchMergeProposalAddVoteView',
- 'BranchMergeProposalChangeStatusView',
- 'BranchMergeProposalCommitMessageEditView',
- 'BranchMergeProposalContextMenu',
- 'BranchMergeProposalDeleteView',
- 'BranchMergeProposalDescriptionEditView',
- 'BranchMergeProposalEditMenu',
- 'BranchMergeProposalEditView',
- 'BranchMergeProposalNavigation',
- 'BranchMergeProposalMergedView',
- 'BranchMergeProposalRequestReviewView',
- 'BranchMergeProposalResubmitView',
- 'BranchMergeProposalSubscribersView',
- 'BranchMergeProposalView',
- 'BranchMergeProposalVoteView',
- 'latest_proposals_for_each_branch',
- ]
+ "BranchMergeCandidateView",
+ "BranchMergeProposalActionNavigationMenu",
+ "BranchMergeProposalAddVoteView",
+ "BranchMergeProposalChangeStatusView",
+ "BranchMergeProposalCommitMessageEditView",
+ "BranchMergeProposalContextMenu",
+ "BranchMergeProposalDeleteView",
+ "BranchMergeProposalDescriptionEditView",
+ "BranchMergeProposalEditMenu",
+ "BranchMergeProposalEditView",
+ "BranchMergeProposalNavigation",
+ "BranchMergeProposalMergedView",
+ "BranchMergeProposalRequestReviewView",
+ "BranchMergeProposalResubmitView",
+ "BranchMergeProposalSubscribersView",
+ "BranchMergeProposalView",
+ "BranchMergeProposalVoteView",
+ "latest_proposals_for_each_branch",
+]
-from functools import wraps
import operator
-from urllib.parse import (
- urlsplit,
- urlunsplit,
- )
+from functools import wraps
+from urllib.parse import urlsplit, urlunsplit
+import simplejson
from lazr.delegates import delegate_to
from lazr.restful.interface import copy_field
-from lazr.restful.interfaces import (
- IJSONRequestCache,
- IWebServiceClientRequest,
- )
-import simplejson
-from zope.component import (
- adapter,
- getMultiAdapter,
- getUtility,
- )
+from lazr.restful.interfaces import IJSONRequestCache, IWebServiceClientRequest
+from zope.component import adapter, getMultiAdapter, getUtility
from zope.formlib import form
from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.widgets import TextAreaWidget
-from zope.interface import (
- implementer,
- Interface,
- )
-from zope.schema import (
- Bool,
- Choice,
- Int,
- Text,
- )
-from zope.schema.vocabulary import (
- SimpleTerm,
- SimpleVocabulary,
- )
+from zope.interface import Interface, implementer
+from zope.schema import Bool, Choice, Int, Text
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from lp import _
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.browser.lazrjs import (
TextAreaEditorWidget,
vocabulary_to_choice_edit_items,
- )
+)
from lp.app.browser.tales import DateTimeFormatterAPI
from lp.code.adapters.branch import BranchMergeProposalNoPreviewDiffDelta
from lp.code.browser.codereviewcomment import CodeReviewDisplayComment
@@ -82,7 +61,7 @@ from lp.code.enums import (
BranchType,
CodeReviewNotificationLevel,
CodeReviewVote,
- )
+)
from lp.code.errors import (
BranchMergeProposalExists,
ClaimReviewFailed,
@@ -90,53 +69,47 @@ from lp.code.errors import (
GitRepositoryScanFault,
InvalidBranchMergeProposal,
WrongBranchMergeProposal,
- )
+)
from lp.code.interfaces.branch import IBranch
from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
from lp.code.interfaces.codereviewcomment import ICodeReviewComment
from lp.code.interfaces.codereviewinlinecomment import (
ICodeReviewInlineCommentSet,
- )
+)
from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
from lp.code.interfaces.diff import IPreviewDiff
from lp.registry.interfaces.person import IPersonSet
from lp.services.comments.interfaces.conversation import (
IComment,
IConversation,
- )
+)
from lp.services.config import config
from lp.services.features import getFeatureFlag
from lp.services.job.interfaces.job import JobStatus
from lp.services.librarian.interfaces.client import LibrarianServerError
-from lp.services.propertycache import (
- cachedproperty,
- get_property_cache,
- )
+from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.scripts import log
-from lp.services.timeout import (
- reduced_timeout,
- TimeoutError,
- )
+from lp.services.timeout import TimeoutError, reduced_timeout
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
+ canonical_url,
+ enabled_with_permission,
stepthrough,
- )
+)
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.escaping import structured
from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.menu import NavigationMenu
-
GIT_HOSTING_ERROR_MSG = (
"There was an error fetching revisions from git servers. "
"Please try again in a few minutes. If the problem persists, "
- "<a href='/launchpad/+addquestion'>contact Launchpad support</a>.")
+ "<a href='/launchpad/+addquestion'>contact Launchpad support</a>."
+)
def latest_proposals_for_each_branch(proposals):
@@ -147,7 +120,7 @@ def latest_proposals_for_each_branch(proposals):
targets = {}
for proposal in proposals:
# Don't show the proposal if the user can't see it.
- if not check_permission('launchpad.View', proposal):
+ if not check_permission("launchpad.View", proposal):
continue
# Only show the most recent proposal for any given target.
date_created = proposal.date_created
@@ -158,7 +131,9 @@ def latest_proposals_for_each_branch(proposals):
return sorted(
(proposal for proposal, date_created in targets.values()),
- key=operator.attrgetter('date_created'), reverse=True)
+ key=operator.attrgetter("date_created"),
+ reverse=True,
+ )
class BranchMergeProposalBreadcrumb(Breadcrumb):
@@ -166,7 +141,7 @@ class BranchMergeProposalBreadcrumb(Breadcrumb):
@property
def text(self):
- return 'Merge into %s' % self.context.merge_target.name
+ return "Merge into %s" % self.context.merge_target.name
@property
def inside(self):
@@ -175,10 +150,12 @@ class BranchMergeProposalBreadcrumb(Breadcrumb):
def notify(func):
"""Decorate a view method to send a notification."""
+
@wraps(func)
def decorator(view, *args, **kwargs):
with BranchMergeProposalNoPreviewDiffDelta.monitor(view.context):
return func(view, *args, **kwargs)
+
return decorator
@@ -188,15 +165,14 @@ class BranchMergeCandidateView(LaunchpadView):
def friendly_text(self):
"""Prints friendly text for a branch."""
friendly_texts = {
- BranchMergeProposalStatus.WORK_IN_PROGRESS: 'On hold',
- BranchMergeProposalStatus.NEEDS_REVIEW: 'Ready for review',
- BranchMergeProposalStatus.CODE_APPROVED: 'Approved',
- BranchMergeProposalStatus.REJECTED: 'Rejected',
- BranchMergeProposalStatus.MERGED: 'Merged',
- BranchMergeProposalStatus.MERGE_FAILED:
- 'Approved [Merge Failed]',
- BranchMergeProposalStatus.QUEUED: 'Queued',
- BranchMergeProposalStatus.SUPERSEDED: 'Superseded',
+ BranchMergeProposalStatus.WORK_IN_PROGRESS: "On hold",
+ BranchMergeProposalStatus.NEEDS_REVIEW: "Ready for review",
+ BranchMergeProposalStatus.CODE_APPROVED: "Approved",
+ BranchMergeProposalStatus.REJECTED: "Rejected",
+ BranchMergeProposalStatus.MERGED: "Merged",
+ BranchMergeProposalStatus.MERGE_FAILED: "Approved [Merge Failed]",
+ BranchMergeProposalStatus.QUEUED: "Queued",
+ BranchMergeProposalStatus.SUPERSEDED: "Superseded",
}
return friendly_texts[self.context.queue_status]
@@ -206,14 +182,16 @@ class BranchMergeCandidateView(LaunchpadView):
Only set if the status is approved or rejected.
"""
- result = ''
+ result = ""
if self.context.queue_status in (
BranchMergeProposalStatus.CODE_APPROVED,
- BranchMergeProposalStatus.REJECTED):
+ BranchMergeProposalStatus.REJECTED,
+ ):
formatter = DateTimeFormatterAPI(self.context.date_reviewed)
- result = '%s %s' % (
+ result = "%s %s" % (
self.context.reviewer.displayname,
- formatter.displaydate())
+ formatter.displaydate(),
+ )
return result
@property
@@ -226,37 +204,36 @@ class BranchMergeCandidateView(LaunchpadView):
class BranchMergeProposalMenuMixin:
"""Mixin class for merge proposal menus."""
- @enabled_with_permission('launchpad.AnyPerson')
+ @enabled_with_permission("launchpad.AnyPerson")
def add_comment(self):
- return Link('+comment', 'Add a review or comment', icon='add')
+ return Link("+comment", "Add a review or comment", icon="add")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
- text = 'Edit details'
+ text = "Edit details"
enabled = self.context.isMergable()
- return Link('+edit', text, icon='edit', enabled=enabled)
+ return Link("+edit", text, icon="edit", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def set_description(self):
- text = 'Set description'
- return Link('+edit-description', text, icon='add')
+ text = "Set description"
+ return Link("+edit-description", text, icon="add")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def set_commit_message(self):
- text = 'Set commit message'
+ text = "Set commit message"
enabled = self.context.isMergable()
- return Link('+edit-commit-message', text, icon='add',
- enabled=enabled)
+ return Link("+edit-commit-message", text, icon="add", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit_status(self):
- text = 'Change status'
- return Link('+edit-status', text, icon='edit')
+ text = "Change status"
+ return Link("+edit-status", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def delete(self):
- text = 'Delete proposal to merge'
- return Link('+delete', text, icon='trash-icon')
+ text = "Delete proposal to merge"
+ return Link("+delete", text, icon="trash-icon")
def _enabledForStatus(self, next_state):
"""True if the next_state is a valid transition for the current user.
@@ -270,75 +247,75 @@ class BranchMergeProposalMenuMixin:
else:
return bmp.isValidTransition(next_state, self.user)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def request_review(self):
- text = 'Request a review'
+ text = "Request a review"
enabled = self._enabledForStatus(
- BranchMergeProposalStatus.NEEDS_REVIEW)
- if (self.context.queue_status ==
- BranchMergeProposalStatus.NEEDS_REVIEW):
+ BranchMergeProposalStatus.NEEDS_REVIEW
+ )
+ if self.context.queue_status == BranchMergeProposalStatus.NEEDS_REVIEW:
enabled = True
if len(self.context.votes) > 0:
- text = 'Request another review'
- return Link('+request-review', text, icon='add', enabled=enabled)
+ text = "Request another review"
+ return Link("+request-review", text, icon="add", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def merge(self):
- text = 'Mark as merged'
- enabled = self._enabledForStatus(
- BranchMergeProposalStatus.MERGED)
- return Link('+merged', text, enabled=enabled)
+ text = "Mark as merged"
+ enabled = self._enabledForStatus(BranchMergeProposalStatus.MERGED)
+ return Link("+merged", text, enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def update_merge_revno(self):
if IBranch.providedBy(self.context.merge_target):
- text = 'Update revision number'
+ text = "Update revision number"
else:
- text = 'Update revision ID'
- return Link('+merged', text)
+ text = "Update revision ID"
+ return Link("+merged", text)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def resubmit(self):
- text = 'Resubmit proposal'
- enabled = self._enabledForStatus(
- BranchMergeProposalStatus.SUPERSEDED)
- return Link('+resubmit', text, enabled=enabled, icon='edit')
+ text = "Resubmit proposal"
+ enabled = self._enabledForStatus(BranchMergeProposalStatus.SUPERSEDED)
+ return Link("+resubmit", text, enabled=enabled, icon="edit")
def link_bug(self):
- text = 'Link a bug report'
- return Link('+linkbug', text, icon='add')
+ text = "Link a bug report"
+ return Link("+linkbug", text, icon="add")
-class BranchMergeProposalEditMenu(NavigationMenu,
- BranchMergeProposalMenuMixin):
+class BranchMergeProposalEditMenu(
+ NavigationMenu, BranchMergeProposalMenuMixin
+):
"""Edit menu for Branch Merge Proposals."""
usedfor = IBranchMergeProposal
- title = 'Edit Proposal'
- facet = 'branches'
- links = ['resubmit', 'delete']
+ title = "Edit Proposal"
+ facet = "branches"
+ links = ["resubmit", "delete"]
@property
def branch_merge_proposal(self):
return self.context
-class BranchMergeProposalContextMenu(ContextMenu,
- BranchMergeProposalMenuMixin):
+class BranchMergeProposalContextMenu(
+ ContextMenu, BranchMergeProposalMenuMixin
+):
"""Context menu for merge proposals."""
usedfor = IBranchMergeProposal
links = [
- 'add_comment',
- 'set_commit_message',
- 'set_description',
- 'edit_status',
- 'link_bug',
- 'merge',
- 'request_review',
- 'resubmit',
- 'update_merge_revno',
- ]
+ "add_comment",
+ "set_commit_message",
+ "set_description",
+ "edit_status",
+ "link_bug",
+ "merge",
+ "request_review",
+ "resubmit",
+ "update_merge_revno",
+ ]
@property
def branch_merge_proposal(self):
@@ -349,13 +326,14 @@ class IBranchMergeProposalActionMenu(Interface):
"""A marker interface for the global action navigation menu."""
-class BranchMergeProposalActionNavigationMenu(NavigationMenu,
- BranchMergeProposalMenuMixin):
+class BranchMergeProposalActionNavigationMenu(
+ NavigationMenu, BranchMergeProposalMenuMixin
+):
"""A sub-menu for acting upon a Product."""
usedfor = IBranchMergeProposalActionMenu
- facet = 'branches'
- links = ('resubmit', 'delete')
+ facet = "branches"
+ links = ("resubmit", "delete")
@property
def branch_merge_proposal(self):
@@ -365,6 +343,7 @@ class BranchMergeProposalActionNavigationMenu(NavigationMenu,
class UnmergedRevisionsMixin:
"""Provides the methods needed to show unmerged revisions."""
+
# This is set at self.unlanded_revisions, and should be used at view level
# as self.commit_infos_message (see git-macros.pt).
_unlanded_revisions_message = None
@@ -372,17 +351,20 @@ class UnmergedRevisionsMixin:
@cachedproperty
def unlanded_revisions(self):
"""Return the unlanded revisions from the source branch."""
- self._unlanded_revisions_message = ''
+ self._unlanded_revisions_message = ""
with reduced_timeout(1.0, webapp_max=5.0):
try:
return self.context.getUnlandedSourceBranchRevisions()
except TimeoutError:
log.exception(
"Timeout fetching unlanded source revisions for merge "
- "proposal %s (%s => %s)" % (
+ "proposal %s (%s => %s)"
+ % (
self.context.id,
self.context.merge_source.identity,
- self.context.merge_target.identity))
+ self.context.merge_target.identity,
+ )
+ )
return []
except GitRepositoryScanFault as e:
log.exception("Error scanning git repository: %s" % e)
@@ -425,12 +407,14 @@ class BranchMergeProposalRevisionIdMixin:
return revision_id
else:
branch_revision = source_branch.getBranchRevision(
- revision_id=revision_id)
+ revision_id=revision_id
+ )
if branch_revision is None:
return "no longer in the source branch."
elif branch_revision.sequence is None:
return (
- "no longer in the revision history of the source branch.")
+ "no longer in the revision history of the source branch."
+ )
else:
return branch_revision.sequence
@@ -438,7 +422,8 @@ class BranchMergeProposalRevisionIdMixin:
def reviewed_revision_number(self):
"""Return the number of the reviewed revision."""
return self._getRevisionNumberForRevisionId(
- self.context.reviewed_revision_id)
+ self.context.reviewed_revision_id
+ )
class BranchMergeProposalNavigation(Navigation):
@@ -446,7 +431,7 @@ class BranchMergeProposalNavigation(Navigation):
usedfor = IBranchMergeProposal
- @stepthrough('reviews')
+ @stepthrough("reviews")
def traverse_review(self, id):
"""Navigate to a CodeReviewVoteReference through its BMP."""
try:
@@ -458,7 +443,7 @@ class BranchMergeProposalNavigation(Navigation):
except WrongBranchMergeProposal:
return None
- @stepthrough('comments')
+ @stepthrough("comments")
def traverse_comment(self, id):
try:
id = int(id)
@@ -486,7 +471,7 @@ class BranchMergeProposalNavigation(Navigation):
except (DiffNotFound, WrongBranchMergeProposal):
return None
- @stepthrough('+review')
+ @stepthrough("+review")
def review(self, id):
"""Step to the CodeReviewVoteReference."""
try:
@@ -514,7 +499,7 @@ class ClaimButton(Interface):
class BranchMergeProposalStatusMixin:
- '''A mixin for generating status vocabularies.'''
+ """A mixin for generating status vocabularies."""
def _createStatusVocabulary(self):
# Create the vocabulary that is used for the status widget.
@@ -524,7 +509,7 @@ class BranchMergeProposalStatusMixin:
BranchMergeProposalStatus.CODE_APPROVED,
BranchMergeProposalStatus.REJECTED,
BranchMergeProposalStatus.MERGED,
- )
+ )
terms = []
for status in possible_next_states:
if not self.context.isValidTransition(status, self.user):
@@ -549,7 +534,7 @@ class DiffRenderingMixin:
@property
def diff_available(self):
"""Is the preview diff available from the librarian?"""
- if getattr(self, '_diff_available', None) is None:
+ if getattr(self, "_diff_available", None) is None:
# Load the cache so that the answer is known.
self.preview_diff_text
return self._diff_available
@@ -565,9 +550,9 @@ class DiffRenderingMixin:
diff = preview_diff.text
except (LookupError, LibrarianServerError):
self._diff_available = False
- diff = ''
+ diff = ""
# Strip off the trailing carriage returns.
- return diff.rstrip('\n')
+ return diff.rstrip("\n")
@cachedproperty
def diff_oversized(self):
@@ -582,7 +567,7 @@ class DiffRenderingMixin:
if preview_diff is None:
return False
diff_text = self.preview_diff_text
- return diff_text.count('\n') >= config.diff.max_format_lines
+ return diff_text.count("\n") >= config.diff.max_format_lines
class ICodeReviewNewRevisions(IComment):
@@ -631,11 +616,14 @@ class CodeReviewNewRevisions:
@implementer(IBranchMergeProposalActionMenu)
-class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
- BranchMergeProposalRevisionIdMixin,
- BranchMergeProposalStatusMixin,
- DiffRenderingMixin,
- HasRevisionStatusReportsMixin):
+class BranchMergeProposalView(
+ LaunchpadFormView,
+ UnmergedRevisionsMixin,
+ BranchMergeProposalRevisionIdMixin,
+ BranchMergeProposalStatusMixin,
+ DiffRenderingMixin,
+ HasRevisionStatusReportsMixin,
+):
"""A basic view used for the index page."""
schema = ClaimButton
@@ -643,22 +631,26 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
def initialize(self):
super().initialize()
cache = IJSONRequestCache(self.request)
- cache.objects['branch_name'] = self.context.merge_source.name
- if (IBranch.providedBy(self.context.merge_source) and
- getFeatureFlag("code.bzr.diff.disable_proxy")):
+ cache.objects["branch_name"] = self.context.merge_source.name
+ if IBranch.providedBy(self.context.merge_source) and getFeatureFlag(
+ "code.bzr.diff.disable_proxy"
+ ):
# This fallback works for public branches, but not private ones.
- cache.objects['branch_diff_link'] = (
- 'https://%s/+loggerhead/%s/diff/' % (
- config.launchpad.code_domain,
- self.context.source_branch.unique_name))
+ cache.objects[
+ "branch_diff_link"
+ ] = "https://%s/+loggerhead/%s/diff/" % (
+ config.launchpad.code_domain,
+ self.context.source_branch.unique_name,
+ )
else:
- cache.objects['branch_diff_link'] = (
- canonical_url(self.context.parent) + '/+diff/')
+ cache.objects["branch_diff_link"] = (
+ canonical_url(self.context.parent) + "/+diff/"
+ )
- @action('Claim', name='claim')
+ @action("Claim", name="claim")
def claim_action(self, action, data):
"""Claim this proposal."""
- request = self.context.getVoteReference(data['review_id'])
+ request = self.context.getVoteReference(data["review_id"])
if request is not None:
try:
request.claimReview(self.user)
@@ -669,7 +661,7 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
@property
def comment_location(self):
"""Location of page for commenting on this proposal."""
- return canonical_url(self.context, view_name='+comment')
+ return canonical_url(self.context, view_name="+comment")
@property
def source_git_ssh_url(self):
@@ -687,15 +679,18 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
merge_proposal = self.context
with reduced_timeout(1.0, webapp_max=5.0):
try:
- self._conversation_message = ''
+ self._conversation_message = ""
groups = list(merge_proposal.getRevisionsSinceReviewStart())
except TimeoutError:
log.exception(
"Timeout fetching revisions since review start for "
- "merge proposal %s (%s => %s)" % (
+ "merge proposal %s (%s => %s)"
+ % (
merge_proposal.id,
merge_proposal.merge_source.identity,
- merge_proposal.merge_target.identity))
+ merge_proposal.merge_target.identity,
+ )
+ )
groups = []
except GitRepositoryScanFault as e:
log.exception("Error scanning git repository: %s" % e)
@@ -707,13 +702,18 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
user = getUtility(ILaunchBag).user
strip_invisible = not merge_proposal.userCanSetCommentVisibility(user)
comments = []
- if (getFeatureFlag('code.incremental_diffs.enabled') and
- merge_proposal.source_branch is not None):
+ if (
+ getFeatureFlag("code.incremental_diffs.enabled")
+ and merge_proposal.source_branch is not None
+ ):
# XXX cjwatson 2016-05-09: Implement for Git.
ranges = [
- (revisions[0].revision.getLefthandParent(),
- revisions[-1].revision)
- for revisions in groups]
+ (
+ revisions[0].revision.getLefthandParent(),
+ revisions[-1].revision,
+ )
+ for revisions in groups
+ ]
diffs = merge_proposal.getIncrementalDiffs(ranges)
else:
diffs = [None] * len(groups)
@@ -723,16 +723,19 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
else:
last_date_created = revisions[-1]["author_date"]
newrevs = CodeReviewNewRevisions(
- revisions, last_date_created, source, diff)
+ revisions, last_date_created, source, diff
+ )
comments.append(newrevs)
while merge_proposal is not None:
from_superseded = merge_proposal != self.context
comments.extend(
CodeReviewDisplayComment(
- comment, from_superseded, limit_length=True)
- for comment in merge_proposal.all_comments)
+ comment, from_superseded, limit_length=True
+ )
+ for comment in merge_proposal.all_comments
+ )
merge_proposal = merge_proposal.supersedes
- comments = sorted(comments, key=operator.attrgetter('date'))
+ comments = sorted(comments, key=operator.attrgetter("date"))
if strip_invisible:
comments = [c for c in comments if c.visible or c.author == user]
self._populate_previewdiffs(comments)
@@ -751,25 +754,31 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
Only operated on objects providing `ICodeReviewComment`.
"""
- comments = [comment for comment in comments
- if ICodeReviewComment.providedBy(comment)]
+ comments = [
+ comment
+ for comment in comments
+ if ICodeReviewComment.providedBy(comment)
+ ]
cric_set = getUtility(ICodeReviewInlineCommentSet)
relations = cric_set.getPreviewDiffsForComments(comments)
for comment in comments:
- get_property_cache(
- comment).previewdiff_id = relations.get(comment.id)
+ get_property_cache(comment).previewdiff_id = relations.get(
+ comment.id
+ )
@property
def label(self):
return "Merge %s into %s" % (
self.context.merge_source.identity,
- self.context.merge_target.identity)
+ self.context.merge_target.identity,
+ )
@property
def pending_diff(self):
return (
- self.context.next_preview_diff_job is not None or
- self.context.merge_source.pending_updates)
+ self.context.next_preview_diff_job is not None
+ or self.context.merge_source.pending_updates
+ )
@cachedproperty
def preview_diff(self):
@@ -791,7 +800,8 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
# blueprints.
return []
return list(
- self.context.source_branch.getSpecificationLinks(self.user))
+ self.context.source_branch.getSpecificationLinks(self.user)
+ )
@cachedproperty
def linked_bugtasks(self):
@@ -809,10 +819,11 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
def description_html(self):
"""The description as widget HTML."""
mp = self.context
- description = IBranchMergeProposal['description']
+ description = IBranchMergeProposal["description"]
title = "Description of the change"
return TextAreaEditorWidget(
- mp, description, title, edit_view='+edit-description')
+ mp, description, title, edit_view="+edit-description"
+ )
@property
def opengraph_description(self):
@@ -831,23 +842,28 @@ class BranchMergeProposalView(LaunchpadFormView, UnmergedRevisionsMixin,
def commit_message_html(self):
"""The commit message as widget HTML."""
mp = self.context
- commit_message = IBranchMergeProposal['commit_message']
+ commit_message = IBranchMergeProposal["commit_message"]
title = "Commit message"
return TextAreaEditorWidget(
- mp, commit_message, title, edit_view='+edit-commit-message')
+ mp, commit_message, title, edit_view="+edit-commit-message"
+ )
@property
def status_config(self):
"""The config to configure the ChoiceSource JS widget."""
- return simplejson.dumps({
- 'status_widget_items': vocabulary_to_choice_edit_items(
- self._createStatusVocabulary(),
- css_class_prefix='mergestatus'),
- 'status_value': self.context.queue_status.title,
- 'source_revid': self.source_revid,
- 'user_can_edit_status': check_permission(
- 'launchpad.Edit', self.context),
- })
+ return simplejson.dumps(
+ {
+ "status_widget_items": vocabulary_to_choice_edit_items(
+ self._createStatusVocabulary(),
+ css_class_prefix="mergestatus",
+ ),
+ "status_value": self.context.queue_status.title,
+ "source_revid": self.source_revid,
+ "user_can_edit_status": check_permission(
+ "launchpad.Edit", self.context
+ ),
+ }
+ )
@property
def show_diff_update_link(self):
@@ -874,7 +890,7 @@ class BranchMergeProposalRescanView(LaunchpadEditFormView):
field_names = []
- @action('Rescan', name='rescan')
+ @action("Rescan", name="rescan")
def rescan(self, action, data):
source_job = self.context.merge_source.getLatestScanJob()
target_job = self.context.merge_target.getLatestScanJob()
@@ -897,19 +913,21 @@ class DecoratedCodeReviewVoteReference:
CodeReviewVote.NEEDS_INFO: CodeReviewVote.NEEDS_INFO.title,
CodeReviewVote.NEEDS_FIXING: CodeReviewVote.NEEDS_FIXING.title,
CodeReviewVote.NEEDS_RESUBMITTING: (
- CodeReviewVote.NEEDS_RESUBMITTING.title),
- }
+ CodeReviewVote.NEEDS_RESUBMITTING.title
+ ),
+ }
def __init__(self, context, user, users_vote):
self.context = context
- self.can_change_review = (user == context.reviewer)
+ self.can_change_review = user == context.reviewer
if user is None:
self.user_can_review = False
else:
# The user cannot review for a requested team review if the user
# has already reviewed this proposal.
- self.user_can_review = (self.can_change_review or
- (user.inTeam(context.reviewer) and (users_vote is None)))
+ self.user_can_review = self.can_change_review or (
+ user.inTeam(context.reviewer) and (users_vote is None)
+ )
if context.reviewer == user:
self.user_can_claim = False
else:
@@ -921,10 +939,11 @@ class DecoratedCodeReviewVoteReference:
@cachedproperty
def trusted(self):
- """ Is the person a trusted reviewer."""
+ """Is the person a trusted reviewer."""
proposal = self.context.branch_merge_proposal
return proposal.merge_target.isPersonTrustedReviewer(
- self.context.reviewer)
+ self.context.reviewer
+ )
@property
def show_date_requested(self):
@@ -940,7 +959,7 @@ class DecoratedCodeReviewVoteReference:
def review_type_str(self):
"""We want '' not 'None' if no type set."""
if self.context.review_type is None:
- return ''
+ return ""
return self.context.review_type
@property
@@ -969,12 +988,13 @@ class BranchMergeProposalVoteView(LaunchpadView):
# the branch is not in a final state. We want to show the table as
# the menu link is now shown in the table itself.
can_request_review = (
- check_permission('launchpad.Edit', self.context) and
- self.context.isMergable())
+ check_permission("launchpad.Edit", self.context)
+ and self.context.isMergable()
+ )
# Show the table if there are review to show, or the user can review,
# or if the user can request a review.
- return (len(self.reviews) > 0 or can_request_review)
+ return len(self.reviews) > 0 or can_request_review
@cachedproperty
def reviews(self):
@@ -983,32 +1003,37 @@ class BranchMergeProposalVoteView(LaunchpadView):
# This would use getUsersVoteReference, but we need to
# be able to cache the property. We don't need to normalize
# the review types.
- users_vote = sorted((uv for uv in self.context.votes
- if uv.reviewer == self.user),
- key=operator.attrgetter('date_created'))
+ users_vote = sorted(
+ (uv for uv in self.context.votes if uv.reviewer == self.user),
+ key=operator.attrgetter("date_created"),
+ )
final_vote = users_vote[0] if users_vote else None
- return [DecoratedCodeReviewVoteReference(vote, self.user, final_vote)
- for vote in self.context.votes
- if check_permission('launchpad.LimitedView', vote.reviewer)]
+ return [
+ DecoratedCodeReviewVoteReference(vote, self.user, final_vote)
+ for vote in self.context.votes
+ if check_permission("launchpad.LimitedView", vote.reviewer)
+ ]
@cachedproperty
def current_reviews(self):
"""The current votes ordered by vote then date."""
# We want the reviews in a specific order.
# Disapprovals first, then approvals, then abstentions.
- reviews = [review for review in self.reviews
- if review.comment is not None]
- return sorted(reviews, key=operator.attrgetter('date_of_comment'),
- reverse=True)
+ reviews = [
+ review for review in self.reviews if review.comment is not None
+ ]
+ return sorted(
+ reviews, key=operator.attrgetter("date_of_comment"), reverse=True
+ )
@cachedproperty
def requested_reviews(self):
"""Reviews requested but not yet done."""
- reviews = [review for review in self.reviews
- if review.comment is None]
+ reviews = [review for review in self.reviews if review.comment is None]
# Now sort so the most recently created is first.
- return sorted(reviews, key=operator.attrgetter('date_created'),
- reverse=True)
+ return sorted(
+ reviews, key=operator.attrgetter("date_created"), reverse=True
+ )
class BranchMergeProposalScheduleUpdateDiffView(LaunchpadEditFormView):
@@ -1017,7 +1042,7 @@ class BranchMergeProposalScheduleUpdateDiffView(LaunchpadEditFormView):
field_names = []
- @action('Update', name='update')
+ @action("Update", name="update")
def update(self, action, data):
self.context.scheduleDiffUpdates()
self.request.response.addNotification("Diff update scheduled")
@@ -1027,12 +1052,13 @@ class BranchMergeProposalScheduleUpdateDiffView(LaunchpadEditFormView):
class IReviewRequest(Interface):
"""Schema for requesting a review."""
- reviewer = copy_field(ICodeReviewVoteReference['reviewer'])
+ reviewer = copy_field(ICodeReviewVoteReference["reviewer"])
review_type = copy_field(
- ICodeReviewVoteReference['review_type'],
- description='Lowercase keywords describing the type of review you '
- 'would like to be performed.')
+ ICodeReviewVoteReference["review_type"],
+ description="Lowercase keywords describing the type of review you "
+ "would like to be performed.",
+ )
class BranchMergeProposalRequestReviewView(LaunchpadEditFormView):
@@ -1044,7 +1070,7 @@ class BranchMergeProposalRequestReviewView(LaunchpadEditFormView):
@property
def initial_values(self):
"""Force the non-BMP values to None."""
- return {'reviewer': None, 'review_type': None}
+ return {"reviewer": None, "review_type": None}
@property
def adapters(self):
@@ -1054,8 +1080,9 @@ class BranchMergeProposalRequestReviewView(LaunchpadEditFormView):
@property
def is_needs_review(self):
"""Return True if queue status is NEEDS_REVIEW."""
- return (self.context.queue_status ==
- BranchMergeProposalStatus.NEEDS_REVIEW)
+ return (
+ self.context.queue_status == BranchMergeProposalStatus.NEEDS_REVIEW
+ )
@property
def next_url(self):
@@ -1067,25 +1094,28 @@ class BranchMergeProposalRequestReviewView(LaunchpadEditFormView):
"""Request a `review_type` review from `candidate` and email them."""
self.context.nominateReviewer(candidate, self.user, review_type)
- @action('Request Review', name='review')
+ @action("Request Review", name="review")
@notify
def review_action(self, action, data):
"""Set 'Needs review' status, nominate reviewers, send emails."""
self.context.requestReview()
- candidate = data.pop('reviewer', None)
- review_type = data.pop('review_type', None)
+ candidate = data.pop("reviewer", None)
+ review_type = data.pop("review_type", None)
if candidate is not None:
self.requestReview(candidate, review_type)
def validate(self, data):
"""Ensure that the proposal is in an appropriate state."""
if not self.context.isMergable():
- self.addError("The merge proposal is not an a valid state to "
- "mark as 'Needs review'.")
+ self.addError(
+ "The merge proposal is not an a valid state to "
+ "mark as 'Needs review'."
+ )
-class MergeProposalEditView(LaunchpadEditFormView,
- BranchMergeProposalRevisionIdMixin):
+class MergeProposalEditView(
+ LaunchpadEditFormView, BranchMergeProposalRevisionIdMixin
+):
"""A base class for merge proposal edit views."""
def initialize(self):
@@ -1098,15 +1128,18 @@ class MergeProposalEditView(LaunchpadEditFormView,
class ResubmitSchema(IBranchMergeProposal):
break_link = Bool(
- title='Start afresh',
+ title="Start afresh",
description=(
- 'Do not show old conversation and do not link to superseded'
- ' proposal.'),
- default=False,)
+ "Do not show old conversation and do not link to superseded"
+ " proposal."
+ ),
+ default=False,
+ )
-class BranchMergeProposalResubmitView(LaunchpadFormView,
- UnmergedRevisionsMixin):
+class BranchMergeProposalResubmitView(
+ LaunchpadFormView, UnmergedRevisionsMixin
+):
"""The view to resubmit a proposal to merge."""
schema = ResubmitSchema
@@ -1114,11 +1147,14 @@ class BranchMergeProposalResubmitView(LaunchpadFormView,
page_title = label = "Resubmit proposal to merge"
custom_widget_source_git_ref = CustomWidgetFactory(
- GitRefWidget, require_branch=True)
+ GitRefWidget, require_branch=True
+ )
custom_widget_target_git_ref = CustomWidgetFactory(
- GitRefWidget, require_branch=True)
+ GitRefWidget, require_branch=True
+ )
custom_widget_prerequisite_git_ref = CustomWidgetFactory(
- GitRefWidget, require_branch=True)
+ GitRefWidget, require_branch=True
+ )
def initialize(self):
self.cancel_url = canonical_url(self.context)
@@ -1128,52 +1164,63 @@ class BranchMergeProposalResubmitView(LaunchpadFormView,
def field_names(self):
if IBranch.providedBy(self.context.merge_source):
field_names = [
- 'source_branch',
- 'target_branch',
- 'prerequisite_branch',
- ]
+ "source_branch",
+ "target_branch",
+ "prerequisite_branch",
+ ]
else:
field_names = [
- 'source_git_ref',
- 'target_git_ref',
- 'prerequisite_git_ref',
- ]
- field_names.extend([
- 'description',
- 'commit_message',
- 'break_link',
- ])
+ "source_git_ref",
+ "target_git_ref",
+ "prerequisite_git_ref",
+ ]
+ field_names.extend(
+ [
+ "description",
+ "commit_message",
+ "break_link",
+ ]
+ )
return field_names
@property
def initial_values(self):
UNSET = object()
- items = ((key, getattr(self.context, key, UNSET)) for key in
- self.field_names if key != 'break_link')
+ items = (
+ (key, getattr(self.context, key, UNSET))
+ for key in self.field_names
+ if key != "break_link"
+ )
return dict(item for item in items if item[1] is not UNSET)
- @action('Resubmit', name='resubmit')
+ @action("Resubmit", name="resubmit")
@notify
def resubmit_action(self, action, data):
"""Resubmit this proposal."""
try:
if IBranch.providedBy(self.context.merge_source):
- merge_source = data['source_branch']
- merge_target = data['target_branch']
- merge_prerequisite = data['prerequisite_branch']
+ merge_source = data["source_branch"]
+ merge_target = data["target_branch"]
+ merge_prerequisite = data["prerequisite_branch"]
else:
- merge_source = data['source_git_ref']
- merge_target = data['target_git_ref']
- merge_prerequisite = data.get('prerequisite_git_ref')
+ merge_source = data["source_git_ref"]
+ merge_target = data["target_git_ref"]
+ merge_prerequisite = data.get("prerequisite_git_ref")
proposal = self.context.resubmit(
- self.user, merge_source, merge_target, merge_prerequisite,
- data['commit_message'], data['description'],
- data['break_link'])
+ self.user,
+ merge_source,
+ merge_target,
+ merge_prerequisite,
+ data["commit_message"],
+ data["description"],
+ data["break_link"],
+ )
except BranchMergeProposalExists as e:
message = structured(
'Cannot resubmit because <a href="%(url)s">a similar merge'
- ' proposal</a> is already active.',
- url=canonical_url(e.existing_proposal))
+ " proposal</a> is already active.",
+ url=canonical_url(e.existing_proposal),
+ )
self.request.response.addErrorNotification(message)
self.next_url = canonical_url(self.context)
return None
@@ -1186,11 +1233,12 @@ class BranchMergeProposalResubmitView(LaunchpadFormView,
class BranchMergeProposalEditView(MergeProposalEditView):
"""The view to control the editing of merge proposals."""
+
schema = IBranchMergeProposal
page_title = label = "Edit branch merge proposal"
field_names = ["commit_message", "whiteboard"]
- @action('Update', name='update')
+ @action("Update", name="update")
def update_action(self, action, data):
"""Update the whiteboard and go back to the source branch."""
self.updateContextFromData(data)
@@ -1202,9 +1250,9 @@ class BranchMergeProposalCommitMessageEditView(MergeProposalEditView):
schema = IBranchMergeProposal
label = "Edit merge proposal commit message"
page_title = label
- field_names = ['commit_message']
+ field_names = ["commit_message"]
- @action('Update', name='update')
+ @action("Update", name="update")
def update_action(self, action, data):
"""Update the commit message."""
self.updateContextFromData(data)
@@ -1216,9 +1264,9 @@ class BranchMergeProposalDescriptionEditView(MergeProposalEditView):
schema = IBranchMergeProposal
label = "Edit merge proposal description"
page_title = label
- field_names = ['description']
+ field_names = ["description"]
- @action('Update', name='update')
+ @action("Update", name="update")
def update_action(self, action, data):
"""Update the commit message."""
self.updateContextFromData(data)
@@ -1226,9 +1274,10 @@ class BranchMergeProposalDescriptionEditView(MergeProposalEditView):
class BranchMergeProposalDeleteView(MergeProposalEditView):
"""The view to control the deletion of merge proposals."""
+
schema = IBranchMergeProposal
field_names = []
- page_title = label = 'Delete proposal to merge branch'
+ page_title = label = "Delete proposal to merge branch"
def initialize(self):
# Store the source branch for `next_url` to make sure that
@@ -1237,7 +1286,7 @@ class BranchMergeProposalDeleteView(MergeProposalEditView):
self.merge_source = self.context.merge_source
super().initialize()
- @action('Delete proposal', name='delete')
+ @action("Delete proposal", name="delete")
def delete_action(self, action, data):
"""Delete the merge proposal and go back to the source branch."""
self.context.deleteProposal()
@@ -1247,6 +1296,7 @@ class BranchMergeProposalDeleteView(MergeProposalEditView):
class BranchMergeProposalMergedView(LaunchpadEditFormView):
"""The view to mark a merge proposal as merged."""
+
schema = IBranchMergeProposal
page_title = label = "Edit branch merge proposal"
for_input = True
@@ -1267,13 +1317,13 @@ class BranchMergeProposalMergedView(LaunchpadEditFormView):
revno = self.context.merged_revno
else:
revno = self.context.merge_target.revision_count
- return {'merged_revno': revno}
+ return {"merged_revno": revno}
else:
if self.context.merged_revision_id is not None:
revision_id = self.context.merged_revision_id
else:
revision_id = self.context.merge_target.commit_sha1
- return {'merged_revision_id': revision_id}
+ return {"merged_revision_id": revision_id}
@property
def next_url(self):
@@ -1281,31 +1331,34 @@ class BranchMergeProposalMergedView(LaunchpadEditFormView):
cancel_url = next_url
- @action('Mark as Merged', name='mark_merged')
+ @action("Mark as Merged", name="mark_merged")
@notify
def mark_merged_action(self, action, data):
"""Update the whiteboard and go back to the source branch."""
if IBranch.providedBy(self.context.merge_target):
- kwargs = {'merged_revno': data['merged_revno']}
+ kwargs = {"merged_revno": data["merged_revno"]}
else:
- kwargs = {'merged_revision_id': data['merged_revision_id']}
+ kwargs = {"merged_revision_id": data["merged_revision_id"]}
if self.context.queue_status == BranchMergeProposalStatus.MERGED:
self.context.markAsMerged(**kwargs)
self.request.response.addNotification(
- 'The proposal\'s merged revision has been updated.')
+ "The proposal's merged revision has been updated."
+ )
else:
self.context.markAsMerged(merge_reporter=self.user, **kwargs)
self.request.response.addNotification(
- 'The proposal has now been marked as merged.')
+ "The proposal has now been marked as merged."
+ )
def validate(self, data):
# Ensure a positive integer value.
- revno = data.get('merged_revno')
+ revno = data.get("merged_revno")
if revno is not None:
if revno <= 0:
self.setFieldError(
- 'merged_revno',
- 'Revision numbers must be positive integers.')
+ "merged_revno",
+ "Revision numbers must be positive integers.",
+ )
class BranchMergeProposalSubscribersView(LaunchpadView):
@@ -1324,7 +1377,8 @@ class BranchMergeProposalSubscribersView(LaunchpadView):
# only once, and for the most detailed subscription from the source
# and target branches.
self._status_subscribers = (
- self._status_subscribers - self._full_subscribers)
+ self._status_subscribers - self._full_subscribers
+ )
def _add_subscribers_for_branch(self, branch):
"""Add the subscribers to the subscription sets for the branch."""
@@ -1343,13 +1397,15 @@ class BranchMergeProposalSubscribersView(LaunchpadView):
def full_subscribers(self):
"""A list of full subscribers ordered by displayname."""
return sorted(
- self._full_subscribers, key=operator.attrgetter('displayname'))
+ self._full_subscribers, key=operator.attrgetter("displayname")
+ )
@cachedproperty
def status_subscribers(self):
"""A list of full subscribers ordered by displayname."""
return sorted(
- self._status_subscribers, key=operator.attrgetter('displayname'))
+ self._status_subscribers, key=operator.attrgetter("displayname")
+ )
@property
def has_subscribers(self):
@@ -1357,8 +1413,9 @@ class BranchMergeProposalSubscribersView(LaunchpadView):
return len(self.full_subscribers) + len(self.status_subscribers)
-class BranchMergeProposalChangeStatusView(MergeProposalEditView,
- BranchMergeProposalStatusMixin):
+class BranchMergeProposalChangeStatusView(
+ MergeProposalEditView, BranchMergeProposalStatusMixin
+):
page_title = label = "Change merge proposal status"
schema = IBranchMergeProposal
@@ -1367,16 +1424,20 @@ class BranchMergeProposalChangeStatusView(MergeProposalEditView,
def setUpFields(self):
MergeProposalEditView.setUpFields(self)
# Add the custom restricted queue status widget.
- status_field = self.schema['queue_status']
+ status_field = self.schema["queue_status"]
status_choice = Choice(
- __name__='queue_status', title=status_field.title,
- required=True, vocabulary=self._createStatusVocabulary())
+ __name__="queue_status",
+ title=status_field.title,
+ required=True,
+ vocabulary=self._createStatusVocabulary(),
+ )
status_field = form.Fields(
- status_choice, render_context=self.render_context)
+ status_choice, render_context=self.render_context
+ )
self.form_fields = status_field + self.form_fields
- @action('Change Status', name='update')
+ @action("Change Status", name="update")
@notify
def update_action(self, action, data):
"""Update the status."""
@@ -1384,38 +1445,41 @@ class BranchMergeProposalChangeStatusView(MergeProposalEditView,
curr_status = self.context.queue_status
# Assume for now that the queue_status in the data is a valid
# transition from where we are.
- rev_id = self.request.form['revno']
- new_status = data['queue_status']
+ rev_id = self.request.form["revno"]
+ new_status = data["queue_status"]
# Don't do anything if the user hasn't changed the status.
if new_status == curr_status:
return
- assert new_status != BranchMergeProposalStatus.SUPERSEDED, (
- 'Superseded is done via an action, not by setting status.')
+ assert (
+ new_status != BranchMergeProposalStatus.SUPERSEDED
+ ), "Superseded is done via an action, not by setting status."
self.context.setStatus(new_status, self.user, rev_id)
class IAddVote(Interface):
"""Interface for use as a schema for CodeReviewComment forms."""
- vote = copy_field(ICodeReviewComment['vote'], required=True)
+ vote = copy_field(ICodeReviewComment["vote"], required=True)
review_type = copy_field(
- ICodeReviewVoteReference['review_type'],
- description='Lowercase keywords describing the type of review you '
- 'are performing.')
+ ICodeReviewVoteReference["review_type"],
+ description="Lowercase keywords describing the type of review you "
+ "are performing.",
+ )
- comment = Text(title=_('Comment'), required=False)
+ comment = Text(title=_("Comment"), required=False)
class BranchMergeProposalAddVoteView(LaunchpadFormView):
"""View for adding a CodeReviewComment."""
schema = IAddVote
- field_names = ['vote', 'review_type', 'comment']
+ field_names = ["vote", "review_type", "comment"]
custom_widget_comment = CustomWidgetFactory(
- TextAreaWidget, cssClass='comment-text')
+ TextAreaWidget, cssClass="comment-text"
+ )
@cachedproperty
def initial_values(self):
@@ -1423,11 +1487,11 @@ class BranchMergeProposalAddVoteView(LaunchpadFormView):
# Look to see if there is a vote reference already for the user.
if self.users_vote_ref is None:
# Look at the request to see if there is something there.
- review_type = self.request.form.get('review_type', '')
+ review_type = self.request.form.get("review_type", "")
else:
review_type = self.users_vote_ref.review_type
# We'll be positive here and default the vote to approve.
- return {'vote': CodeReviewVote.APPROVE, 'review_type': review_type}
+ return {"vote": CodeReviewVote.APPROVE, "review_type": review_type}
def initialize(self):
"""Get the users existing vote reference."""
@@ -1441,7 +1505,7 @@ class BranchMergeProposalAddVoteView(LaunchpadFormView):
if self.user is None:
# Anonymous users are not valid voters.
- raise AssertionError('Invalid voter')
+ raise AssertionError("Invalid voter")
super().initialize()
def setUpFields(self):
@@ -1449,26 +1513,28 @@ class BranchMergeProposalAddVoteView(LaunchpadFormView):
self.reviewer = self.user.name
# claim_review is set in situations where a user is reviewing on
# behalf of a team.
- claim_review = self.request.form.get('claim')
+ claim_review = self.request.form.get("claim")
if claim_review and self.users_vote_ref is None:
team = getUtility(IPersonSet).getByName(claim_review)
if team is not None and self.user.inTeam(team):
# If the review type is None, then don't show the field.
self.reviewer = team.name
- if self.initial_values['review_type'] == '':
- self.form_fields = self.form_fields.omit('review_type')
+ if self.initial_values["review_type"] == "":
+ self.form_fields = self.form_fields.omit("review_type")
else:
# Disable the review_type field
- self.form_fields['review_type'].for_display = True
+ self.form_fields["review_type"].for_display = True
@property
def label(self):
"""The pagetitle and heading."""
return "Review merge proposal for %s" % (
- self.context.merge_source.identity)
+ self.context.merge_source.identity
+ )
+
page_title = label
- @action('Save Review', name='vote')
+ @action("Save Review", name="vote")
def vote_action(self, action, data):
"""Create the comment."""
# Get the review type from the data dict. If the setUpFields set the
@@ -1476,18 +1542,23 @@ class BranchMergeProposalAddVoteView(LaunchpadFormView):
# the data dict. If this is the case, get the review_type from the
# hidden field that we so cunningly added to the form.
review_type = data.get(
- 'review_type', self.request.form.get('review_type'))
+ "review_type", self.request.form.get("review_type")
+ )
# Translate the request parameter back into what our object model
# needs.
- if review_type == '':
+ if review_type == "":
review_type = None
# Get the reviewer from the hidden input.
- reviewer_name = self.request.form.get('reviewer')
+ reviewer_name = self.request.form.get("reviewer")
reviewer = getUtility(IPersonSet).getByName(reviewer_name)
- if (reviewer.is_team and self.user.inTeam(reviewer) and
- self.users_vote_ref is None):
+ if (
+ reviewer.is_team
+ and self.user.inTeam(reviewer)
+ and self.users_vote_ref is None
+ ):
vote_ref = self.context.getUsersVoteReference(
- reviewer, review_type)
+ reviewer, review_type
+ )
if vote_ref is not None:
# Claim this vote reference, i.e. say that the individual
# self. user is doing this review ond behalf of the 'reviewer'
@@ -1495,8 +1566,12 @@ class BranchMergeProposalAddVoteView(LaunchpadFormView):
vote_ref.claimReview(self.user)
self.context.createComment(
- self.user, subject=None, content=data['comment'],
- vote=data['vote'], review_type=review_type)
+ self.user,
+ subject=None,
+ content=data["comment"],
+ vote=data["vote"],
+ review_type=review_type,
+ )
@property
def next_url(self):
@@ -1523,6 +1598,5 @@ class PreviewDiffHTMLRepresentation:
def __call__(self):
"""Render `BugBranch` as XHTML using the webservice."""
- diff_view = getMultiAdapter(
- (self.diff, self.request), name="+diff")
+ diff_view = getMultiAdapter((self.diff, self.request), name="+diff")
return diff_view()
diff --git a/lib/lp/code/browser/branchmergeproposallisting.py b/lib/lp/code/browser/branchmergeproposallisting.py
index 5a219da..d98b467 100644
--- a/lib/lp/code/browser/branchmergeproposallisting.py
+++ b/lib/lp/code/browser/branchmergeproposallisting.py
@@ -4,60 +4,52 @@
"""Base class view for branch merge proposal listings."""
__all__ = [
- 'ActiveReviewsView',
- 'BranchActiveReviewsView',
- 'BranchDependentMergesView',
- 'BranchMergeProposalListingItem',
- 'BranchMergeProposalListingView',
- 'PersonActiveReviewsView',
- 'PersonProductActiveReviewsView',
- ]
+ "ActiveReviewsView",
+ "BranchActiveReviewsView",
+ "BranchDependentMergesView",
+ "BranchMergeProposalListingItem",
+ "BranchMergeProposalListingView",
+ "PersonActiveReviewsView",
+ "PersonProductActiveReviewsView",
+]
from operator import attrgetter
from lazr.delegates import delegate_to
-from lazr.enum import (
- EnumeratedType,
- Item,
- use_template,
- )
+from lazr.enum import EnumeratedType, Item, use_template
from zope.component import getUtility
-from zope.interface import (
- implementer,
- Interface,
- )
+from zope.interface import Interface, implementer
from zope.schema import Choice
from lp import _
from lp.app.browser.launchpadform import LaunchpadFormView
from lp.app.interfaces.launchpad import IHeadingContext
from lp.app.widgets.itemswidgets import LaunchpadDropdownWidget
-from lp.code.enums import (
- BranchMergeProposalStatus,
- CodeReviewVote,
- )
+from lp.code.enums import BranchMergeProposalStatus, CodeReviewVote
from lp.code.interfaces.branchmergeproposal import (
BRANCH_MERGE_PROPOSAL_FINAL_STATES,
IBranchMergeProposal,
IBranchMergeProposalGetter,
IBranchMergeProposalListingBatchNavigator,
- )
+)
from lp.code.interfaces.hasbranches import IHasMergeProposals
from lp.services.config import config
-from lp.services.propertycache import (
- cachedproperty,
- get_property_cache,
- )
+from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.batching import TableBatchNavigator
-@delegate_to(IBranchMergeProposal, context='context')
+@delegate_to(IBranchMergeProposal, context="context")
class BranchMergeProposalListingItem:
"""A branch merge proposal that knows summary values for comments."""
- def __init__(self, branch_merge_proposal, summary, proposal_reviewer,
- vote_references=None):
+ def __init__(
+ self,
+ branch_merge_proposal,
+ summary,
+ proposal_reviewer,
+ vote_references=None,
+ ):
self.context = branch_merge_proposal
self.summary = summary
self.proposal_reviewer = proposal_reviewer
@@ -83,9 +75,12 @@ class BranchMergeProposalListingItem:
for ref in self.vote_references:
if ref.comment is not None and ref.comment.vote == vote:
reviewers.append(ref.reviewer.unique_displayname)
- yield {'name': vote.name, 'title': vote.title,
- 'count': vote_count,
- 'reviewers': ', '.join(sorted(reviewers))}
+ yield {
+ "name": vote.name,
+ "title": vote.title,
+ "count": vote_count,
+ "reviewers": ", ".join(sorted(reviewers)),
+ }
@property
def vote_type_count(self):
@@ -96,7 +91,7 @@ class BranchMergeProposalListingItem:
@property
def comment_count(self):
"""The number of comments (that aren't votes)."""
- return self.summary['comment_count']
+ return self.summary["comment_count"]
@property
def has_no_activity(self):
@@ -135,9 +130,11 @@ class BranchMergeProposalListingBatchNavigator(TableBatchNavigator):
def __init__(self, view):
super().__init__(
- view.getVisibleProposalsForUser(), view.request,
+ view.getVisibleProposalsForUser(),
+ view.request,
columns_to_show=view.extra_columns,
- size=config.launchpad.branchlisting_batch_size)
+ size=config.launchpad.branchlisting_batch_size,
+ )
self.view = view
@cachedproperty
@@ -149,13 +146,15 @@ class BranchMergeProposalListingBatchNavigator(TableBatchNavigator):
"""A dict of proposals to counts of votes and comments."""
utility = getUtility(IBranchMergeProposalGetter)
return utility.getVoteSummariesForProposals(
- self._proposals_for_current_batch)
+ self._proposals_for_current_batch
+ )
def _createItem(self, proposal):
"""Create the listing item for the proposal."""
summary = self._vote_summaries[proposal]
- return BranchMergeProposalListingItem(proposal, summary,
- proposal_reviewer=self.view.getUserFromContext())
+ return BranchMergeProposalListingItem(
+ proposal, summary, proposal_reviewer=self.view.getUserFromContext()
+ )
@cachedproperty
def proposals(self):
@@ -173,11 +172,20 @@ class BranchMergeProposalListingBatchNavigator(TableBatchNavigator):
class FilterableStatusValues(EnumeratedType):
"""Selectable values for filtering the merge proposal listings."""
+
use_template(BranchMergeProposalStatus)
sort_order = (
- 'ALL', 'WORK_IN_PROGRESS', 'NEEDS_REVIEW', 'CODE_APPROVED',
- 'REJECTED', 'MERGED', 'MERGE_FAILED', 'QUEUED', 'SUPERSEDED')
+ "ALL",
+ "WORK_IN_PROGRESS",
+ "NEEDS_REVIEW",
+ "CODE_APPROVED",
+ "REJECTED",
+ "MERGED",
+ "MERGE_FAILED",
+ "QUEUED",
+ "SUPERSEDED",
+ )
ALL = Item("Any status")
@@ -187,21 +195,23 @@ class BranchMergeProposalFilterSchema(Interface):
# Stats and status attributes
status = Choice(
- title=_('Status'), vocabulary=FilterableStatusValues,
- default=FilterableStatusValues.ALL,)
+ title=_("Status"),
+ vocabulary=FilterableStatusValues,
+ default=FilterableStatusValues.ALL,
+ )
class BranchMergeProposalListingView(LaunchpadFormView):
"""A base class for views of branch merge proposal listings."""
schema = BranchMergeProposalFilterSchema
- field_names = ['status']
+ field_names = ["status"]
custom_widget_status = LaunchpadDropdownWidget
extra_columns = []
_queue_status = None
- page_title = 'Merge proposals'
+ page_title = "Merge proposals"
@property
def label(self):
@@ -212,12 +222,12 @@ class BranchMergeProposalListingView(LaunchpadFormView):
@property
def initial_values(self):
- return {'status': FilterableStatusValues.ALL}
+ return {"status": FilterableStatusValues.ALL}
@cachedproperty
def status_value(self):
"""The effective value of the status widget."""
- widget = self.widgets['status']
+ widget = self.widgets["status"]
if widget.hasValidInput():
return widget.getInputValue()
else:
@@ -229,7 +239,7 @@ class BranchMergeProposalListingView(LaunchpadFormView):
if self.status_value == FilterableStatusValues.ALL:
return BranchMergeProposalStatus.items
else:
- return (BranchMergeProposalStatus.items[self.status_value.name], )
+ return (BranchMergeProposalStatus.items[self.status_value.name],)
@property
def proposals(self):
@@ -243,7 +253,8 @@ class BranchMergeProposalListingView(LaunchpadFormView):
def getVisibleProposalsForUser(self):
"""Branch merge proposals that are visible by the logged in user."""
return IHasMergeProposals(self.context).getMergeProposals(
- self.status_filter, self.user, eager_load=True)
+ self.status_filter, self.user, eager_load=True
+ )
@cachedproperty
def proposal_count(self):
@@ -257,13 +268,15 @@ class BranchMergeProposalListingView(LaunchpadFormView):
return "%s has no merge proposals." % self.context.displayname
else:
return "%s has no merge proposals with status: %s" % (
- self.context.displayname, self.status_value.title)
+ self.context.displayname,
+ self.status_value.title,
+ )
class BranchDependentMergesView(BranchMergeProposalListingView):
"""Branch merge proposals that list this branch as a prerequisite."""
- page_title = 'Dependent merge proposals'
+ page_title = "Dependent merge proposals"
@property
def label(self):
@@ -272,7 +285,8 @@ class BranchDependentMergesView(BranchMergeProposalListingView):
def getVisibleProposalsForUser(self):
"""See `BranchMergeProposalListingView`."""
return self.context.getDependentMergeProposals(
- self.status_filter, self.user, eager_load=True)
+ self.status_filter, self.user, eager_load=True
+ )
class ActiveReviewsView(BranchMergeProposalListingView):
@@ -283,21 +297,24 @@ class ActiveReviewsView(BranchMergeProposalListingView):
show_diffs = False
# The grouping classifications.
- APPROVED = 'approved'
- TO_DO = 'to_do'
- ARE_DOING = 'are_doing'
- CAN_DO = 'can_do'
- MINE = 'mine'
- OTHER = 'other'
- WIP = 'wip'
+ APPROVED = "approved"
+ TO_DO = "to_do"
+ ARE_DOING = "are_doing"
+ CAN_DO = "can_do"
+ MINE = "mine"
+ OTHER = "other"
+ WIP = "wip"
def getProposals(self):
"""Get the proposals for the view."""
return self.context.getMergeProposals(
status=(
BranchMergeProposalStatus.CODE_APPROVED,
- BranchMergeProposalStatus.NEEDS_REVIEW),
- visible_by_user=self.user, eager_load=True)
+ BranchMergeProposalStatus.NEEDS_REVIEW,
+ ),
+ visible_by_user=self.user,
+ eager_load=True,
+ )
def _getReviewGroup(self, proposal, votes, reviewer):
"""One of APPROVED, MINE, TO_DO, CAN_DO, ARE_DOING, OTHER or WIP.
@@ -328,10 +345,13 @@ class ActiveReviewsView(BranchMergeProposalListingView):
if proposal.queue_status == bmp_status.WORK_IN_PROGRESS:
return self.WIP
- if (reviewer is not None and
- (proposal.merge_source.owner == reviewer or
- (reviewer.inTeam(proposal.merge_source.owner) and
- proposal.registrant == reviewer))):
+ if reviewer is not None and (
+ proposal.merge_source.owner == reviewer
+ or (
+ reviewer.inTeam(proposal.merge_source.owner)
+ and proposal.registrant == reviewer
+ )
+ ):
return self.MINE
result = self.OTHER
@@ -367,15 +387,18 @@ class ActiveReviewsView(BranchMergeProposalListingView):
for proposal in proposals:
proposal_votes = all_votes[proposal]
review_group = self._getReviewGroup(
- proposal, proposal_votes, reviewer)
+ proposal, proposal_votes, reviewer
+ )
self.review_groups.setdefault(review_group, []).append(
BranchMergeProposalListingItem(
- proposal, vote_summaries[proposal], None, proposal_votes))
+ proposal, vote_summaries[proposal], None, proposal_votes
+ )
+ )
if proposal.preview_diff is not None:
self.show_diffs = True
# Sort each collection...
for group in self.review_groups.values():
- group.sort(key=attrgetter('sort_key'))
+ group.sort(key=attrgetter("sort_key"))
get_property_cache(self).proposal_count = len(proposals)
@cachedproperty
@@ -383,17 +406,18 @@ class ActiveReviewsView(BranchMergeProposalListingView):
"""Return a dict of headings for the groups."""
reviewer = self._getReviewer()
headings = {
- self.APPROVED: 'Approved reviews ready to land',
- self.TO_DO: 'Reviews you have to do',
- self.ARE_DOING: 'Reviews you are doing',
- self.CAN_DO: 'Requested reviews you can do',
- self.MINE: 'Reviews you are waiting on',
- self.OTHER: 'Other reviews you are not actively reviewing',
- self.WIP: 'Work in progress'}
+ self.APPROVED: "Approved reviews ready to land",
+ self.TO_DO: "Reviews you have to do",
+ self.ARE_DOING: "Reviews you are doing",
+ self.CAN_DO: "Requested reviews you can do",
+ self.MINE: "Reviews you are waiting on",
+ self.OTHER: "Other reviews you are not actively reviewing",
+ self.WIP: "Work in progress",
+ }
if reviewer is None:
# If there is no reviewer, then there will be no TO_DO, ARE_DOING,
# CAN_DO or MINE, and we are not in a person context.
- headings[self.OTHER] = 'Reviews requested or in progress'
+ headings[self.OTHER] = "Reviews requested or in progress"
elif self.user is not None and self.user.inTeam(reviewer):
# The user is either looking at their own person review page, or a
# reviews for a team that they are a member of. The default
@@ -402,18 +426,20 @@ class ActiveReviewsView(BranchMergeProposalListingView):
elif reviewer.is_team:
# Looking at a person team page.
name = reviewer.displayname
- headings[self.CAN_DO] = 'Reviews %s can do' % name
+ headings[self.CAN_DO] = "Reviews %s can do" % name
headings[self.OTHER] = (
- 'Reviews %s is not actively reviewing' % name)
+ "Reviews %s is not actively reviewing" % name
+ )
else:
# A user is looking at someone elses personal review page.
name = reviewer.displayname
- headings[self.TO_DO] = 'Reviews %s has to do' % name
- headings[self.ARE_DOING] = 'Reviews %s is doing' % name
- headings[self.CAN_DO] = 'Reviews %s can do' % name
- headings[self.MINE] = 'Reviews %s is waiting on' % name
+ headings[self.TO_DO] = "Reviews %s has to do" % name
+ headings[self.ARE_DOING] = "Reviews %s is doing" % name
+ headings[self.CAN_DO] = "Reviews %s can do" % name
+ headings[self.MINE] = "Reviews %s is waiting on" % name
headings[self.OTHER] = (
- 'Reviews %s is not actively reviewing' % name)
+ "Reviews %s is not actively reviewing" % name
+ )
return headings
@property
@@ -428,12 +454,17 @@ class BranchActiveReviewsView(ActiveReviewsView):
def getProposals(self):
"""See `ActiveReviewsView`."""
non_final = tuple(
- set(BranchMergeProposalStatus.items) -
- set(BRANCH_MERGE_PROPOSAL_FINAL_STATES))
+ set(BranchMergeProposalStatus.items)
+ - set(BRANCH_MERGE_PROPOSAL_FINAL_STATES)
+ )
candidates = self.context.getMergeProposals(
- status=non_final, eager_load=True, visible_by_user=self.user)
- return [proposal for proposal in candidates
- if check_permission('launchpad.View', proposal)]
+ status=non_final, eager_load=True, visible_by_user=self.user
+ )
+ return [
+ proposal
+ for proposal in candidates
+ if check_permission("launchpad.View", proposal)
+ ]
class PersonActiveReviewsView(ActiveReviewsView):
@@ -447,8 +478,12 @@ class PersonActiveReviewsView(ActiveReviewsView):
return self._getReviewer().getOwnedAndRequestedReviews(
status=(
BranchMergeProposalStatus.CODE_APPROVED,
- BranchMergeProposalStatus.NEEDS_REVIEW),
- visible_by_user=self.user, project=project, eager_load=True)
+ BranchMergeProposalStatus.NEEDS_REVIEW,
+ ),
+ visible_by_user=self.user,
+ project=project,
+ eager_load=True,
+ )
class PersonProductActiveReviewsView(PersonActiveReviewsView):
@@ -456,8 +491,10 @@ class PersonProductActiveReviewsView(PersonActiveReviewsView):
@property
def label(self):
- return '%s for %s' % (
- self.page_title, self.context.product.displayname)
+ return "%s for %s" % (
+ self.page_title,
+ self.context.product.displayname,
+ )
def _getReviewer(self):
return self.context.person
@@ -469,4 +506,6 @@ class PersonProductActiveReviewsView(PersonActiveReviewsView):
def no_proposal_message(self):
"""Shown when there is no table to show."""
return "%s has no active code reviews for %s." % (
- self.context.person.displayname, self.context.product.displayname)
+ self.context.person.displayname,
+ self.context.product.displayname,
+ )
diff --git a/lib/lp/code/browser/branchref.py b/lib/lp/code/browser/branchref.py
index f26fba8..5d440ca 100644
--- a/lib/lp/code/browser/branchref.py
+++ b/lib/lp/code/browser/branchref.py
@@ -3,25 +3,18 @@
"""Browser code used to implement virtual '.bzr' directories."""
-__all__ = [
- 'BranchRef'
- ]
+__all__ = ["BranchRef"]
from zope.interface import implementer
from zope.publisher.interfaces.browser import IBrowserPublisher
from lp.code.interfaces.branchref import IBranchRef
from lp.services.config import config
-from lp.services.webapp import (
- Navigation,
- stepthrough,
- stepto,
- )
+from lp.services.webapp import Navigation, stepthrough, stepto
@implementer(IBranchRef)
class BranchRef:
-
def __init__(self, branch):
self.branch = branch
@@ -35,28 +28,30 @@ class BranchRef:
# Synthesising a branch reference provides the desired behaviour with
# current Bazaar releases, however.
+
class BranchRefNavigation(Navigation):
usedfor = IBranchRef
- @stepto('branch-format')
+ @stepto("branch-format")
def branch_format(self):
- return StaticContentView('Bazaar-NG meta directory, format 1\n')
+ return StaticContentView("Bazaar-NG meta directory, format 1\n")
- @stepthrough('branch')
+ @stepthrough("branch")
def traverse_branch(self, name):
- if name == 'format':
- return StaticContentView('Bazaar-NG Branch Reference Format 1\n')
- elif name == 'location':
- return StaticContentView(config.codehosting.supermirror_root +
- self.context.branch.unique_name)
+ if name == "format":
+ return StaticContentView("Bazaar-NG Branch Reference Format 1\n")
+ elif name == "location":
+ return StaticContentView(
+ config.codehosting.supermirror_root
+ + self.context.branch.unique_name
+ )
else:
return None
@implementer(IBrowserPublisher)
class StaticContentView:
-
def __init__(self, contents):
self.contents = contents
diff --git a/lib/lp/code/browser/branchsubscription.py b/lib/lp/code/browser/branchsubscription.py
index b90a040..2881b03 100644
--- a/lib/lp/code/browser/branchsubscription.py
+++ b/lib/lp/code/browser/branchsubscription.py
@@ -2,32 +2,29 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'BranchPortletSubscribersContent',
- 'BranchSubscriptionAddOtherView',
- 'BranchSubscriptionAddView',
- 'BranchSubscriptionEditOwnView',
- 'BranchSubscriptionEditView',
- ]
+ "BranchPortletSubscribersContent",
+ "BranchSubscriptionAddOtherView",
+ "BranchSubscriptionAddView",
+ "BranchSubscriptionEditOwnView",
+ "BranchSubscriptionEditView",
+]
from zope.component import getUtility
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.interfaces.services import IService
from lp.code.enums import BranchSubscriptionNotificationLevel
from lp.code.interfaces.branchsubscription import IBranchSubscription
from lp.registry.interfaces.person import IPersonSet
-from lp.services.webapp import (
- canonical_url,
- LaunchpadView,
- )
+from lp.services.webapp import LaunchpadView, canonical_url
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.services.webapp.escaping import structured
@@ -42,20 +39,28 @@ class BranchPortletSubscribersContent(LaunchpadView):
# the expense of running several complex SQL queries.
subscriptions = list(self.context.subscriptions)
person_ids = [sub.person_id for sub in subscriptions]
- list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
- person_ids, need_validity=True))
+ list(
+ getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ person_ids, need_validity=True
+ )
+ )
if self.user is not None:
subscribers = [
- subscription.person for subscription in subscriptions]
+ subscription.person for subscription in subscriptions
+ ]
precache_permission_for_objects(
- self.request, "launchpad.LimitedView", subscribers)
+ self.request, "launchpad.LimitedView", subscribers
+ )
visible_subscriptions = [
- subscription for subscription in subscriptions
- if check_permission('launchpad.LimitedView', subscription.person)]
+ subscription
+ for subscription in subscriptions
+ if check_permission("launchpad.LimitedView", subscription.person)
+ ]
return sorted(
visible_subscriptions,
- key=lambda subscription: subscription.person.displayname)
+ key=lambda subscription: subscription.person.displayname,
+ )
class _BranchSubscriptionView(LaunchpadFormView):
@@ -63,11 +68,12 @@ class _BranchSubscriptionView(LaunchpadFormView):
"""Contains the common functionality of the Add and Edit views."""
schema = IBranchSubscription
- field_names = ['notification_level', 'max_diff_lines', 'review_level']
+ field_names = ["notification_level", "max_diff_lines", "review_level"]
LEVELS_REQUIRING_LINES_SPECIFICATION = (
BranchSubscriptionNotificationLevel.DIFFSONLY,
- BranchSubscriptionNotificationLevel.FULL)
+ BranchSubscriptionNotificationLevel.FULL,
+ )
@property
def user_is_subscribed(self):
@@ -82,17 +88,21 @@ class _BranchSubscriptionView(LaunchpadFormView):
cancel_url = next_url
- def add_notification_message(self, initial, notification_level,
- max_diff_lines, review_level):
+ def add_notification_message(
+ self, initial, notification_level, max_diff_lines, review_level
+ ):
if notification_level in self.LEVELS_REQUIRING_LINES_SPECIFICATION:
- lines_message = '<li>%s</li>' % max_diff_lines.description
+ lines_message = "<li>%s</li>" % max_diff_lines.description
else:
- lines_message = ''
+ lines_message = ""
- format_str = '%%s<ul><li>%%s</li>%s<li>%%s</li></ul>' % lines_message
+ format_str = "%%s<ul><li>%%s</li>%s<li>%%s</li></ul>" % lines_message
message = structured(
- format_str, initial, notification_level.description,
- review_level.description)
+ format_str,
+ initial,
+ notification_level.description,
+ review_level.description,
+ )
self.request.response.addNotification(message)
def optional_max_diff_lines(self, notification_level, max_diff_lines):
@@ -112,31 +122,39 @@ class BranchSubscriptionAddView(_BranchSubscriptionView):
# subscribed before continuing.
if self.context.hasSubscription(self.user):
self.request.response.addNotification(
- 'You are already subscribed to this branch.')
+ "You are already subscribed to this branch."
+ )
else:
- notification_level = data['notification_level']
+ notification_level = data["notification_level"]
max_diff_lines = self.optional_max_diff_lines(
- notification_level, data['max_diff_lines'])
- review_level = data['review_level']
+ notification_level, data["max_diff_lines"]
+ )
+ review_level = data["review_level"]
self.context.subscribe(
- self.user, notification_level, max_diff_lines, review_level,
- self.user)
+ self.user,
+ notification_level,
+ max_diff_lines,
+ review_level,
+ self.user,
+ )
self.add_notification_message(
- 'You have subscribed to this branch with: ',
- notification_level, max_diff_lines, review_level)
+ "You have subscribed to this branch with: ",
+ notification_level,
+ max_diff_lines,
+ review_level,
+ )
class BranchSubscriptionEditOwnView(_BranchSubscriptionView):
-
@property
def label(self):
return "Edit subscription to branch"
@property
def page_title(self):
- return 'Edit subscription to branch %s' % self.context.displayname
+ return "Edit subscription to branch %s" % self.context.displayname
@property
def initial_values(self):
@@ -145,29 +163,33 @@ class BranchSubscriptionEditOwnView(_BranchSubscriptionView):
# This is the case of URL hacking or stale page.
return {}
else:
- return {'notification_level': subscription.notification_level,
- 'max_diff_lines': subscription.max_diff_lines,
- 'review_level': subscription.review_level}
+ return {
+ "notification_level": subscription.notification_level,
+ "max_diff_lines": subscription.max_diff_lines,
+ "review_level": subscription.review_level,
+ }
@action("Change")
def change_details(self, action, data):
# Be proactive in the checking to catch the stale post problem.
if self.context.hasSubscription(self.user):
subscription = self.context.getSubscription(self.user)
- subscription.notification_level = data['notification_level']
+ subscription.notification_level = data["notification_level"]
subscription.max_diff_lines = self.optional_max_diff_lines(
- subscription.notification_level,
- data['max_diff_lines'])
- subscription.review_level = data['review_level']
+ subscription.notification_level, data["max_diff_lines"]
+ )
+ subscription.review_level = data["review_level"]
self.add_notification_message(
- 'Subscription updated to: ',
+ "Subscription updated to: ",
subscription.notification_level,
subscription.max_diff_lines,
- subscription.review_level)
+ subscription.review_level,
+ )
else:
self.request.response.addNotification(
- 'You are not subscribed to this branch.')
+ "You are not subscribed to this branch."
+ )
@action("Unsubscribe")
def unsubscribe(self, action, data):
@@ -175,17 +197,23 @@ class BranchSubscriptionEditOwnView(_BranchSubscriptionView):
if self.context.hasSubscription(self.user):
self.context.unsubscribe(self.user, self.user)
self.request.response.addNotification(
- "You have unsubscribed from this branch.")
+ "You have unsubscribed from this branch."
+ )
else:
self.request.response.addNotification(
- 'You are not subscribed to this branch.')
+ "You are not subscribed to this branch."
+ )
class BranchSubscriptionAddOtherView(_BranchSubscriptionView):
"""View used to subscribe someone other than the current user."""
field_names = [
- 'person', 'notification_level', 'max_diff_lines', 'review_level']
+ "person",
+ "notification_level",
+ "max_diff_lines",
+ "review_level",
+ ]
for_input = True
# Since we are subscribing other people, the current user
@@ -195,37 +223,51 @@ class BranchSubscriptionAddOtherView(_BranchSubscriptionView):
page_title = label = "Subscribe to branch"
def validate(self, data):
- if 'person' in data:
- person = data['person']
+ if "person" in data:
+ person = data["person"]
subscription = self.context.getSubscription(person)
if subscription is None and not self.context.userCanBeSubscribed(
- person):
- self.setFieldError('person', "Open and delegated teams "
- "cannot be subscribed to private branches.")
+ person
+ ):
+ self.setFieldError(
+ "person",
+ "Open and delegated teams "
+ "cannot be subscribed to private branches.",
+ )
@action("Subscribe", name="subscribe_action")
def subscribe_action(self, action, data):
"""Subscribe the specified user to the branch."""
- notification_level = data['notification_level']
+ notification_level = data["notification_level"]
max_diff_lines = self.optional_max_diff_lines(
- notification_level, data['max_diff_lines'])
- review_level = data['review_level']
- person = data['person']
+ notification_level, data["max_diff_lines"]
+ )
+ review_level = data["review_level"]
+ person = data["person"]
subscription = self.context.getSubscription(person)
if subscription is None:
self.context.subscribe(
- person, notification_level, max_diff_lines, review_level,
- self.user)
+ person,
+ notification_level,
+ max_diff_lines,
+ review_level,
+ self.user,
+ )
self.add_notification_message(
- '%s has been subscribed to this branch with: '
- % person.displayname, notification_level, max_diff_lines,
- review_level)
+ "%s has been subscribed to this branch with: "
+ % person.displayname,
+ notification_level,
+ max_diff_lines,
+ review_level,
+ )
else:
self.add_notification_message(
- '%s was already subscribed to this branch with: '
+ "%s was already subscribed to this branch with: "
% person.displayname,
- subscription.notification_level, subscription.max_diff_lines,
- review_level)
+ subscription.notification_level,
+ subscription.max_diff_lines,
+ review_level,
+ )
class BranchSubscriptionEditView(LaunchpadEditFormView):
@@ -235,12 +277,13 @@ class BranchSubscriptionEditView(LaunchpadEditFormView):
through the branch action item to edit the user's own subscription.
This is the only current way to edit a team branch subscription.
"""
+
schema = IBranchSubscription
- field_names = ['notification_level', 'max_diff_lines', 'review_level']
+ field_names = ["notification_level", "max_diff_lines", "review_level"]
@property
def page_title(self):
- return 'Edit subscription to branch %s' % self.branch.displayname
+ return "Edit subscription to branch %s" % self.branch.displayname
@property
def label(self):
@@ -262,16 +305,17 @@ class BranchSubscriptionEditView(LaunchpadEditFormView):
self.branch.unsubscribe(self.person, self.user)
self.request.response.addNotification(
"%s has been unsubscribed from this branch."
- % self.person.displayname)
+ % self.person.displayname
+ )
@property
def next_url(self):
url = canonical_url(self.branch)
# If the subscriber can no longer see the branch, redirect them away.
- service = getUtility(IService, 'sharing')
+ service = getUtility(IService, "sharing")
branches = service.getVisibleArtifacts(
- self.person, branches=[self.branch],
- ignore_permissions=True)["branches"]
+ self.person, branches=[self.branch], ignore_permissions=True
+ )["branches"]
if not branches:
url = canonical_url(self.branch.target)
return url
diff --git a/lib/lp/code/browser/cibuild.py b/lib/lp/code/browser/cibuild.py
index e03d7b7..187f5ef 100644
--- a/lib/lp/code/browser/cibuild.py
+++ b/lib/lp/code/browser/cibuild.py
@@ -7,23 +7,20 @@ __all__ = [
"CIBuildContextMenu",
"CIBuildNavigation",
"CIBuildView",
- ]
+]
from zope.interface import Interface
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.code.interfaces.cibuild import ICIBuild
from lp.services.librarian.browser import FileNavigationMixin
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
Link,
Navigation,
- )
+ canonical_url,
+ enabled_with_permission,
+)
from lp.soyuz.interfaces.binarypackagebuild import IBuildRescoreForm
@@ -43,20 +40,29 @@ class CIBuildContextMenu(ContextMenu):
@enabled_with_permission("launchpad.Edit")
def retry(self):
return Link(
- "+retry", "Retry this build", icon="retry",
- enabled=self.context.can_be_retried)
+ "+retry",
+ "Retry this build",
+ icon="retry",
+ enabled=self.context.can_be_retried,
+ )
@enabled_with_permission("launchpad.Edit")
def cancel(self):
return Link(
- "+cancel", "Cancel build", icon="remove",
- enabled=self.context.can_be_cancelled)
+ "+cancel",
+ "Cancel build",
+ icon="remove",
+ enabled=self.context.can_be_cancelled,
+ )
@enabled_with_permission("launchpad.Admin")
def rescore(self):
return Link(
- "+rescore", "Rescore build", icon="edit",
- enabled=self.context.can_be_rescored)
+ "+rescore",
+ "Rescore build",
+ icon="edit",
+ enabled=self.context.can_be_rescored,
+ )
class CIBuildView(LaunchpadFormView):
@@ -83,6 +89,7 @@ class CIBuildRetryView(LaunchpadFormView):
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
@action("Retry build", name="retry")
@@ -90,7 +97,8 @@ class CIBuildRetryView(LaunchpadFormView):
"""Retry the build."""
if not self.context.can_be_retried:
self.request.response.addErrorNotification(
- "Build cannot be retried")
+ "Build cannot be retried"
+ )
else:
self.context.retry()
self.request.response.addInfoNotification("Build has been queued")
@@ -109,6 +117,7 @@ class CIBuildCancelView(LaunchpadFormView):
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
@action("Cancel build", name="cancel")
@@ -128,12 +137,14 @@ class CIBuildRescoreView(LaunchpadFormView):
if self.context.can_be_rescored:
return super().__call__()
self.request.response.addWarningNotification(
- "Cannot rescore this build because it is not queued.")
+ "Cannot rescore this build because it is not queued."
+ )
self.request.response.redirect(canonical_url(self.context))
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
@action("Rescore build", name="rescore")
diff --git a/lib/lp/code/browser/codeimport.py b/lib/lp/code/browser/codeimport.py
index 33a2cd4..7856269 100644
--- a/lib/lp/code/browser/codeimport.py
+++ b/lib/lp/code/browser/codeimport.py
@@ -4,30 +4,24 @@
"""Browser views for CodeImports."""
__all__ = [
- 'CodeImportEditView',
- 'CodeImportMachineView',
- 'CodeImportNameValidationMixin',
- 'CodeImportNewView',
- 'CodeImportSetBreadcrumb',
- 'CodeImportSetNavigation',
- 'CodeImportSetView',
- 'CodeImportTargetMixin',
- 'RequestImportView',
- 'TryImportAgainView',
- 'validate_import_url',
- ]
+ "CodeImportEditView",
+ "CodeImportMachineView",
+ "CodeImportNameValidationMixin",
+ "CodeImportNewView",
+ "CodeImportSetBreadcrumb",
+ "CodeImportSetNavigation",
+ "CodeImportSetView",
+ "CodeImportTargetMixin",
+ "RequestImportView",
+ "TryImportAgainView",
+ "validate_import_url",
+]
from textwrap import dedent
from urllib.parse import urlparse
-from lazr.restful.interface import (
- copy_field,
- use_template,
- )
-from zope.component import (
- getUtility,
- queryAdapter,
- )
+from lazr.restful.interface import copy_field, use_template
+from zope.component import getUtility, queryAdapter
from zope.formlib import form
from zope.formlib.interfaces import IInputWidget
from zope.formlib.utility import setUpWidget
@@ -38,63 +32,46 @@ from zope.security.interfaces import Unauthorized
from zope.traversing.interfaces import IPathAdapter
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.app.errors import NotFoundError
from lp.app.widgets.itemswidgets import (
LaunchpadDropdownWidget,
LaunchpadRadioWidget,
- )
-from lp.app.widgets.textwidgets import (
- StrippedTextWidget,
- URIWidget,
- )
+)
+from lp.app.widgets.textwidgets import StrippedTextWidget, URIWidget
from lp.code.enums import (
+ NON_CVS_RCS_TYPES,
BranchSubscriptionDiffSize,
BranchSubscriptionNotificationLevel,
CodeImportResultStatus,
CodeImportReviewStatus,
CodeReviewNotificationLevel,
- NON_CVS_RCS_TYPES,
RevisionControlSystems,
TargetRevisionControlSystems,
- )
+)
from lp.code.errors import (
BranchExists,
CodeImportAlreadyRequested,
CodeImportAlreadyRunning,
CodeImportNotInReviewedState,
GitRepositoryExists,
- )
-from lp.code.interfaces.branch import (
- IBranch,
- user_has_special_branch_access,
- )
+)
+from lp.code.interfaces.branch import IBranch, user_has_special_branch_access
from lp.code.interfaces.branchnamespace import (
- get_branch_namespace,
IBranchNamespacePolicy,
- )
-from lp.code.interfaces.codeimport import (
- ICodeImport,
- ICodeImportSet,
- )
+ get_branch_namespace,
+)
+from lp.code.interfaces.codeimport import ICodeImport, ICodeImportSet
from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
from lp.code.interfaces.gitnamespace import (
- get_git_namespace,
IGitNamespacePolicy,
- )
+ get_git_namespace,
+)
from lp.registry.interfaces.product import IProduct
from lp.services.beautifulsoup import BeautifulSoup
from lp.services.fields import URIField
from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
- canonical_url,
- LaunchpadView,
- Navigation,
- stepto,
- )
+from lp.services.webapp import LaunchpadView, Navigation, canonical_url, stepto
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import Breadcrumb
@@ -103,16 +80,18 @@ from lp.services.webapp.escaping import structured
class CodeImportSetNavigation(Navigation):
"""Navigation methods for IBuilder."""
+
usedfor = ICodeImportSet
- @stepto('+machines')
+ @stepto("+machines")
def bugs(self):
return getUtility(ICodeImportMachineSet)
class CodeImportSetBreadcrumb(Breadcrumb):
"""Builds a breadcrumb for an `ICodeImportSet`."""
- text = 'Code Import System'
+
+ text = "Code Import System"
class DropdownWidgetWithAny(LaunchpadDropdownWidget):
@@ -122,7 +101,8 @@ class DropdownWidgetWithAny(LaunchpadDropdownWidget):
associated value is None or not supplied, which is not what we want on
this page.
"""
- _messageNoValue = _('Any')
+
+ _messageNoValue = _("Any")
class CodeImportSetView(LaunchpadView):
@@ -131,26 +111,31 @@ class CodeImportSetView(LaunchpadView):
We present the CodeImportSet as a list of all imports.
"""
- page_title = 'Code Imports'
+ page_title = "Code Imports"
def initialize(self):
"""See `LaunchpadView.initialize`."""
review_status_field = copy_field(
- ICodeImport['review_status'], required=False, default=None)
+ ICodeImport["review_status"], required=False, default=None
+ )
self.review_status_widget = CustomWidgetFactory(DropdownWidgetWithAny)
- setUpWidget(self, 'review_status', review_status_field, IInputWidget)
+ setUpWidget(self, "review_status", review_status_field, IInputWidget)
rcs_type_field = copy_field(
- ICodeImport['rcs_type'], required=False, default=None)
+ ICodeImport["rcs_type"], required=False, default=None
+ )
self.rcs_type_widget = CustomWidgetFactory(DropdownWidgetWithAny)
- setUpWidget(self, 'rcs_type', rcs_type_field, IInputWidget)
+ setUpWidget(self, "rcs_type", rcs_type_field, IInputWidget)
target_rcs_type_field = copy_field(
- ICodeImport['target_rcs_type'], required=False, default=None)
+ ICodeImport["target_rcs_type"], required=False, default=None
+ )
self.target_rcs_type_widget = CustomWidgetFactory(
- DropdownWidgetWithAny)
+ DropdownWidgetWithAny
+ )
setUpWidget(
- self, 'target_rcs_type', target_rcs_type_field, IInputWidget)
+ self, "target_rcs_type", target_rcs_type_field, IInputWidget
+ )
# status should be None if either (a) there were no query arguments
# supplied, i.e. the user browsed directly to this page (this is when
@@ -169,8 +154,10 @@ class CodeImportSetView(LaunchpadView):
target_rcs_type = self.target_rcs_type_widget.getInputValue()
imports = self.context.search(
- review_status=review_status, rcs_type=rcs_type,
- target_rcs_type=target_rcs_type)
+ review_status=review_status,
+ rcs_type=rcs_type,
+ target_rcs_type=target_rcs_type,
+ )
self.batchnav = BatchNavigator(imports, self.request)
@@ -181,9 +168,11 @@ class CodeImportBaseView(LaunchpadFormView):
schema = ICodeImport
custom_widget_cvs_root = CustomWidgetFactory(
- StrippedTextWidget, displayWidth=50)
+ StrippedTextWidget, displayWidth=50
+ )
custom_widget_cvs_module = CustomWidgetFactory(
- StrippedTextWidget, displayWidth=20)
+ StrippedTextWidget, displayWidth=20
+ )
custom_widget_url = CustomWidgetFactory(URIWidget, displayWidth=50)
@cachedproperty
@@ -211,33 +200,43 @@ class CodeImportBaseView(LaunchpadFormView):
"""If the user has specified cvs, then we need to make
sure that there isn't already an import with those values."""
if cvs_root is None:
- self.setSecondaryFieldError(
- 'cvs_root', 'Enter a CVS root.')
+ self.setSecondaryFieldError("cvs_root", "Enter a CVS root.")
if cvs_module is None:
- self.setSecondaryFieldError(
- 'cvs_module', 'Enter a CVS module.')
+ self.setSecondaryFieldError("cvs_module", "Enter a CVS module.")
if cvs_root and cvs_module:
code_import = getUtility(ICodeImportSet).getByCVSDetails(
- cvs_root, cvs_module)
- if (code_import is not None and
- code_import != existing_import):
- self.addError(structured("""
+ cvs_root, cvs_module
+ )
+ if code_import is not None and code_import != existing_import:
+ self.addError(
+ structured(
+ """
Those CVS details are already specified for
the imported branch <a href="%s">%s</a>.""",
- canonical_url(code_import.target),
- code_import.target.unique_name))
-
- def _validateURL(self, url, rcs_type, target_rcs_type,
- existing_import=None, field_name='url'):
+ canonical_url(code_import.target),
+ code_import.target.unique_name,
+ )
+ )
+
+ def _validateURL(
+ self,
+ url,
+ rcs_type,
+ target_rcs_type,
+ existing_import=None,
+ field_name="url",
+ ):
"""If the user has specified a url, we need to make sure that there
isn't already an import with that url."""
if url is None:
self.setSecondaryFieldError(
- field_name, 'Enter the URL of a foreign VCS branch.')
+ field_name, "Enter the URL of a foreign VCS branch."
+ )
else:
reason = validate_import_url(
- url, rcs_type, target_rcs_type, existing_import)
+ url, rcs_type, target_rcs_type, existing_import
+ )
if reason:
self.setFieldError(field_name, reason)
@@ -248,81 +247,98 @@ class CodeImportNameValidationMixin:
def _setBranchExists(self, existing_branch, field_name):
self.setFieldError(
field_name,
- structured(dedent("""
+ structured(
+ dedent(
+ """
There is already an existing import for
<a href="%(product_url)s">%(product_name)s</a>
with the name of
- <a href="%(branch_url)s">%(branch_name)s</a>."""),
- product_url=canonical_url(existing_branch.target),
- product_name=existing_branch.target.name,
- branch_url=canonical_url(existing_branch),
- branch_name=existing_branch.name))
+ <a href="%(branch_url)s">%(branch_name)s</a>."""
+ ),
+ product_url=canonical_url(existing_branch.target),
+ product_name=existing_branch.target.name,
+ branch_url=canonical_url(existing_branch),
+ branch_name=existing_branch.name,
+ ),
+ )
class NewCodeImportForm(Interface):
"""The fields presented on the form for editing a code import."""
- use_template(IBranch, ['owner'])
- use_template(ICodeImport, ['rcs_type', 'cvs_root', 'cvs_module'])
+ use_template(IBranch, ["owner"])
+ use_template(ICodeImport, ["rcs_type", "cvs_root", "cvs_module"])
svn_branch_url = URIField(
- title=_("Branch URL"), required=False,
+ title=_("Branch URL"),
+ required=False,
description=_(
"The URL of a Subversion branch, starting with svn:// or "
"http(s)://. You can include a username and password as part "
- "of the url, but this will be displayed on the branch page."),
+ "of the url, but this will be displayed on the branch page."
+ ),
allowed_schemes=["http", "https", "svn"],
allow_userinfo=True,
allow_port=True,
allow_query=False,
allow_fragment=False,
- trailing_slash=False)
+ trailing_slash=False,
+ )
git_repo_url = URIField(
- title=_("Repo URL"), required=False,
+ title=_("Repo URL"),
+ required=False,
description=_(
"The URL of the Git repository. For imports to Bazaar, the "
"HEAD branch will be imported by default, but you can import "
"different branches by appending ',branch=$name' to the URL. "
- "For imports to Git, the entire repository will be imported."),
+ "For imports to Git, the entire repository will be imported."
+ ),
allowed_schemes=["git", "http", "https"],
allow_userinfo=True,
allow_port=True,
allow_query=False,
allow_fragment=False,
- trailing_slash=False)
+ trailing_slash=False,
+ )
git_target_rcs_type = Choice(
title=_("Target version control system"),
description=_(
"The version control system that the source code should be "
- "imported into on the Launchpad side."),
- required=False, vocabulary=TargetRevisionControlSystems)
+ "imported into on the Launchpad side."
+ ),
+ required=False,
+ vocabulary=TargetRevisionControlSystems,
+ )
bzr_branch_url = URIField(
- title=_("Branch URL"), required=False,
+ title=_("Branch URL"),
+ required=False,
description=_("The URL of the Bazaar branch."),
allowed_schemes=["http", "https", "bzr", "ftp"],
allow_userinfo=True,
allow_port=True,
- allow_query=False, # Query makes no sense in Bazaar
+ allow_query=False, # Query makes no sense in Bazaar
allow_fragment=False, # Fragment makes no sense in Bazaar
- trailing_slash=False)
+ trailing_slash=False,
+ )
branch_name = copy_field(
- IBranch['name'],
- __name__='branch_name',
- title=_('Name'),
+ IBranch["name"],
+ __name__="branch_name",
+ title=_("Name"),
description=_(
"This will be used in the branch or repository URL to identify "
- "the import. Examples: main, trunk."),
- )
+ "the import. Examples: main, trunk."
+ ),
+ )
product = Choice(
- title=_('Project'),
+ title=_("Project"),
description=_("The Project to associate the code import with."),
vocabulary="Product",
- )
+ )
class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
@@ -337,11 +353,11 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
@property
def initial_values(self):
return {
- 'owner': self.user,
- 'rcs_type': RevisionControlSystems.BZR,
- 'branch_name': 'trunk',
- 'git_target_rcs_type': TargetRevisionControlSystems.BZR,
- }
+ "owner": self.user,
+ "rcs_type": RevisionControlSystems.BZR,
+ "branch_name": "trunk",
+ "git_target_rcs_type": TargetRevisionControlSystems.BZR,
+ }
@property
def context_is_product(self):
@@ -349,35 +365,40 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
@property
def label(self):
- label = 'Request a code import'
+ label = "Request a code import"
if self.context_is_product:
- label += ' for %s' % self.context.displayname
+ label += " for %s" % self.context.displayname
return label
@property
def cancel_url(self):
"""Cancel should take the user back to the root site."""
- return '/'
+ return "/"
def setUpFields(self):
CodeImportBaseView.setUpFields(self)
if self.context_is_product:
- self.form_fields = self.form_fields.omit('product')
+ self.form_fields = self.form_fields.omit("product")
# If the user can administer branches, then they should be able to
# assign the ownership of the branch to any valid person or team.
if user_has_special_branch_access(self.user):
- owner_field = self.schema['owner']
+ owner_field = self.schema["owner"]
any_owner_choice = Choice(
- __name__='owner', title=owner_field.title,
+ __name__="owner",
+ title=owner_field.title,
description=_(
"As an administrator you are able to reassign this "
- "branch to any person or team."),
- required=True, vocabulary='ValidPersonOrTeam')
+ "branch to any person or team."
+ ),
+ required=True,
+ vocabulary="ValidPersonOrTeam",
+ )
any_owner_field = form.Fields(
- any_owner_choice, render_context=self.render_context)
+ any_owner_choice, render_context=self.render_context
+ )
# Replace the normal owner field with a more permissive vocab.
- self.form_fields = self.form_fields.omit('owner')
+ self.form_fields = self.form_fields.omit("owner")
self.form_fields = any_owner_field + self.form_fields
def setUpWidgets(self):
@@ -385,17 +406,17 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
# Extract the radio buttons from the rcs_type widget, so we can
# display them separately in the form.
- soup = BeautifulSoup(self.widgets['rcs_type']())
- fields = soup.find_all('input')
- [cvs_button, svn_button, git_button, bzr_button,
- empty_marker] = [
- field for field in fields
- if field.get('value') in [
- 'CVS', 'BZR_SVN', 'GIT', 'BZR', '1']]
- bzr_button['onclick'] = 'updateWidgets()'
- cvs_button['onclick'] = 'updateWidgets()'
- svn_button['onclick'] = 'updateWidgets()'
- git_button['onclick'] = 'updateWidgets()'
+ soup = BeautifulSoup(self.widgets["rcs_type"]())
+ fields = soup.find_all("input")
+ [cvs_button, svn_button, git_button, bzr_button, empty_marker] = [
+ field
+ for field in fields
+ if field.get("value") in ["CVS", "BZR_SVN", "GIT", "BZR", "1"]
+ ]
+ bzr_button["onclick"] = "updateWidgets()"
+ cvs_button["onclick"] = "updateWidgets()"
+ svn_button["onclick"] = "updateWidgets()"
+ git_button["onclick"] = "updateWidgets()"
# The following attributes are used only in the page template.
self.rcs_type_cvs = str(cvs_button)
self.rcs_type_svn = str(svn_button)
@@ -404,54 +425,57 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
self.rcs_type_emptymarker = str(empty_marker)
# This widget is only conditionally required in the rcs_type == GIT
# case, but we still don't want a "(nothing selected)" item.
- self.widgets['git_target_rcs_type']._displayItemForMissingValue = False
+ self.widgets["git_target_rcs_type"]._displayItemForMissingValue = False
def _getImportLocation(self, data):
"""Return the import location based on type."""
- rcs_type = data['rcs_type']
+ rcs_type = data["rcs_type"]
if rcs_type == RevisionControlSystems.CVS:
- return data.get('cvs_root'), data.get('cvs_module'), None
+ return data.get("cvs_root"), data.get("cvs_module"), None
elif rcs_type == RevisionControlSystems.BZR_SVN:
- return None, None, data.get('svn_branch_url')
+ return None, None, data.get("svn_branch_url")
elif rcs_type == RevisionControlSystems.GIT:
- return None, None, data.get('git_repo_url')
+ return None, None, data.get("git_repo_url")
elif rcs_type == RevisionControlSystems.BZR:
- return None, None, data.get('bzr_branch_url')
+ return None, None, data.get("bzr_branch_url")
else:
raise AssertionError(
- 'Unexpected revision control type %r.' % rcs_type)
+ "Unexpected revision control type %r." % rcs_type
+ )
def _create_import(self, data, status):
"""Create the code import."""
product = self.getProduct(data)
cvs_root, cvs_module, url = self._getImportLocation(data)
- if data['rcs_type'] == RevisionControlSystems.GIT:
+ if data["rcs_type"] == RevisionControlSystems.GIT:
target_rcs_type = data.get(
- 'git_target_rcs_type', TargetRevisionControlSystems.BZR)
+ "git_target_rcs_type", TargetRevisionControlSystems.BZR
+ )
else:
target_rcs_type = TargetRevisionControlSystems.BZR
return getUtility(ICodeImportSet).new(
registrant=self.user,
- owner=data['owner'],
+ owner=data["owner"],
context=product,
- branch_name=data['branch_name'],
- rcs_type=data['rcs_type'],
+ branch_name=data["branch_name"],
+ rcs_type=data["rcs_type"],
target_rcs_type=target_rcs_type,
url=url,
cvs_root=cvs_root,
cvs_module=cvs_module,
- review_status=status)
+ review_status=status,
+ )
- @action(_('Request Import'), name='request_import')
+ @action(_("Request Import"), name="request_import")
def request_import_action(self, action, data):
"""Create the code_import, and subscribe the user to the branch."""
try:
code_import = self._create_import(data, None)
except BranchExists as e:
- self._setBranchExists(e.existing_branch, 'branch_name')
+ self._setBranchExists(e.existing_branch, "branch_name")
return
except GitRepositoryExists as e:
- self._setBranchExists(e.existing_repository, 'branch_name')
+ self._setBranchExists(e.existing_repository, "branch_name")
return
# Subscribe the user.
@@ -460,32 +484,37 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
BranchSubscriptionNotificationLevel.FULL,
BranchSubscriptionDiffSize.NODIFF,
CodeReviewNotificationLevel.NOEMAIL,
- self.user)
+ self.user,
+ )
self.next_url = canonical_url(code_import.target)
- self.request.response.addNotification("""
- New code import created. The code import will start shortly.""")
+ self.request.response.addNotification(
+ """
+ New code import created. The code import will start shortly."""
+ )
def getProduct(self, data):
"""If the context is a product, use that, otherwise get from data."""
if self.context_is_product:
return self.context
else:
- return data.get('product')
+ return data.get("product")
def validate_widgets(self, data, names=None):
"""See `LaunchpadFormView`."""
- self.widgets['git_target_rcs_type'].context.required = (
- data.get('rcs_type') == RevisionControlSystems.GIT)
+ self.widgets["git_target_rcs_type"].context.required = (
+ data.get("rcs_type") == RevisionControlSystems.GIT
+ )
super().validate_widgets(data, names=names)
def validate(self, data):
"""See `LaunchpadFormView`."""
- rcs_type = data['rcs_type']
+ rcs_type = data["rcs_type"]
if rcs_type == RevisionControlSystems.GIT:
target_rcs_type = data.get(
- 'git_target_rcs_type', TargetRevisionControlSystems.BZR)
+ "git_target_rcs_type", TargetRevisionControlSystems.BZR
+ )
else:
target_rcs_type = TargetRevisionControlSystems.BZR
@@ -493,7 +522,7 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
# for the specified namespace.
product = self.getProduct(data)
# 'owner' in data may be None if it failed validation.
- owner = data.get('owner')
+ owner = data.get("owner")
if product is not None and owner is not None:
if target_rcs_type == TargetRevisionControlSystems.BZR:
namespace = get_branch_namespace(owner, product)
@@ -505,38 +534,49 @@ class CodeImportNewView(CodeImportBaseView, CodeImportNameValidationMixin):
can_create = policy.canCreateRepositories(self.user)
if not can_create:
self.setFieldError(
- 'product',
+ "product",
"You are not allowed to register imports for %s."
- % product.displayname)
+ % product.displayname,
+ )
# Make sure fields for unselected revision control systems
# are blanked out:
if rcs_type == RevisionControlSystems.CVS:
- self._validateCVS(data.get('cvs_root'), data.get('cvs_module'))
+ self._validateCVS(data.get("cvs_root"), data.get("cvs_module"))
elif rcs_type == RevisionControlSystems.BZR_SVN:
self._validateURL(
- data.get('svn_branch_url'), rcs_type, target_rcs_type,
- field_name='svn_branch_url')
+ data.get("svn_branch_url"),
+ rcs_type,
+ target_rcs_type,
+ field_name="svn_branch_url",
+ )
elif rcs_type == RevisionControlSystems.GIT:
self._validateURL(
- data.get('git_repo_url'), rcs_type, target_rcs_type,
- field_name='git_repo_url')
+ data.get("git_repo_url"),
+ rcs_type,
+ target_rcs_type,
+ field_name="git_repo_url",
+ )
elif rcs_type == RevisionControlSystems.BZR:
self._validateURL(
- data.get('bzr_branch_url'), rcs_type, target_rcs_type,
- field_name='bzr_branch_url')
+ data.get("bzr_branch_url"),
+ rcs_type,
+ target_rcs_type,
+ field_name="bzr_branch_url",
+ )
else:
raise AssertionError(
- 'Unexpected revision control type %r.' % rcs_type)
+ "Unexpected revision control type %r." % rcs_type
+ )
class EditCodeImportForm(Interface):
"""The fields presented on the form for editing a code import."""
- url = copy_field(ICodeImport['url'], readonly=False)
- cvs_root = copy_field(ICodeImport['cvs_root'], readonly=False)
- cvs_module = copy_field(ICodeImport['cvs_module'], readonly=False)
- whiteboard = copy_field(IBranch['whiteboard'])
+ url = copy_field(ICodeImport["url"], readonly=False)
+ cvs_root = copy_field(ICodeImport["cvs_root"], readonly=False)
+ cvs_module = copy_field(ICodeImport["cvs_module"], readonly=False)
+ whiteboard = copy_field(IBranch["whiteboard"])
def _makeEditAction(label, status, text):
@@ -550,8 +590,10 @@ def _makeEditAction(label, status, text):
notifcation, if a change was made.
"""
if status is not None:
+
def condition(self, ignored):
return self._showButtonForStatus(status)
+
else:
condition = None
@@ -561,22 +603,24 @@ def _makeEditAction(label, status, text):
# Moderators can change everything in code import, including its
# status.
if status is not None:
- data['review_status'] = status
+ data["review_status"] = status
event = self.code_import.updateFromData(data, self.user)
if event is not None:
self.request.response.addNotification(
- 'The code import has been ' + text + '.')
+ "The code import has been " + text + "."
+ )
elif self._is_edit_user and "url" in data:
# Edit users can only change URL
event = self.code_import.updateURL(data["url"], self.user)
if event is not None:
self.request.response.addNotification(
- 'The code import URL has been updated.')
+ "The code import URL has been updated."
+ )
else:
- self.request.response.addNotification('No changes made.')
- name = label.lower().replace(' ', '_')
- return form.Action(
- label, name=name, success=success, condition=condition)
+ self.request.response.addNotification("No changes made.")
+
+ name = label.lower().replace(" ", "_")
+ return form.Action(label, name=name, success=success, condition=condition)
class CodeImportEditView(CodeImportBaseView):
@@ -595,14 +639,16 @@ class CodeImportEditView(CodeImportBaseView):
# Need this to render the context to prepopulate the form fields.
# Added here as the base class isn't LaunchpadEditFormView.
render_context = True
- page_title = 'Edit import details'
+ page_title = "Edit import details"
label = page_title
@property
def initial_values(self):
- if (self.code_import.target_rcs_type ==
- TargetRevisionControlSystems.BZR):
- return {'whiteboard': self.context.whiteboard}
+ if (
+ self.code_import.target_rcs_type
+ == TargetRevisionControlSystems.BZR
+ ):
+ return {"whiteboard": self.context.whiteboard}
else:
return {}
@@ -628,49 +674,58 @@ class CodeImportEditView(CodeImportBaseView):
# If the import is a Subversion import, then omit the CVS
# fields, and vice versa.
if self.code_import.rcs_type == RevisionControlSystems.CVS:
- self.form_fields = self.form_fields.omit('url')
+ self.form_fields = self.form_fields.omit("url")
elif self.code_import.rcs_type in NON_CVS_RCS_TYPES:
- self.form_fields = self.form_fields.omit('cvs_root', 'cvs_module')
+ self.form_fields = self.form_fields.omit("cvs_root", "cvs_module")
else:
- raise AssertionError('Unknown rcs_type for code import.')
+ raise AssertionError("Unknown rcs_type for code import.")
- if (self.code_import.target_rcs_type !=
- TargetRevisionControlSystems.BZR):
- self.form_fields = self.form_fields.omit('whiteboard')
+ if (
+ self.code_import.target_rcs_type
+ != TargetRevisionControlSystems.BZR
+ ):
+ self.form_fields = self.form_fields.omit("whiteboard")
def _showButtonForStatus(self, status):
"""If the status is different, and the user is super, show button."""
- return (self._is_moderator_user and
- self.code_import.review_status != status)
+ return (
+ self._is_moderator_user
+ and self.code_import.review_status != status
+ )
actions = form.Actions(
- _makeEditAction(_('Update'), None, 'updated'),
+ _makeEditAction(_("Update"), None, "updated"),
_makeEditAction(
- _('Approve'), CodeImportReviewStatus.REVIEWED,
- 'approved'),
+ _("Approve"), CodeImportReviewStatus.REVIEWED, "approved"
+ ),
_makeEditAction(
- _('Mark Invalid'), CodeImportReviewStatus.INVALID,
- 'set as invalid'),
+ _("Mark Invalid"), CodeImportReviewStatus.INVALID, "set as invalid"
+ ),
_makeEditAction(
- _('Suspend'), CodeImportReviewStatus.SUSPENDED,
- 'suspended'),
+ _("Suspend"), CodeImportReviewStatus.SUSPENDED, "suspended"
+ ),
_makeEditAction(
- _('Mark Failing'), CodeImportReviewStatus.FAILING,
- 'marked as failing'),
- )
+ _("Mark Failing"),
+ CodeImportReviewStatus.FAILING,
+ "marked as failing",
+ ),
+ )
def validate(self, data):
"""See `LaunchpadFormView`."""
if self.code_import.rcs_type == RevisionControlSystems.CVS:
self._validateCVS(
- data.get('cvs_root'), data.get('cvs_module'),
- self.code_import)
+ data.get("cvs_root"), data.get("cvs_module"), self.code_import
+ )
elif self.code_import.rcs_type in NON_CVS_RCS_TYPES:
self._validateURL(
- data.get('url'), self.code_import.rcs_type,
- self.code_import.target_rcs_type, self.code_import)
+ data.get("url"),
+ self.code_import.rcs_type,
+ self.code_import.target_rcs_type,
+ self.code_import,
+ )
else:
- raise AssertionError('Unknown rcs_type for code import.')
+ raise AssertionError("Unknown rcs_type for code import.")
class CodeImportMachineView(LaunchpadView):
@@ -686,11 +741,13 @@ class CodeImportMachineView(LaunchpadView):
def validate_import_url(url, rcs_type, target_rcs_type, existing_import=None):
"""Validate the given import URL."""
- if (rcs_type.name == target_rcs_type.name and
- urlparse(url).netloc.endswith('launchpad.net')):
+ if rcs_type.name == target_rcs_type.name and urlparse(url).netloc.endswith(
+ "launchpad.net"
+ ):
return (
"You cannot create same-VCS imports for branches or repositories "
- "that are hosted by Launchpad.")
+ "that are hosted by Launchpad."
+ )
code_import = getUtility(ICodeImportSet).getByURL(url, target_rcs_type)
if code_import is not None:
if existing_import and code_import == existing_import:
@@ -701,8 +758,11 @@ def validate_import_url(url, rcs_type, target_rcs_type, existing_import=None):
target_type = "repository"
return structured(
"This foreign branch URL is already specified for the imported "
- "%s <a href='%s'>%s</a>.", target_type,
- canonical_url(code_import.target), code_import.target.unique_name)
+ "%s <a href='%s'>%s</a>.",
+ target_type,
+ canonical_url(code_import.target),
+ code_import.target.unique_name,
+ )
class CodeImportTargetMixin:
@@ -751,25 +811,32 @@ class RequestImportView(LaunchpadFormView):
def next_url(self):
return canonical_url(self.context)
- @action('Import Now', name='request')
+ @action("Import Now", name="request")
def request_import_action(self, action, data):
try:
self.context.code_import.requestImport(
- self.user, error_if_already_requested=True)
+ self.user, error_if_already_requested=True
+ )
self.request.response.addNotification(
- "Import will run as soon as possible.")
+ "Import will run as soon as possible."
+ )
except CodeImportNotInReviewedState:
self.request.response.addNotification(
- "This import is no longer being updated automatically.")
+ "This import is no longer being updated automatically."
+ )
except CodeImportAlreadyRunning:
self.request.response.addNotification(
- "The import is already running.")
+ "The import is already running."
+ )
except CodeImportAlreadyRequested as e:
user = e.requesting_user
- adapter = queryAdapter(user, IPathAdapter, 'fmt')
+ adapter = queryAdapter(user, IPathAdapter, "fmt")
self.request.response.addNotification(
- structured("The import has already been requested by %s." %
- adapter.link(None)))
+ structured(
+ "The import has already been requested by %s."
+ % adapter.link(None)
+ )
+ )
@property
def prefix(self):
@@ -794,17 +861,21 @@ class TryImportAgainView(LaunchpadFormView):
def next_url(self):
return canonical_url(self.context)
- @action('Try Again', name='tryagain')
+ @action("Try Again", name="tryagain")
def request_try_again(self, action, data):
- if (self.context.code_import.review_status !=
- CodeImportReviewStatus.FAILING):
+ if (
+ self.context.code_import.review_status
+ != CodeImportReviewStatus.FAILING
+ ):
self.request.response.addNotification(
"The import is now %s."
- % self.context.code_import.review_status.name)
+ % self.context.code_import.review_status.name
+ )
else:
self.context.code_import.tryFailingImportAgain(self.user)
self.request.response.addNotification(
- "Import will be tried again as soon as possible.")
+ "Import will be tried again as soon as possible."
+ )
@property
def prefix(self):
diff --git a/lib/lp/code/browser/codeimportmachine.py b/lib/lp/code/browser/codeimportmachine.py
index 428e422..5b84c73 100644
--- a/lib/lp/code/browser/codeimportmachine.py
+++ b/lib/lp/code/browser/codeimportmachine.py
@@ -4,12 +4,12 @@
"""Browser views for CodeImportMachines."""
__all__ = [
- 'CodeImportMachineBreadcrumb',
- 'CodeImportMachineSetBreadcrumb',
- 'CodeImportMachineSetNavigation',
- 'CodeImportMachineSetView',
- 'CodeImportMachineView',
- ]
+ "CodeImportMachineBreadcrumb",
+ "CodeImportMachineSetBreadcrumb",
+ "CodeImportMachineSetNavigation",
+ "CodeImportMachineSetView",
+ "CodeImportMachineView",
+]
from lazr.delegates import delegate_to
@@ -18,24 +18,17 @@ from zope.interface import Interface
from zope.schema import TextLine
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.code.enums import (
CodeImportJobState,
CodeImportMachineOfflineReason,
CodeImportMachineState,
- )
+)
from lp.code.interfaces.codeimportevent import ICodeImportEvent
from lp.code.interfaces.codeimportjob import ICodeImportJobSet
from lp.code.interfaces.codeimportmachine import ICodeImportMachineSet
from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
- canonical_url,
- LaunchpadView,
- Navigation,
- )
+from lp.services.webapp import LaunchpadView, Navigation, canonical_url
from lp.services.webapp.breadcrumb import Breadcrumb
@@ -49,6 +42,7 @@ class CodeImportMachineBreadcrumb(Breadcrumb):
class CodeImportMachineSetNavigation(Navigation):
"""Navigation methods for ICodeImportMachineSet."""
+
usedfor = ICodeImportMachineSet
def traverse(self, hostname):
@@ -58,7 +52,8 @@ class CodeImportMachineSetNavigation(Navigation):
class CodeImportMachineSetBreadcrumb(Breadcrumb):
"""Builds a breadcrumb for an `ICodeImportMachineSet`."""
- text = 'Machines'
+
+ text = "Machines"
class CodeImportMachineSetView(LaunchpadView):
@@ -75,31 +70,42 @@ class CodeImportMachineSetView(LaunchpadView):
@property
def pending_imports(self):
"""Get the number of imports that are pending."""
- return getUtility(ICodeImportJobSet).getJobsInState(
- CodeImportJobState.PENDING).count()
+ return (
+ getUtility(ICodeImportJobSet)
+ .getJobsInState(CodeImportJobState.PENDING)
+ .count()
+ )
@property
def scheduled_imports(self):
"""Get the number of imports that are scheduled."""
- return getUtility(ICodeImportJobSet).getJobsInState(
- CodeImportJobState.SCHEDULED).count()
+ return (
+ getUtility(ICodeImportJobSet)
+ .getJobsInState(CodeImportJobState.SCHEDULED)
+ .count()
+ )
@property
def running_imports(self):
"""Get the number of imports that are running."""
- return getUtility(ICodeImportJobSet).getJobsInState(
- CodeImportJobState.RUNNING).count()
+ return (
+ getUtility(ICodeImportJobSet)
+ .getJobsInState(CodeImportJobState.RUNNING)
+ .count()
+ )
class UpdateMachineStateForm(Interface):
"""An interface to allow the user to enter a reason for quiescing."""
reason = TextLine(
- title=_('Reason'), required=False, description=_(
- "Why the machine state is changing."))
+ title=_("Reason"),
+ required=False,
+ description=_("Why the machine state is changing."),
+ )
-@delegate_to(ICodeImportEvent, context='event')
+@delegate_to(ICodeImportEvent, context="event")
class DecoratedEvent:
"""A CodeImportEvent with cached items."""
@@ -118,7 +124,7 @@ class CodeImportMachineView(LaunchpadFormView):
schema = UpdateMachineStateForm
# The default reason is always the empty string.
- initial_values = {'reason': ''}
+ initial_values = {"reason": ""}
@property
def page_title(self):
@@ -144,27 +150,37 @@ class CodeImportMachineView(LaunchpadFormView):
The next_state is stored in the data dict of the action.
"""
- next_state = action.data['next_state']
+ next_state = action.data["next_state"]
if next_state == CodeImportMachineState.QUIESCING:
return self.context.state == CodeImportMachineState.ONLINE
else:
return self.context.state != next_state
- @action('Set Online', name='set_online',
- data={'next_state': CodeImportMachineState.ONLINE},
- condition=_canChangeToState)
+ @action(
+ "Set Online",
+ name="set_online",
+ data={"next_state": CodeImportMachineState.ONLINE},
+ condition=_canChangeToState,
+ )
def set_online_action(self, action, data):
- self.context.setOnline(self.user, data['reason'])
+ self.context.setOnline(self.user, data["reason"])
- @action('Set Offline', name='set_offline',
- data={'next_state': CodeImportMachineState.OFFLINE},
- condition=_canChangeToState)
+ @action(
+ "Set Offline",
+ name="set_offline",
+ data={"next_state": CodeImportMachineState.OFFLINE},
+ condition=_canChangeToState,
+ )
def set_offline_action(self, action, data):
self.context.setOffline(
- CodeImportMachineOfflineReason.STOPPED, self.user, data['reason'])
-
- @action('Set Quiescing', name='set_quiescing',
- data={'next_state': CodeImportMachineState.QUIESCING},
- condition=_canChangeToState)
+ CodeImportMachineOfflineReason.STOPPED, self.user, data["reason"]
+ )
+
+ @action(
+ "Set Quiescing",
+ name="set_quiescing",
+ data={"next_state": CodeImportMachineState.QUIESCING},
+ condition=_canChangeToState,
+ )
def set_quiescing_action(self, action, data):
- self.context.setQuiescing(self.user, data['reason'])
+ self.context.setQuiescing(self.user, data["reason"])
diff --git a/lib/lp/code/browser/codereviewcomment.py b/lib/lp/code/browser/codereviewcomment.py
index a8a480c..01cdc12 100644
--- a/lib/lp/code/browser/codereviewcomment.py
+++ b/lib/lp/code/browser/codereviewcomment.py
@@ -2,70 +2,52 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'CodeReviewCommentAddView',
- 'CodeReviewCommentContextMenu',
- 'CodeReviewCommentView',
- 'CodeReviewDisplayComment',
- ]
+ "CodeReviewCommentAddView",
+ "CodeReviewCommentContextMenu",
+ "CodeReviewCommentView",
+ "CodeReviewDisplayComment",
+]
from lazr.delegates import delegate_to
from lazr.restful.interface import copy_field
from zope.component import getUtility
from zope.formlib.widget import CustomWidgetFactory
-from zope.formlib.widgets import (
- DropdownWidget,
- TextAreaWidget,
- TextWidget,
- )
-from zope.interface import (
- implementer,
- Interface,
- )
-from zope.schema import (
- Object,
- Text,
- )
+from zope.formlib.widgets import DropdownWidget, TextAreaWidget, TextWidget
+from zope.interface import Interface, implementer
+from zope.schema import Object, Text
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.code.interfaces.codereviewcomment import ICodeReviewComment
from lp.code.interfaces.codereviewinlinecomment import (
ICodeReviewInlineCommentSet,
- )
+)
from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
from lp.services.comments.browser.comment import download_body
from lp.services.comments.browser.messagecomment import MessageComment
from lp.services.comments.interfaces.conversation import IComment
from lp.services.config import config
from lp.services.librarian.interfaces import ILibraryFileAlias
-from lp.services.messages.interfaces.message import (
- IMessage,
- IMessageEdit,
- )
-from lp.services.propertycache import (
- cachedproperty,
- get_property_cache,
- )
+from lp.services.messages.interfaces.message import IMessage, IMessageEdit
+from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.webapp import (
- canonical_url,
ContextMenu,
LaunchpadView,
Link,
Navigation,
+ canonical_url,
stepthrough,
- )
+)
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.interfaces import ILaunchBag
class CodeReviewCommentNavigation(Navigation):
"""Navigation for the `ICodeReviewComment`."""
+
usedfor = ICodeReviewComment
- @stepthrough('revisions')
+ @stepthrough("revisions")
def traverse_revisions(self, revision):
try:
revision = int(revision)
@@ -76,12 +58,13 @@ class CodeReviewCommentNavigation(Navigation):
class ICodeReviewDisplayComment(IComment, ICodeReviewComment):
"""Marker interface for displaying code review comments."""
- message = Object(schema=IMessage, title=_('The message.'))
+
+ message = Object(schema=IMessage, title=_("The message."))
@implementer(ICodeReviewDisplayComment)
-@delegate_to(ICodeReviewComment, context='comment')
-@delegate_to(IMessageEdit, context='message')
+@delegate_to(ICodeReviewComment, context="comment")
+@delegate_to(IMessageEdit, context="message")
class CodeReviewDisplayComment(MessageComment):
"""A code review comment or activity or both.
@@ -111,13 +94,14 @@ class CodeReviewDisplayComment(MessageComment):
def extra_css_class(self):
css_classes = super().extra_css_class.split()
if self.from_superseded:
- css_classes.append('from-superseded')
- return ' '.join(css_classes)
+ css_classes.append("from-superseded")
+ return " ".join(css_classes)
@cachedproperty
def previewdiff_id(self):
inline_comment = getUtility(
- ICodeReviewInlineCommentSet).getByReviewComment(self.comment)
+ ICodeReviewInlineCommentSet
+ ).getByReviewComment(self.comment)
if inline_comment is not None:
return inline_comment.previewdiff_id
return None
@@ -143,7 +127,7 @@ class CodeReviewDisplayComment(MessageComment):
@property
def download_url(self):
- return canonical_url(self.comment, view_name='+download')
+ return canonical_url(self.comment, view_name="+download")
@cachedproperty
def show_spam_controls(self):
@@ -160,15 +144,15 @@ class CodeReviewCommentContextMenu(ContextMenu):
"""Context menu for branches."""
usedfor = ICodeReviewComment
- links = ['reply']
+ links = ["reply"]
def reply(self):
enabled = self.context.branch_merge_proposal.isMergable()
- return Link('+reply', 'Reply', icon='add', enabled=enabled)
+ return Link("+reply", "Reply", icon="add", enabled=enabled)
@implementer(ILibraryFileAlias)
-@delegate_to(ILibraryFileAlias, context='alias')
+@delegate_to(ILibraryFileAlias, context="alias")
class DiffAttachment:
"""An attachment that we are going to display."""
@@ -188,11 +172,11 @@ class DiffAttachment:
def diff_text(self):
"""Get the text and attempt to decode it."""
try:
- diff = self.text.decode('utf-8')
+ diff = self.text.decode("utf-8")
except UnicodeDecodeError:
- diff = self.text.decode('windows-1252', 'replace')
+ diff = self.text.decode("windows-1252", "replace")
# Strip off the trailing carriage returns.
- return diff.rstrip('\n')
+ return diff.rstrip("\n")
class CodeReviewCommentView(LaunchpadView):
@@ -211,11 +195,12 @@ class CodeReviewCommentView(LaunchpadView):
def download(self):
return download_body(
- CodeReviewDisplayComment(self.context), self.request)
+ CodeReviewDisplayComment(self.context), self.request
+ )
@property
def can_edit(self):
- return check_permission('launchpad.Edit', self.context.message)
+ return check_permission("launchpad.Edit", self.context.message)
# Should the comment be shown in full?
full_comment = True
@@ -224,7 +209,6 @@ class CodeReviewCommentView(LaunchpadView):
class CodeReviewCommentIndexView(CodeReviewCommentView):
-
def __call__(self):
"""View redirects to +download if comment is too long to render."""
if self.comment.too_long_to_render:
@@ -235,14 +219,15 @@ class CodeReviewCommentIndexView(CodeReviewCommentView):
class IEditCodeReviewComment(Interface):
"""Interface for use as a schema for CodeReviewComment forms."""
- vote = copy_field(ICodeReviewComment['vote'], required=False)
+ vote = copy_field(ICodeReviewComment["vote"], required=False)
review_type = copy_field(
- ICodeReviewVoteReference['review_type'],
- description='Lowercase keywords describing the type of review you '
- 'are performing.')
+ ICodeReviewVoteReference["review_type"],
+ description="Lowercase keywords describing the type of review you "
+ "are performing.",
+ )
- comment = Text(title=_('Comment'), required=False)
+ comment = Text(title=_("Comment"), required=False)
class CodeReviewCommentAddView(LaunchpadFormView):
@@ -250,17 +235,19 @@ class CodeReviewCommentAddView(LaunchpadFormView):
class MyDropWidget(DropdownWidget):
"Override the default none-selected display name to -Select-."
- _messageNoValue = 'Comment only'
+ _messageNoValue = "Comment only"
schema = IEditCodeReviewComment
custom_widget_review_type = CustomWidgetFactory(
- TextWidget, displayWidth=15)
+ TextWidget, displayWidth=15
+ )
custom_widget_comment = CustomWidgetFactory(
- TextAreaWidget, cssClass='comment-text')
+ TextAreaWidget, cssClass="comment-text"
+ )
custom_widget_vote = MyDropWidget
- page_title = 'Reply to code review comment'
+ page_title = "Reply to code review comment"
@property
def initial_values(self):
@@ -272,8 +259,8 @@ class CodeReviewCommentAddView(LaunchpadFormView):
if self.is_reply:
comment = self.reply_to.as_quoted_email
else:
- comment = ''
- return {'comment': comment}
+ comment = ""
+ return {"comment": comment}
@property
def is_reply(self):
@@ -296,14 +283,19 @@ class CodeReviewCommentAddView(LaunchpadFormView):
else:
return None
- @action('Save Comment', name='add')
+ @action("Save Comment", name="add")
def add_action(self, action, data):
"""Create the comment..."""
- vote = data.get('vote')
- review_type = data.get('review_type')
+ vote = data.get("vote")
+ review_type = data.get("review_type")
self.branch_merge_proposal.createComment(
- self.user, subject=None, content=data['comment'],
- parent=self.reply_to, vote=vote, review_type=review_type)
+ self.user,
+ subject=None,
+ content=data["comment"],
+ parent=self.reply_to,
+ vote=vote,
+ review_type=review_type,
+ )
@property
def next_url(self):
diff --git a/lib/lp/code/browser/codereviewvote.py b/lib/lp/code/browser/codereviewvote.py
index 3d3d9c2..3996bb7 100644
--- a/lib/lp/code/browser/codereviewvote.py
+++ b/lib/lp/code/browser/codereviewvote.py
@@ -6,14 +6,8 @@
from zope.interface import Interface
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
-from lp.code.errors import (
- ReviewNotPending,
- UserHasExistingReview,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
+from lp.code.errors import ReviewNotPending, UserHasExistingReview
from lp.services.fields import PublicPersonChoice
from lp.services.webapp import canonical_url
@@ -21,9 +15,12 @@ from lp.services.webapp import canonical_url
class ReassignSchema(Interface):
"""Schema to use when reassigning the reviewer for a requested review."""
- reviewer = PublicPersonChoice(title=_('Reviewer'), required=True,
- description=_('A person who you want to review this.'),
- vocabulary='ValidBranchReviewer')
+ reviewer = PublicPersonChoice(
+ title=_("Reviewer"),
+ required=True,
+ description=_("A person who you want to review this."),
+ vocabulary="ValidBranchReviewer",
+ )
class CodeReviewVoteReassign(LaunchpadFormView):
@@ -31,7 +28,7 @@ class CodeReviewVoteReassign(LaunchpadFormView):
schema = ReassignSchema
- page_title = label = 'Reassign review request'
+ page_title = label = "Reassign review request"
@property
def next_url(self):
@@ -39,14 +36,14 @@ class CodeReviewVoteReassign(LaunchpadFormView):
cancel_url = next_url
- @action('Reassign', name='reassign')
+ @action("Reassign", name="reassign")
def reassign_action(self, action, data):
"""Use the form data to change the review request reviewer."""
- self.context.reassignReview(data['reviewer'])
+ self.context.reassignReview(data["reviewer"])
def validate(self, data):
"""Make sure that the reassignment can happen."""
- reviewer = data.get('reviewer')
+ reviewer = data.get("reviewer")
if reviewer is not None:
try:
self.context.validateReasignReview(reviewer)
diff --git a/lib/lp/code/browser/decorations.py b/lib/lp/code/browser/decorations.py
index d1110e5..3fadbfe 100644
--- a/lib/lp/code/browser/decorations.py
+++ b/lib/lp/code/browser/decorations.py
@@ -4,23 +4,20 @@
"""Decorated model objects used in the browser code."""
__all__ = [
- 'DecoratedBranch',
- ]
+ "DecoratedBranch",
+]
from lazr.delegates import delegate_to
from zope.interface import implementer
from lp.app.interfaces.informationtype import IInformationType
from lp.app.interfaces.launchpad import IPrivacy
-from lp.code.interfaces.branch import (
- BzrIdentityMixin,
- IBranch,
- )
+from lp.code.interfaces.branch import BzrIdentityMixin, IBranch
from lp.services.propertycache import cachedproperty
@implementer(IPrivacy)
-@delegate_to(IBranch, IInformationType, context='branch')
+@delegate_to(IBranch, IInformationType, context="branch")
class DecoratedBranch(BzrIdentityMixin):
"""Wrap a number of the branch accessors to cache results.
@@ -54,8 +51,9 @@ class DecoratedBranch(BzrIdentityMixin):
"""A simple property to see if there are any series links."""
# True if linked to a product series or suite source package.
return (
- len(self.associated_product_series) > 0 or
- len(self.suite_source_packages) > 0)
+ len(self.associated_product_series) > 0
+ or len(self.suite_source_packages) > 0
+ )
def associatedProductSeries(self):
"""Override the IBranch.associatedProductSeries."""
diff --git a/lib/lp/code/browser/diff.py b/lib/lp/code/browser/diff.py
index d97ff97..0e4fd79 100644
--- a/lib/lp/code/browser/diff.py
+++ b/lib/lp/code/browser/diff.py
@@ -4,8 +4,8 @@
"""Display classes relating to diff objects of one sort or another."""
__all__ = [
- 'PreviewDiffFormatterAPI',
- ]
+ "PreviewDiffFormatterAPI",
+]
from lp import _
@@ -24,13 +24,11 @@ class PreviewDiffNavigation(Navigation, FileNavigationMixin):
class DiffFormatterAPI(ObjectFormatterAPI):
-
def _get_url(self, librarian_alias):
return librarian_alias.getURL()
def url(self, view_name=None, rootsite=None):
- """Use the url of the librarian file containing the diff.
- """
+ """Use the url of the librarian file containing the diff."""
librarian_alias = self._context.diff_text
if librarian_alias is None:
return None
@@ -50,51 +48,59 @@ class DiffFormatterAPI(ObjectFormatterAPI):
that name on this object.
"""
diff = self._context
- conflict_text = ''
+ conflict_text = ""
if diff.has_conflicts:
- conflict_text = _(' (has conflicts)')
+ conflict_text = _(" (has conflicts)")
- count_text = ''
+ count_text = ""
added = diff.added_lines_count
removed = diff.removed_lines_count
- if (added is not None and removed is not None):
- count_text = ' (+%d/-%d)' % (added, removed)
+ if added is not None and removed is not None:
+ count_text = " (+%d/-%d)" % (added, removed)
- file_text = ''
+ file_text = ""
diffstat = diff.diffstat
if diffstat is not None:
file_count = len(diffstat)
basic_file_text = get_plural_text(
- file_count, _('%d file modified'), _('%d files modified'))
+ file_count, _("%d file modified"), _("%d files modified")
+ )
basic_file_text = basic_file_text % file_count
- diffstat_text = '<br/>'.join(
- structured('%s (+%s/-%s)', path, added, removed).escapedtext
- for path, (added, removed) in sorted(diffstat.items()))
+ diffstat_text = "<br/>".join(
+ structured("%s (+%s/-%s)", path, added, removed).escapedtext
+ for path, (added, removed) in sorted(diffstat.items())
+ )
file_text = (
- '<div class="collapsible"><span>%s</span><div>%s</div></div>' %
- (basic_file_text, diffstat_text))
+ '<div class="collapsible"><span>%s</span><div>%s</div></div>'
+ % (basic_file_text, diffstat_text)
+ )
args = {
- 'line_count': _('%s lines') % diff.diff_lines_count,
- 'conflict_text': conflict_text,
- 'count_text': count_text,
- 'url': self.url(view_name),
- }
+ "line_count": _("%s lines") % diff.diff_lines_count,
+ "conflict_text": conflict_text,
+ "count_text": count_text,
+ "url": self.url(view_name),
+ }
# Under normal circumstances, there will be an associated file,
# however if the diff is empty, then there is no alias to link to.
- if args['url'] is None:
+ if args["url"] is None:
return structured(
- '<span class="empty-diff">'
- '%(line_count)s</span>', **args).escapedtext
+ '<span class="empty-diff">' "%(line_count)s</span>", **args
+ ).escapedtext
else:
- return structured(
- '<a href="%(url)s" class="diff-link">'
- '%(line_count)s%(count_text)s%(conflict_text)s'
- '</a>', **args).escapedtext + file_text
+ return (
+ structured(
+ '<a href="%(url)s" class="diff-link">'
+ "%(line_count)s%(count_text)s%(conflict_text)s"
+ "</a>",
+ **args,
+ ).escapedtext
+ + file_text
+ )
class PreviewDiffFormatterAPI(DiffFormatterAPI):
"""Formatter for preview diffs."""
def _get_url(self, library_):
- return canonical_url(self._context) + '/+files/preview.diff'
+ return canonical_url(self._context) + "/+files/preview.diff"
diff --git a/lib/lp/code/browser/gitlisting.py b/lib/lp/code/browser/gitlisting.py
index 5a9c092..f5415f1 100644
--- a/lib/lp/code/browser/gitlisting.py
+++ b/lib/lp/code/browser/gitlisting.py
@@ -4,15 +4,12 @@
"""View classes for Git repository listings."""
__all__ = [
- 'PersonTargetGitListingView',
- 'TargetGitListingView',
- ]
+ "PersonTargetGitListingView",
+ "TargetGitListingView",
+]
from zope.component import getUtility
-from zope.interface import (
- implementer,
- Interface,
- )
+from zope.interface import Interface, implementer
from lp.app.enums import PRIVATE_INFORMATION_TYPES
from lp.code.browser.gitrepository import GitRefBatchNavigator
@@ -20,13 +17,13 @@ from lp.code.enums import GitListingSort
from lp.code.interfaces.branchcollection import IBranchCollection
from lp.code.interfaces.gitcollection import IGitCollection
from lp.code.interfaces.gitnamespace import (
- get_git_namespace,
IGitNamespacePolicy,
- )
+ get_git_namespace,
+)
from lp.code.interfaces.gitrepository import IGitRepositorySet
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackage,
- )
+)
from lp.registry.interfaces.personociproject import IPersonOCIProject
from lp.registry.interfaces.personproduct import IPersonProduct
from lp.services.config import config
@@ -44,20 +41,22 @@ class IGitRepositoryBatchNavigator(Interface):
class GitRepositoryBatchNavigator(TableBatchNavigator):
"""Batch up Git repository listings."""
- variable_name_prefix = 'repo'
+ variable_name_prefix = "repo"
def __init__(self, view, repo_collection):
super().__init__(
repo_collection.getRepositories(
eager_load=True,
- sort_by=GitListingSort.MOST_RECENTLY_CHANGED_FIRST),
- view.request, size=config.launchpad.branchlisting_batch_size)
+ sort_by=GitListingSort.MOST_RECENTLY_CHANGED_FIRST,
+ ),
+ view.request,
+ size=config.launchpad.branchlisting_batch_size,
+ )
self.view = view
self.column_count = 2
class BaseGitListingView(LaunchpadView):
-
@property
def target(self):
raise NotImplementedError()
@@ -112,7 +111,7 @@ class BaseGitListingView(LaunchpadView):
class TargetGitListingView(BaseGitListingView):
- page_title = 'Git'
+ page_title = "Git"
@property
def target(self):
@@ -120,11 +119,10 @@ class TargetGitListingView(BaseGitListingView):
@cachedproperty
def default_git_repository(self):
- repo = getUtility(IGitRepositorySet).getDefaultRepository(
- self.context)
+ repo = getUtility(IGitRepositorySet).getDefaultRepository(self.context)
if repo is None:
return None
- elif check_permission('launchpad.View', repo):
+ elif check_permission("launchpad.View", repo):
return repo
else:
return None
@@ -132,11 +130,11 @@ class TargetGitListingView(BaseGitListingView):
class PersonTargetGitListingView(BaseGitListingView):
- page_title = 'Git'
+ page_title = "Git"
@property
def label(self):
- return 'Git repositories for %s' % self.target.displayname
+ return "Git repositories for %s" % self.target.displayname
@property
def target(self):
@@ -152,10 +150,11 @@ class PersonTargetGitListingView(BaseGitListingView):
@cachedproperty
def default_git_repository(self):
repo = getUtility(IGitRepositorySet).getDefaultRepositoryForOwner(
- self.context.person, self.target)
+ self.context.person, self.target
+ )
if repo is None:
return None
- elif check_permission('launchpad.View', repo):
+ elif check_permission("launchpad.View", repo):
return repo
else:
return None
@@ -168,7 +167,8 @@ class OCIProjectGitListingView(TargetGitListingView):
class PersonDistributionSourcePackageGitListingView(
- PersonTargetGitListingView):
+ PersonTargetGitListingView
+):
# PersonDistributionSourcePackage:+branches doesn't exist.
show_bzr_link = False
@@ -182,6 +182,6 @@ class PersonOCIProjectGitListingView(PersonTargetGitListingView):
class PlainGitListingView(BaseGitListingView):
- page_title = 'Git'
+ page_title = "Git"
target = None
default_git_repository = None
diff --git a/lib/lp/code/browser/gitref.py b/lib/lp/code/browser/gitref.py
index bc72f3a..df8c704 100644
--- a/lib/lp/code/browser/gitref.py
+++ b/lib/lp/code/browser/gitref.py
@@ -4,17 +4,13 @@
"""Git reference views."""
__all__ = [
- 'GitRefContextMenu',
- 'GitRefRegisterMergeProposalView',
- 'GitRefView',
- ]
+ "GitRefContextMenu",
+ "GitRefRegisterMergeProposalView",
+ "GitRefView",
+]
import json
-from urllib.parse import (
- quote_plus,
- urlsplit,
- urlunsplit,
- )
+from urllib.parse import quote_plus, urlsplit, urlunsplit
from breezy import urlutils
from lazr.restful.interface import copy_field
@@ -23,74 +19,60 @@ from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.widgets import TextAreaWidget
from zope.interface import Interface
from zope.publisher.interfaces import NotFound
-from zope.schema import (
- Bool,
- Text,
- )
+from zope.schema import Bool, Text
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.charms.browser.hascharmrecipes import (
HasCharmRecipesMenuMixin,
HasCharmRecipesViewMixin,
- )
+)
from lp.code.browser.branchmergeproposal import (
latest_proposals_for_each_branch,
- )
+)
from lp.code.browser.revisionstatus import HasRevisionStatusReportsMixin
from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
from lp.code.browser.widgets.gitref import GitRefWidget
from lp.code.enums import GitRepositoryType
-from lp.code.errors import (
- GitRepositoryScanFault,
- InvalidBranchMergeProposal,
- )
+from lp.code.errors import GitRepositoryScanFault, InvalidBranchMergeProposal
from lp.code.interfaces.branchmergeproposal import IBranchMergeProposal
from lp.code.interfaces.codereviewvote import ICodeReviewVoteReference
from lp.code.interfaces.gitref import IGitRef
from lp.code.interfaces.gitrepository import (
ContributorGitIdentity,
IGitRepositorySet,
- )
+)
from lp.registry.interfaces.person import IPerson
from lp.services.config import config
from lp.services.helpers import english_list
from lp.services.propertycache import cachedproperty
from lp.services.scripts import log
-from lp.services.webapp import (
- canonical_url,
- ContextMenu,
- LaunchpadView,
- Link,
- )
+from lp.services.webapp import ContextMenu, LaunchpadView, Link, canonical_url
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.escaping import structured
-from lp.snappy.browser.hassnaps import (
- HasSnapsMenuMixin,
- HasSnapsViewMixin,
- )
+from lp.snappy.browser.hassnaps import HasSnapsMenuMixin, HasSnapsViewMixin
class GitRefContextMenu(
- ContextMenu, HasRecipesMenuMixin, HasSnapsMenuMixin,
- HasCharmRecipesMenuMixin):
+ ContextMenu,
+ HasRecipesMenuMixin,
+ HasSnapsMenuMixin,
+ HasCharmRecipesMenuMixin,
+):
"""Context menu for Git references."""
usedfor = IGitRef
- facet = 'branches'
+ facet = "branches"
links = [
- 'browse_commits',
- 'create_charm_recipe',
- 'create_recipe',
- 'create_snap',
- 'register_merge',
- 'source',
- 'view_charm_recipes',
- 'view_recipes',
- ]
+ "browse_commits",
+ "create_charm_recipe",
+ "create_recipe",
+ "create_snap",
+ "register_merge",
+ "source",
+ "view_charm_recipes",
+ "view_recipes",
+ ]
def source(self):
"""Return a link to the branch's browsing interface."""
@@ -103,13 +85,14 @@ class GitRefContextMenu(
text = "All commits"
url = "%s/log/?h=%s" % (
self.context.repository.getCodebrowseUrl(),
- quote_plus(self.context.name.encode("UTF-8")))
+ quote_plus(self.context.name.encode("UTF-8")),
+ )
return Link(url, text)
def register_merge(self):
- text = 'Propose for merging'
+ text = "Propose for merging"
enabled = self.context.namespace.supports_merge_proposals
- return Link('+register-merge', text, icon='add', enabled=enabled)
+ return Link("+register-merge", text, icon="add", enabled=enabled)
def create_recipe(self):
# You can't create a recipe for a reference in a private repository.
@@ -118,8 +101,12 @@ class GitRefContextMenu(
return Link("+new-recipe", text, enabled=enabled, icon="add")
-class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
- HasRevisionStatusReportsMixin):
+class GitRefView(
+ LaunchpadView,
+ HasSnapsViewMixin,
+ HasCharmRecipesViewMixin,
+ HasRevisionStatusReportsMixin,
+):
# This is set at self.commit_infos, and should be accessed by the view
# as self.commit_info_message.
@@ -146,9 +133,11 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
contributor = ContributorGitIdentity(
owner=self.user,
target=self.context.repository.target,
- repository=self.context.repository)
+ repository=self.context.repository,
+ )
base_url = urlutils.join(
- config.codehosting.git_ssh_root, contributor.shortened_path)
+ config.codehosting.git_ssh_root, contributor.shortened_path
+ )
url = list(urlsplit(base_url))
url[1] = "{}@{}".format(self.user.name, url[1])
return urlunsplit(url)
@@ -157,9 +146,9 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
def user_can_push(self):
"""Whether the user can push to this branch."""
return (
- self.context.repository.repository_type ==
- GitRepositoryType.HOSTED and
- check_permission("launchpad.Edit", self.context))
+ self.context.repository.repository_type == GitRepositoryType.HOSTED
+ and check_permission("launchpad.Edit", self.context)
+ )
@property
def show_merge_links(self):
@@ -194,7 +183,8 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
if IPerson.providedBy(self.context.namespace.target):
messages.append(
"You will only be able to propose a merge to another personal "
- "repository with the same name.")
+ "repository with the same name."
+ )
return messages
@cachedproperty
@@ -207,17 +197,20 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
def landing_candidates(self):
"""Return a decorated list of landing candidates."""
candidates = self.context.getPrecachedLandingCandidates(self.user)
- return [proposal for proposal in candidates
- if check_permission("launchpad.View", proposal)]
+ return [
+ proposal
+ for proposal in candidates
+ if check_permission("launchpad.View", proposal)
+ ]
def _getBranchCountText(self, count):
"""Help to show user friendly text."""
if count == 0:
- return 'No branches'
+ return "No branches"
elif count == 1:
- return '1 branch'
+ return "1 branch"
else:
- return '%s branches' % count
+ return "%s branches" % count
@cachedproperty
def landing_candidate_count_text(self):
@@ -225,8 +218,11 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
@cachedproperty
def dependent_landings(self):
- return [proposal for proposal in self.context.dependent_landings
- if check_permission("launchpad.View", proposal)]
+ return [
+ proposal
+ for proposal in self.context.dependent_landings
+ if check_permission("launchpad.View", proposal)
+ ]
@cachedproperty
def dependent_landing_count_text(self):
@@ -235,21 +231,27 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
@cachedproperty
def commit_infos(self):
try:
- self._commit_info_message = ''
+ self._commit_info_message = ""
return self.context.getLatestCommits(
- extended_details=True, user=self.user, handle_timeout=True,
- logger=log)
+ extended_details=True,
+ user=self.user,
+ handle_timeout=True,
+ logger=log,
+ )
except GitRepositoryScanFault as e:
log.error("There was an error fetching git commit info: %s" % e)
self._commit_info_message = (
"There was an error while fetching commit information from "
"code hosting service. Please try again in a few minutes. "
'If the problem persists, <a href="/launchpad/+addquestion">'
- "contact Launchpad support</a>.")
+ "contact Launchpad support</a>."
+ )
return []
except Exception as e:
- log.error("There was an error scanning %s: (%s) %s" %
- (self.context, e.__class__, e))
+ log.error(
+ "There was an error scanning %s: (%s) %s"
+ % (self.context, e.__class__, e)
+ )
raise
def commit_infos_message(self):
@@ -265,54 +267,66 @@ class GitRefView(LaunchpadView, HasSnapsViewMixin, HasCharmRecipesViewMixin,
count = self.context.recipes.count()
if count == 0:
# Nothing to link to.
- return 'No recipes using this branch.'
+ return "No recipes using this branch."
elif count == 1:
# Link to the single recipe.
return structured(
'<a href="%s">1 recipe</a> using this branch.',
- canonical_url(self.context.recipes.one())).escapedtext
+ canonical_url(self.context.recipes.one()),
+ ).escapedtext
else:
# Link to a recipe listing.
return structured(
- '<a href="+recipes">%s recipes</a> using this branch.',
- count).escapedtext
+ '<a href="+recipes">%s recipes</a> using this branch.', count
+ ).escapedtext
class GitRefRegisterMergeProposalSchema(Interface):
"""The schema to define the form for registering a new merge proposal."""
target_git_ref = copy_field(
- IBranchMergeProposal['target_git_ref'], required=True)
+ IBranchMergeProposal["target_git_ref"], required=True
+ )
prerequisite_git_ref = copy_field(
- IBranchMergeProposal['prerequisite_git_ref'], required=False,
- description=_("If the source branch is based on a different branch, "
- "you can add this as a prerequisite. "
- "The changes from that branch will not show "
- "in the diff."))
+ IBranchMergeProposal["prerequisite_git_ref"],
+ required=False,
+ description=_(
+ "If the source branch is based on a different branch, "
+ "you can add this as a prerequisite. "
+ "The changes from that branch will not show "
+ "in the diff."
+ ),
+ )
comment = Text(
- title=_('Description of the change'), required=False,
- description=_('Describe what changes your branch introduces, '
- 'what bugs it fixes, or what features it implements. '
- 'Ideally include rationale and how to test. '
- 'You do not need to repeat information from the commit '
- 'message here.'))
+ title=_("Description of the change"),
+ required=False,
+ description=_(
+ "Describe what changes your branch introduces, "
+ "what bugs it fixes, or what features it implements. "
+ "Ideally include rationale and how to test. "
+ "You do not need to repeat information from the commit "
+ "message here."
+ ),
+ )
- reviewer = copy_field(
- ICodeReviewVoteReference['reviewer'], required=False)
+ reviewer = copy_field(ICodeReviewVoteReference["reviewer"], required=False)
review_type = copy_field(
- ICodeReviewVoteReference['review_type'],
- description='Lowercase keywords describing the type of review you '
- 'would like to be performed.')
+ ICodeReviewVoteReference["review_type"],
+ description="Lowercase keywords describing the type of review you "
+ "would like to be performed.",
+ )
- commit_message = IBranchMergeProposal['commit_message']
+ commit_message = IBranchMergeProposal["commit_message"]
needs_review = Bool(
- title=_("Needs review"), required=True, default=True,
- description=_(
- "Is the proposal ready for review now?"))
+ title=_("Needs review"),
+ required=True,
+ default=True,
+ description=_("Is the proposal ready for review now?"),
+ )
class GitRefRegisterMergeProposalView(LaunchpadFormView):
@@ -322,15 +336,19 @@ class GitRefRegisterMergeProposalView(LaunchpadFormView):
for_input = True
custom_widget_target_git_ref = CustomWidgetFactory(
- GitRefWidget, require_branch=True)
+ GitRefWidget, require_branch=True
+ )
custom_widget_prerequisite_git_ref = CustomWidgetFactory(
- GitRefWidget, require_branch=True)
+ GitRefWidget, require_branch=True
+ )
custom_widget_commit_message = CustomWidgetFactory(
- TextAreaWidget, cssClass='comment-text')
+ TextAreaWidget, cssClass="comment-text"
+ )
custom_widget_comment = CustomWidgetFactory(
- TextAreaWidget, cssClass='comment-text')
+ TextAreaWidget, cssClass="comment-text"
+ )
- page_title = label = 'Propose for merging'
+ page_title = label = "Propose for merging"
@property
def cancel_url(self):
@@ -339,87 +357,103 @@ class GitRefRegisterMergeProposalView(LaunchpadFormView):
def initialize(self):
"""Show a 404 if the repository namespace doesn't support proposals."""
if not self.context.namespace.supports_merge_proposals:
- raise NotFound(self.context, '+register-merge')
+ raise NotFound(self.context, "+register-merge")
super().initialize()
def setUpWidgets(self, context=None):
super().setUpWidgets(context=context)
- if not self.widgets['target_git_ref'].hasInput():
+ if not self.widgets["target_git_ref"].hasInput():
if self.context.repository.namespace.has_defaults:
repo_set = getUtility(IGitRepositorySet)
default_repo = repo_set.getDefaultRepository(
- self.context.repository.target)
+ self.context.repository.target
+ )
else:
default_repo = None
if not default_repo:
default_repo = self.context.repository
if default_repo.default_branch:
default_ref = default_repo.getRefByPath(
- default_repo.default_branch)
+ default_repo.default_branch
+ )
with_path = True
else:
default_ref = self.context
with_path = False
self.widgets["target_git_ref"].setRenderedValue(
- default_ref, with_path=with_path)
+ default_ref, with_path=with_path
+ )
- @action('Propose Merge', name='register',
- failure=LaunchpadFormView.ajax_failure_handler)
+ @action(
+ "Propose Merge",
+ name="register",
+ failure=LaunchpadFormView.ajax_failure_handler,
+ )
def register_action(self, action, data):
"""Register the new merge proposal."""
registrant = self.user
source_ref = self.context
- target_ref = data['target_git_ref']
- prerequisite_ref = data.get('prerequisite_git_ref')
+ target_ref = data["target_git_ref"]
+ prerequisite_ref = data.get("prerequisite_git_ref")
review_requests = []
- reviewer = data.get('reviewer')
- review_type = data.get('review_type')
+ reviewer = data.get("reviewer")
+ review_type = data.get("review_type")
if reviewer is None:
reviewer = target_ref.code_reviewer
if reviewer is not None:
review_requests.append((reviewer, review_type))
repository_names = [
- ref.repository.unique_name for ref in (source_ref, target_ref)]
+ ref.repository.unique_name for ref in (source_ref, target_ref)
+ ]
repository_set = getUtility(IGitRepositorySet)
visibility_info = repository_set.getRepositoryVisibilityInfo(
- self.user, reviewer, repository_names)
- visible_repositories = list(visibility_info['visible_repositories'])
+ self.user, reviewer, repository_names
+ )
+ visible_repositories = list(visibility_info["visible_repositories"])
if self.request.is_ajax and len(visible_repositories) < 2:
self.request.response.setStatus(400, "Repository Visibility")
- self.request.response.setHeader(
- 'Content-Type', 'application/json')
- return json.dumps({
- 'person_name': visibility_info['person_name'],
- 'repositories_to_check': repository_names,
- 'visible_repositories': visible_repositories,
- })
+ self.request.response.setHeader("Content-Type", "application/json")
+ return json.dumps(
+ {
+ "person_name": visibility_info["person_name"],
+ "repositories_to_check": repository_names,
+ "visible_repositories": visible_repositories,
+ }
+ )
try:
proposal = source_ref.addLandingTarget(
- registrant=registrant, merge_target=target_ref,
+ registrant=registrant,
+ merge_target=target_ref,
merge_prerequisite=prerequisite_ref,
- needs_review=data['needs_review'],
- description=data.get('comment'),
+ needs_review=data["needs_review"],
+ description=data.get("comment"),
review_requests=review_requests,
- commit_message=data.get('commit_message'))
+ commit_message=data.get("commit_message"),
+ )
if len(visible_repositories) < 2:
invisible_repositories = [
ref.repository.unique_name
for ref in (source_ref, target_ref)
- if ref.repository.unique_name not in visible_repositories]
+ if ref.repository.unique_name not in visible_repositories
+ ]
self.request.response.addNotification(
- 'To ensure visibility, %s is now subscribed to: %s'
- % (visibility_info['person_name'],
- english_list(invisible_repositories)))
+ "To ensure visibility, %s is now subscribed to: %s"
+ % (
+ visibility_info["person_name"],
+ english_list(invisible_repositories),
+ )
+ )
# Success so we do a client redirect to the new mp page.
if self.request.is_ajax:
self.request.response.setStatus(201)
self.request.response.setHeader(
- 'Location', canonical_url(proposal))
+ "Location", canonical_url(proposal)
+ )
return None
else:
self.next_url = canonical_url(proposal)
@@ -427,31 +461,36 @@ class GitRefRegisterMergeProposalView(LaunchpadFormView):
self.addError(str(error))
def _validateRef(self, data, name):
- ref = data['{}_git_ref'.format(name)]
+ ref = data["{}_git_ref".format(name)]
if ref == self.context:
self.setFieldError(
- '%s_git_ref' % name,
+ "%s_git_ref" % name,
"The %s repository and path together cannot be the same "
- "as the source repository and path." % name)
+ "as the source repository and path." % name,
+ )
return ref.repository
def validate(self, data):
source_ref = self.context
# The existence of target_git_repository is handled by the form
# machinery.
- if data.get('target_git_ref') is not None:
- target_repository = self._validateRef(data, 'target')
+ if data.get("target_git_ref") is not None:
+ target_repository = self._validateRef(data, "target")
if not target_repository.isRepositoryMergeable(
- source_ref.repository):
+ source_ref.repository
+ ):
self.setFieldError(
- 'target_git_ref',
- "%s is not mergeable into this repository." %
- source_ref.repository.identity)
- if data.get('prerequisite_git_ref') is not None:
- prerequisite_repository = self._validateRef(data, 'prerequisite')
+ "target_git_ref",
+ "%s is not mergeable into this repository."
+ % source_ref.repository.identity,
+ )
+ if data.get("prerequisite_git_ref") is not None:
+ prerequisite_repository = self._validateRef(data, "prerequisite")
if not target_repository.isRepositoryMergeable(
- prerequisite_repository):
+ prerequisite_repository
+ ):
self.setFieldError(
- 'prerequisite_git_ref',
- "This repository is not mergeable into %s." %
- target_repository.identity)
+ "prerequisite_git_ref",
+ "This repository is not mergeable into %s."
+ % target_repository.identity,
+ )
diff --git a/lib/lp/code/browser/gitrepository.py b/lib/lp/code/browser/gitrepository.py
index 96a6ce0..8fa17b5 100644
--- a/lib/lp/code/browser/gitrepository.py
+++ b/lib/lp/code/browser/gitrepository.py
@@ -4,101 +4,84 @@
"""Git repository views."""
__all__ = [
- 'GitRefBatchNavigator',
- 'GitRepositoriesBreadcrumb',
- 'GitRepositoryActivityView',
- 'GitRepositoryBreadcrumb',
- 'GitRepositoryContextMenu',
- 'GitRepositoryDeletionView',
- 'GitRepositoryEditInformationTypeView',
- 'GitRepositoryEditMenu',
- 'GitRepositoryEditReviewerView',
- 'GitRepositoryEditView',
- 'GitRepositoryNavigation',
- 'GitRepositoryPermissionsView',
- 'GitRepositoryURL',
- 'GitRepositoryView',
- ]
+ "GitRefBatchNavigator",
+ "GitRepositoriesBreadcrumb",
+ "GitRepositoryActivityView",
+ "GitRepositoryBreadcrumb",
+ "GitRepositoryContextMenu",
+ "GitRepositoryDeletionView",
+ "GitRepositoryEditInformationTypeView",
+ "GitRepositoryEditMenu",
+ "GitRepositoryEditReviewerView",
+ "GitRepositoryEditView",
+ "GitRepositoryNavigation",
+ "GitRepositoryPermissionsView",
+ "GitRepositoryURL",
+ "GitRepositoryView",
+]
import base64
import binascii
from collections import defaultdict
-from urllib.parse import (
- urlsplit,
- urlunsplit,
- )
+from urllib.parse import urlsplit, urlunsplit
from breezy import urlutils
from lazr.lifecycle.event import ObjectModifiedEvent
from lazr.lifecycle.snapshot import Snapshot
-from lazr.restful.interface import (
- copy_field,
- use_template,
- )
+from lazr.restful.interface import copy_field, use_template
from zope.component import getUtility
from zope.event import notify
from zope.formlib import form
from zope.formlib.form import FormFields
from zope.formlib.textwidgets import IntWidget
from zope.formlib.widget import CustomWidgetFactory
-from zope.interface import (
- implementer,
- Interface,
- providedBy,
- )
+from zope.interface import Interface, implementer, providedBy
from zope.publisher.interfaces.browser import IBrowserPublisher
-from zope.schema import (
- Bool,
- Choice,
- Int,
- )
+from zope.schema import Bool, Choice, Int
from zope.schema.vocabulary import (
- getVocabularyRegistry,
SimpleTerm,
SimpleVocabulary,
- )
+ getVocabularyRegistry,
+)
from zope.security.interfaces import Unauthorized
from lp import _
from lp.app.browser.informationtype import InformationTypePortletMixin
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
-from lp.app.errors import (
- NotFoundError,
- UnexpectedFormData,
- )
+ action,
+)
+from lp.app.errors import NotFoundError, UnexpectedFormData
from lp.app.vocabularies import InformationTypeVocabulary
from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
from lp.charms.browser.hascharmrecipes import HasCharmRecipesViewMixin
from lp.code.browser.branch import CodeEditOwnerMixin
from lp.code.browser.branchmergeproposal import (
latest_proposals_for_each_branch,
- )
+)
from lp.code.browser.codeimport import CodeImportTargetMixin
from lp.code.browser.sourcepackagerecipelisting import HasRecipesMenuMixin
from lp.code.browser.widgets.gitgrantee import (
GitGranteeDisplayWidget,
GitGranteeField,
GitGranteeWidget,
- )
+)
from lp.code.browser.widgets.gitrepositorytarget import (
GitRepositoryTargetDisplayWidget,
GitRepositoryTargetWidget,
- )
+)
from lp.code.enums import (
GitGranteeType,
GitRepositoryStatus,
GitRepositoryType,
- )
+)
from lp.code.errors import (
GitDefaultConflict,
GitRepositoryCreationForbidden,
GitRepositoryExists,
GitTargetError,
- )
+)
from lp.code.interfaces.cibuild import ICIBuildSet
from lp.code.interfaces.gitnamespace import get_git_namespace
from lp.code.interfaces.gitref import IGitRefBatchNavigator
@@ -106,16 +89,13 @@ from lp.code.interfaces.gitrepository import (
ContributorGitIdentity,
IGitRepository,
IGitRepositorySet,
- )
+)
from lp.code.interfaces.revisionstatus import (
IRevisionStatusArtifactSet,
IRevisionStatusReportSet,
- )
+)
from lp.code.vocabularies.gitrule import GitPermissionsVocabulary
-from lp.registry.interfaces.person import (
- IPerson,
- IPersonSet,
- )
+from lp.registry.interfaces.person import IPerson, IPersonSet
from lp.registry.vocabularies import UserTeamsParticipationPlusSelfVocabulary
from lp.services.config import config
from lp.services.database.constants import UTC_NOW
@@ -124,20 +104,20 @@ from lp.services.fields import UniqueField
from lp.services.job.interfaces.job import JobStatus
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
NavigationMenu,
+ canonical_url,
+ enabled_with_permission,
stepthrough,
stepto,
- )
+)
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.services.webapp.batching import TableBatchNavigator
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.escaping import structured
@@ -148,8 +128,7 @@ from lp.services.webhooks.browser import WebhookTargetNavigationMixin
from lp.snappy.browser.hassnaps import HasSnapsViewMixin
from lp.soyuz.browser.build import get_build_by_id_str
-
-GIT_REPOSITORY_FORK_ENABLED = 'gitrepository.fork.enabled'
+GIT_REPOSITORY_FORK_ENABLED = "gitrepository.fork.enabled"
@implementer(ICanonicalUrlData)
@@ -181,7 +160,6 @@ class GitRepositoriesBreadcrumb(Breadcrumb):
class GitRepositoryBreadcrumb(Breadcrumb):
-
@property
def text(self):
return self.context.git_identity
@@ -195,27 +173,26 @@ class GitRepositoryNavigation(WebhookTargetNavigationMixin, Navigation):
usedfor = IGitRepository
- @stepthrough('+status')
+ @stepthrough("+status")
def traverse_status(self, id):
try:
report_id = int(id)
except ValueError:
raise NotFoundError(report_id)
- report = getUtility(
- IRevisionStatusReportSet).getByID(report_id)
+ report = getUtility(IRevisionStatusReportSet).getByID(report_id)
if report is None:
raise NotFoundError(report_id)
return report
- @stepthrough('+artifact')
+ @stepthrough("+artifact")
def traverse_artifact(self, id):
try:
artifact_id = int(id)
except ValueError:
raise NotFoundError(artifact_id)
- artifact = getUtility(
- IRevisionStatusArtifactSet).getByRepositoryAndID(
- self.context, artifact_id)
+ artifact = getUtility(IRevisionStatusArtifactSet).getByRepositoryAndID(
+ self.context, artifact_id
+ )
if artifact is None:
raise NotFoundError(artifact_id)
return artifact
@@ -294,7 +271,7 @@ class GitRepositoryEditMenu(NavigationMenu):
"access_tokens",
"webhooks",
"delete",
- ]
+ ]
@enabled_with_permission("launchpad.Edit")
def edit(self):
@@ -325,8 +302,11 @@ class GitRepositoryEditMenu(NavigationMenu):
def webhooks(self):
text = "Manage webhooks"
return Link(
- "+webhooks", text, icon="edit",
- enabled=bool(getFeatureFlag('webhooks.new.enabled')))
+ "+webhooks",
+ text,
+ icon="edit",
+ enabled=bool(getFeatureFlag("webhooks.new.enabled")),
+ )
@enabled_with_permission("launchpad.Edit")
def delete(self):
@@ -340,8 +320,14 @@ class GitRepositoryContextMenu(ContextMenu, HasRecipesMenuMixin):
usedfor = IGitRepository
facet = "branches"
links = [
- "add_subscriber", "create_recipe", "edit_import", "source",
- "subscription", "view_recipes", "visibility"]
+ "add_subscriber",
+ "create_recipe",
+ "edit_import",
+ "source",
+ "subscription",
+ "view_recipes",
+ "visibility",
+ ]
@enabled_with_permission("launchpad.AnyPerson")
def subscription(self):
@@ -375,8 +361,9 @@ class GitRepositoryContextMenu(ContextMenu, HasRecipesMenuMixin):
def edit_import(self):
text = "Edit import source or review import"
enabled = (
- self.context.repository_type == GitRepositoryType.IMPORTED and
- check_permission("launchpad.Edit", self.context.code_import))
+ self.context.repository_type == GitRepositoryType.IMPORTED
+ and check_permission("launchpad.Edit", self.context.code_import)
+ )
return Link("+edit-import", text, icon="edit", enabled=enabled)
def create_recipe(self):
@@ -393,8 +380,10 @@ class GitRefBatchNavigator(TableBatchNavigator):
def __init__(self, view, context):
self.context = context
super().__init__(
- self.context.branches_by_date, view.request,
- size=config.launchpad.branchlisting_batch_size)
+ self.context.branches_by_date,
+ view.request,
+ size=config.launchpad.branchlisting_batch_size,
+ )
self.view = view
self.column_count = 3
@@ -409,10 +398,13 @@ class GitRefBatchNavigator(TableBatchNavigator):
return "listing sortable"
-class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
- HasSnapsViewMixin, HasCharmRecipesViewMixin,
- CodeImportTargetMixin):
-
+class GitRepositoryView(
+ InformationTypePortletMixin,
+ LaunchpadView,
+ HasSnapsViewMixin,
+ HasCharmRecipesViewMixin,
+ CodeImportTargetMixin,
+):
@property
def page_title(self):
return self.context.display_name
@@ -428,7 +420,8 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
authorised_people = [self.context.owner]
if self.user is not None:
precache_permission_for_objects(
- self.request, "launchpad.LimitedView", authorised_people)
+ self.request, "launchpad.LimitedView", authorised_people
+ )
@property
def git_ssh_url(self):
@@ -446,9 +439,11 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
contributor = ContributorGitIdentity(
owner=self.user,
target=self.context.target,
- repository=self.context)
+ repository=self.context,
+ )
base_url = urlutils.join(
- config.codehosting.git_ssh_root, contributor.shortened_path)
+ config.codehosting.git_ssh_root, contributor.shortened_path
+ )
url = list(urlsplit(base_url))
url[1] = "{}@{}".format(self.user.name, url[1])
return urlunsplit(url)
@@ -457,8 +452,9 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
def user_can_push(self):
"""Whether the user can push to this branch."""
return (
- self.context.repository_type == GitRepositoryType.HOSTED and
- check_permission("launchpad.Edit", self.context))
+ self.context.repository_type == GitRepositoryType.HOSTED
+ and check_permission("launchpad.Edit", self.context)
+ )
def branches(self):
"""All branches in this repository, sorted for display."""
@@ -470,17 +466,19 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
count = self.context.recipes.count()
if count == 0:
# Nothing to link to.
- return 'No recipes using this repository.'
+ return "No recipes using this repository."
elif count == 1:
# Link to the single recipe.
return structured(
'<a href="%s">1 recipe</a> using this repository.',
- canonical_url(self.context.recipes.one())).escapedtext
+ canonical_url(self.context.recipes.one()),
+ ).escapedtext
else:
# Link to a recipe listing.
return structured(
'<a href="+recipes">%s recipes</a> using this repository.',
- count).escapedtext
+ count,
+ ).escapedtext
@property
def is_imported(self):
@@ -490,17 +488,20 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
@cachedproperty
def landing_candidates(self):
candidates = self.context.getPrecachedLandingCandidates(self.user)
- return [proposal for proposal in candidates
- if check_permission("launchpad.View", proposal)]
+ return [
+ proposal
+ for proposal in candidates
+ if check_permission("launchpad.View", proposal)
+ ]
def _getBranchCountText(self, count):
"""Help to show user friendly text."""
if count == 0:
- return 'No branches'
+ return "No branches"
elif count == 1:
- return '1 branch'
+ return "1 branch"
else:
- return '%s branches' % count
+ return "%s branches" % count
@cachedproperty
def landing_candidate_count_text(self):
@@ -510,7 +511,8 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
def landing_targets(self):
"""Return a filtered list of active landing targets."""
targets = self.context.getPrecachedLandingTargets(
- self.user, only_active=True)
+ self.user, only_active=True
+ )
return latest_proposals_for_each_branch(targets)
@property
@@ -541,7 +543,7 @@ class GitRepositoryView(InformationTypePortletMixin, LaunchpadView,
@property
def fork_url(self):
- return canonical_url(self.context, view_name='+fork')
+ return canonical_url(self.context, view_name="+fork")
class GitRepositoryForkView(LaunchpadEditFormView):
@@ -558,26 +560,30 @@ class GitRepositoryForkView(LaunchpadEditFormView):
def setUpFields(self):
super().setUpFields()
owner_field = Choice(
- vocabulary='AllUserTeamsParticipationPlusSelf',
- title='Fork to the following owner', required=True,
- __name__='owner')
+ vocabulary="AllUserTeamsParticipationPlusSelf",
+ title="Fork to the following owner",
+ required=True,
+ __name__="owner",
+ )
self.form_fields += FormFields(owner_field)
@property
def initial_values(self):
- return {'owner': self.user}
+ return {"owner": self.user}
def validate(self, data):
new_owner = data.get("owner")
if not new_owner or not self.user.inTeam(new_owner):
self.setFieldError(
"owner",
- "You should select a valid user to fork the repository.")
+ "You should select a valid user to fork the repository.",
+ )
- @action('Fork it', name='fork')
+ @action("Fork it", name="fork")
def fork(self, action, data):
forked = getUtility(IGitRepositorySet).fork(
- self.context, self.user, data.get("owner"))
+ self.context, self.user, data.get("owner")
+ )
self.request.response.addNotification("Repository forked.")
self.next_url = canonical_url(forked)
@@ -592,7 +598,7 @@ class GitRepositoryRescanView(LaunchpadEditFormView):
field_names = []
- @action('Rescan', name='rescan')
+ @action("Rescan", name="rescan")
def rescan(self, action, data):
self.context.rescan()
self.request.response.addNotification("Repository scan scheduled")
@@ -624,14 +630,18 @@ class GitRepositoryEditFormView(LaunchpadEditFormView):
This is necessary to make various fields editable that are not
normally editable through the interface.
"""
+
use_template(IGitRepository, include=["default_branch"])
information_type = copy_field(
- IGitRepository["information_type"], readonly=False,
- vocabulary=InformationTypeVocabulary(types=info_types))
+ IGitRepository["information_type"],
+ readonly=False,
+ vocabulary=InformationTypeVocabulary(types=info_types),
+ )
name = copy_field(IGitRepository["name"], readonly=False)
owner = copy_field(IGitRepository["owner"], readonly=False)
owner_default = copy_field(
- IGitRepository["owner_default"], readonly=False)
+ IGitRepository["owner_default"], readonly=False
+ )
reviewer = copy_field(IGitRepository["reviewer"], required=True)
target = copy_field(IGitRepository["target"], readonly=False)
@@ -650,8 +660,11 @@ class GitRepositoryEditFormView(LaunchpadEditFormView):
"""See `LaunchpadFormView`."""
return {self.schema: self.context}
- @action("Change Git Repository", name="change",
- failure=LaunchpadFormView.ajax_failure_handler)
+ @action(
+ "Change Git Repository",
+ name="change",
+ failure=LaunchpadFormView.ajax_failure_handler,
+ )
def change_action(self, action, data):
# If the owner has changed, add an explicit notification. We take
# our own snapshot here to make sure that the snapshot records
@@ -660,7 +673,8 @@ class GitRepositoryEditFormView(LaunchpadEditFormView):
# updateContextFromData.
changed = False
repository_before_modification = Snapshot(
- self.context, providing=providedBy(self.context))
+ self.context, providing=providedBy(self.context)
+ )
if "name" in data:
name = data.pop("name")
if name != self.context.name:
@@ -672,12 +686,14 @@ class GitRepositoryEditFormView(LaunchpadEditFormView):
self.context.setOwner(owner, self.user)
changed = True
self.request.response.addNotification(
- "The repository owner has been changed to %s (%s)" %
- (owner.displayname, owner.name))
+ "The repository owner has been changed to %s (%s)"
+ % (owner.displayname, owner.name)
+ )
if "information_type" in data:
information_type = data.pop("information_type")
self.context.transitionToInformationType(
- information_type, self.user)
+ information_type, self.user
+ )
if "target" in data:
target = data.pop("target")
if target is None:
@@ -692,11 +708,13 @@ class GitRepositoryEditFormView(LaunchpadEditFormView):
if IPerson.providedBy(target):
self.request.response.addNotification(
"This repository is now a personal repository for %s "
- "(%s)" % (target.displayname, target.name))
+ "(%s)" % (target.displayname, target.name)
+ )
else:
self.request.response.addNotification(
- "The repository target has been changed to %s (%s)" %
- (target.displayname, target.name))
+ "The repository target has been changed to %s (%s)"
+ % (target.displayname, target.name)
+ )
if "reviewer" in data:
reviewer = data.pop("reviewer")
if reviewer != self.context.code_reviewer:
@@ -708,8 +726,10 @@ class GitRepositoryEditFormView(LaunchpadEditFormView):
changed = True
if "owner_default" in data:
owner_default = data.pop("owner_default")
- if (self.context.namespace.has_defaults and
- owner_default != self.context.owner_default):
+ if (
+ self.context.namespace.has_defaults
+ and owner_default != self.context.owner_default
+ ):
self.context.setOwnerDefault(owner_default)
if self.updateContextFromData(data, notify_modified=False):
@@ -719,9 +739,13 @@ class GitRepositoryEditFormView(LaunchpadEditFormView):
# Notify that the object has changed with the snapshot that was
# taken earlier.
field_names = [
- form_field.__name__ for form_field in self.form_fields]
- notify(ObjectModifiedEvent(
- self.context, repository_before_modification, field_names))
+ form_field.__name__ for form_field in self.form_fields
+ ]
+ notify(
+ ObjectModifiedEvent(
+ self.context, repository_before_modification, field_names
+ )
+ )
# Only specify that the context was modified if there
# was in fact a change.
self.context.date_last_modified = UTC_NOW
@@ -767,13 +791,14 @@ class GitRepositoryEditView(CodeEditOwnerMixin, GitRepositoryEditFormView):
"information_type",
"owner_default",
"default_branch",
- ]
+ ]
custom_widget_information_type = LaunchpadRadioWidgetWithDescription
any_owner_description = _(
"As an administrator you are able to assign this repository to any "
- "person or team.")
+ "person or team."
+ )
def setUpFields(self):
super().setUpFields()
@@ -784,13 +809,18 @@ class GitRepositoryEditView(CodeEditOwnerMixin, GitRepositoryEditFormView):
if check_permission("launchpad.Admin", repository):
owner_field = self.schema["owner"]
any_owner_choice = Choice(
- __name__="owner", title=owner_field.title,
+ __name__="owner",
+ title=owner_field.title,
description=_(
"As an administrator you are able to assign this "
- "repository to any person or team."),
- required=True, vocabulary="ValidPersonOrTeam")
+ "repository to any person or team."
+ ),
+ required=True,
+ vocabulary="ValidPersonOrTeam",
+ )
any_owner_field = form.Fields(
- any_owner_choice, render_context=self.render_context)
+ any_owner_choice, render_context=self.render_context
+ )
# Replace the normal owner field with a more permissive vocab.
self.form_fields = self.form_fields.omit("owner")
self.form_fields = any_owner_field + self.form_fields
@@ -802,16 +832,21 @@ class GitRepositoryEditView(CodeEditOwnerMixin, GitRepositoryEditFormView):
if not self.user.inTeam(self.context.owner):
vocab = UserTeamsParticipationPlusSelfVocabulary()
owner = self.context.owner
- terms = [SimpleTerm(
- owner, owner.name, owner.unique_displayname)]
+ terms = [
+ SimpleTerm(owner, owner.name, owner.unique_displayname)
+ ]
terms.extend([term for term in vocab])
owner_field = self.schema["owner"]
owner_choice = Choice(
- __name__="owner", title=owner_field.title,
+ __name__="owner",
+ title=owner_field.title,
description=owner_field.description,
- required=True, vocabulary=SimpleVocabulary(terms))
+ required=True,
+ vocabulary=SimpleVocabulary(terms),
+ )
new_owner_field = form.Fields(
- owner_choice, render_context=self.render_context)
+ owner_choice, render_context=self.render_context
+ )
# Replace the normal owner field with a more permissive vocab.
self.form_fields = self.form_fields.omit("owner")
self.form_fields = new_owner_field + self.form_fields
@@ -828,9 +863,10 @@ class GitRepositoryEditView(CodeEditOwnerMixin, GitRepositoryEditFormView):
if self.context.target_default:
self.widgets["target"].hint = (
"This is the default repository for this target, so it "
- "cannot be moved to another target.")
+ "cannot be moved to another target."
+ )
if self.context.default_branch:
- self.widgets['default_branch'].context.required = True
+ self.widgets["default_branch"].context.required = True
def _setRepositoryExists(self, existing_repository, field_name="name"):
owner = existing_repository.owner
@@ -840,8 +876,10 @@ class GitRepositoryEditView(CodeEditOwnerMixin, GitRepositoryEditFormView):
prefix = "%s already has" % owner.displayname
message = structured(
"%s a repository for <em>%s</em> called <em>%s</em>.",
- prefix, existing_repository.target.displayname,
- existing_repository.name)
+ prefix,
+ existing_repository.target.displayname,
+ existing_repository.name,
+ )
self.setFieldError(field_name, message)
def validate(self, data):
@@ -851,34 +889,44 @@ class GitRepositoryEditView(CodeEditOwnerMixin, GitRepositoryEditFormView):
target = data["target"]
if target is None:
target = owner
- if (name != self.context.name or
- owner != self.context.owner or
- target != self.context.target):
+ if (
+ name != self.context.name
+ or owner != self.context.owner
+ or target != self.context.target
+ ):
namespace = get_git_namespace(target, owner)
try:
namespace.validateMove(self.context, self.user, name=name)
except GitRepositoryCreationForbidden:
self.addError(
- "%s is not allowed to own Git repositories in %s." %
- (owner.displayname, target.displayname))
+ "%s is not allowed to own Git repositories in %s."
+ % (owner.displayname, target.displayname)
+ )
except GitRepositoryExists as e:
self._setRepositoryExists(e.existing_repository)
except GitDefaultConflict as e:
self.addError(str(e))
- if (self.context.target_default and "target" in data and
- data["target"] != self.context.target):
+ if (
+ self.context.target_default
+ and "target" in data
+ and data["target"] != self.context.target
+ ):
self.setFieldError(
"target",
"The default repository for a target cannot be moved to "
- "another target.")
+ "another target.",
+ )
if "default_branch" in data:
default_branch = data["default_branch"]
- if (default_branch is not None and
- self.context.getRefByPath(default_branch) is None):
+ if (
+ default_branch is not None
+ and self.context.getRefByPath(default_branch) is None
+ ):
self.setFieldError(
"default_branch",
"This repository does not contain a reference named "
- "'%s'." % default_branch)
+ "'%s'." % default_branch,
+ )
@implementer(IBrowserPublisher)
@@ -911,14 +959,18 @@ def encode_form_field_id(value):
We use a modified version of base32 which fits into CSS identifiers and
so doesn't cause FormattersAPI.zope_css_id to do unhelpful things.
"""
- return base64.b32encode(
- value.encode("UTF-8")).decode("UTF-8").replace("=", "_")
+ return (
+ base64.b32encode(value.encode("UTF-8"))
+ .decode("UTF-8")
+ .replace("=", "_")
+ )
def decode_form_field_id(encoded):
"""Inverse of `encode_form_field_id`."""
- return base64.b32decode(
- encoded.replace("_", "=").encode("UTF-8")).decode("UTF-8")
+ return base64.b32decode(encoded.replace("_", "=").encode("UTF-8")).decode(
+ "UTF-8"
+ )
class GitRulePatternField(UniqueField):
@@ -942,8 +994,9 @@ class GitRulePatternField(UniqueField):
def unchanged(self, input):
"""See `UniqueField`."""
return (
- self.rule is not None and
- self.ref_prefix + input == self.rule.ref_pattern)
+ self.rule is not None
+ and self.ref_prefix + input == self.rule.ref_pattern
+ )
def set(self, object, value):
"""See `IField`."""
@@ -975,21 +1028,27 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
@property
def branch_rules(self):
return [
- rule for rule in self.rules
- if rule.ref_pattern.startswith(self.heads_prefix)]
+ rule
+ for rule in self.rules
+ if rule.ref_pattern.startswith(self.heads_prefix)
+ ]
@property
def tag_rules(self):
return [
- rule for rule in self.rules
- if rule.ref_pattern.startswith(self.tags_prefix)]
+ rule
+ for rule in self.rules
+ if rule.ref_pattern.startswith(self.tags_prefix)
+ ]
@property
def other_rules(self):
return [
- rule for rule in self.rules
- if not rule.ref_pattern.startswith(self.heads_prefix) and
- not rule.ref_pattern.startswith(self.tags_prefix)]
+ rule
+ for rule in self.rules
+ if not rule.ref_pattern.startswith(self.heads_prefix)
+ and not rule.ref_pattern.startswith(self.tags_prefix)
+ ]
def _getRuleGrants(self, rule):
def grantee_key(grant):
@@ -1004,7 +1063,7 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
"""Parse a pattern into a prefix and the displayed portion."""
for prefix in (self.heads_prefix, self.tags_prefix):
if ref_pattern.startswith(prefix):
- return prefix, ref_pattern[len(prefix):]
+ return prefix, ref_pattern[len(prefix) :]
return "", ref_pattern
def _getFieldName(self, name, ref_pattern, grantee=None):
@@ -1030,7 +1089,8 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
field_bits = field_name.split(".")
if len(field_bits) < 2:
raise UnexpectedFormData(
- "Cannot parse field name: %s" % field_name)
+ "Cannot parse field name: %s" % field_name
+ )
field_type = field_bits[0]
try:
ref_pattern = decode_form_field_id(field_bits[1])
@@ -1038,7 +1098,8 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
# but binascii.Error on Python 3.
except (TypeError, binascii.Error):
raise UnexpectedFormData(
- "Cannot parse field name: %s" % field_name)
+ "Cannot parse field name: %s" % field_name
+ )
if len(field_bits) > 2:
grantee_id = field_bits[2]
if grantee_id.startswith("_"):
@@ -1069,7 +1130,8 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
# This should never happen, because GitPermissionsVocabulary
# adds a custom term for the context grant if necessary.
raise AssertionError(
- "Could not find GitPermissions term for %r" % grant)
+ "Could not find GitPermissions term for %r" % grant
+ )
def setUpFields(self):
"""See `LaunchpadFormView`."""
@@ -1084,7 +1146,7 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
self.heads_prefix: "can_push",
self.tags_prefix: "can_create",
"": "can_push",
- }
+ }
for rule_index, rule in enumerate(self.rules):
# Remove the usual branch/tag prefixes from patterns. The full
@@ -1094,71 +1156,113 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
position_fields.append(
Int(
__name__=self._getFieldName("position", ref_pattern),
- required=True, readonly=False, default=rule_index + 1))
+ required=True,
+ readonly=False,
+ default=rule_index + 1,
+ )
+ )
pattern_fields.append(
GitRulePatternField(
__name__=self._getFieldName("pattern", ref_pattern),
- required=True, readonly=False, ref_prefix=ref_prefix,
- rule=rule, default=short_pattern))
+ required=True,
+ readonly=False,
+ ref_prefix=ref_prefix,
+ rule=rule,
+ default=short_pattern,
+ )
+ )
delete_fields.append(
Bool(
__name__=self._getFieldName("delete", ref_pattern),
- readonly=False, default=False))
+ readonly=False,
+ default=False,
+ )
+ )
for grant in self._getRuleGrants(rule):
grantee = grant.combined_grantee
readonly_grantee_fields.append(
GitGranteeField(
__name__=self._getFieldName(
- "grantee", ref_pattern, grantee),
- required=False, readonly=True, default=grantee,
- rule=rule))
+ "grantee", ref_pattern, grantee
+ ),
+ required=False,
+ readonly=True,
+ default=grantee,
+ rule=rule,
+ )
+ )
permissions_fields.append(
Choice(
__name__=self._getFieldName(
- "permissions", ref_pattern, grantee),
+ "permissions", ref_pattern, grantee
+ ),
source=GitPermissionsVocabulary(grant),
readonly=False,
- default=self._getPermissionsTerm(grant).value))
+ default=self._getPermissionsTerm(grant).value,
+ )
+ )
delete_fields.append(
Bool(
__name__=self._getFieldName(
- "delete", ref_pattern, grantee),
- readonly=False, default=False))
+ "delete", ref_pattern, grantee
+ ),
+ readonly=False,
+ default=False,
+ )
+ )
grantee_fields.append(
GitGranteeField(
__name__=self._getFieldName("grantee", ref_pattern),
- required=False, readonly=False, rule=rule))
+ required=False,
+ readonly=False,
+ rule=rule,
+ )
+ )
permissions_vocabulary = GitPermissionsVocabulary(rule)
permissions_fields.append(
Choice(
- __name__=self._getFieldName(
- "permissions", ref_pattern),
- source=permissions_vocabulary, readonly=False,
+ __name__=self._getFieldName("permissions", ref_pattern),
+ source=permissions_vocabulary,
+ readonly=False,
default=permissions_vocabulary.getTermByToken(
- default_permissions_by_prefix[ref_prefix]).value))
+ default_permissions_by_prefix[ref_prefix]
+ ).value,
+ )
+ )
for ref_prefix in (self.heads_prefix, self.tags_prefix):
position_fields.append(
Int(
__name__=self._getFieldName("new-position", ref_prefix),
- required=False, readonly=True))
+ required=False,
+ readonly=True,
+ )
+ )
pattern_fields.append(
GitRulePatternField(
__name__=self._getFieldName("new-pattern", ref_prefix),
- required=False, readonly=False, ref_prefix=ref_prefix))
+ required=False,
+ readonly=False,
+ ref_prefix=ref_prefix,
+ )
+ )
self.form_fields = (
form.FormFields(
*position_fields,
- custom_widget=CustomWidgetFactory(IntWidget, displayWidth=2)) +
- form.FormFields(*pattern_fields) +
- form.FormFields(*delete_fields) +
- form.FormFields(
+ custom_widget=CustomWidgetFactory(IntWidget, displayWidth=2),
+ )
+ + form.FormFields(*pattern_fields)
+ + form.FormFields(*delete_fields)
+ + form.FormFields(
*readonly_grantee_fields,
- custom_widget=CustomWidgetFactory(GitGranteeDisplayWidget)) +
- form.FormFields(
+ custom_widget=CustomWidgetFactory(GitGranteeDisplayWidget),
+ )
+ + form.FormFields(
*grantee_fields,
- custom_widget=CustomWidgetFactory(GitGranteeWidget)) +
- form.FormFields(*permissions_fields))
+ custom_widget=CustomWidgetFactory(GitGranteeWidget),
+ )
+ + form.FormFields(*permissions_fields)
+ )
def setUpWidgets(self, context=None):
"""See `LaunchpadFormView`."""
@@ -1174,63 +1278,75 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
def getRuleWidgets(self, rule):
widgets_by_name = {widget.name: widget for widget in self.widgets}
ref_pattern = rule.ref_pattern
- position_field_name = (
- "field." + self._getFieldName("position", ref_pattern))
- pattern_field_name = (
- "field." + self._getFieldName("pattern", ref_pattern))
- delete_field_name = (
- "field." + self._getFieldName("delete", ref_pattern))
+ position_field_name = "field." + self._getFieldName(
+ "position", ref_pattern
+ )
+ pattern_field_name = "field." + self._getFieldName(
+ "pattern", ref_pattern
+ )
+ delete_field_name = "field." + self._getFieldName(
+ "delete", ref_pattern
+ )
grant_widgets = []
for grant in self._getRuleGrants(rule):
grantee = grant.combined_grantee
- grantee_field_name = (
- "field." + self._getFieldName("grantee", ref_pattern, grantee))
- permissions_field_name = (
- "field." +
- self._getFieldName("permissions", ref_pattern, grantee))
- delete_grant_field_name = (
- "field." + self._getFieldName("delete", ref_pattern, grantee))
- grant_widgets.append({
- "grantee": widgets_by_name[grantee_field_name],
- "permissions": widgets_by_name[permissions_field_name],
- "delete": widgets_by_name[delete_grant_field_name],
- })
- new_grantee_field_name = (
- "field." + self._getFieldName("grantee", ref_pattern))
- new_permissions_field_name = (
- "field." + self._getFieldName("permissions", ref_pattern))
+ grantee_field_name = "field." + self._getFieldName(
+ "grantee", ref_pattern, grantee
+ )
+ permissions_field_name = "field." + self._getFieldName(
+ "permissions", ref_pattern, grantee
+ )
+ delete_grant_field_name = "field." + self._getFieldName(
+ "delete", ref_pattern, grantee
+ )
+ grant_widgets.append(
+ {
+ "grantee": widgets_by_name[grantee_field_name],
+ "permissions": widgets_by_name[permissions_field_name],
+ "delete": widgets_by_name[delete_grant_field_name],
+ }
+ )
+ new_grantee_field_name = "field." + self._getFieldName(
+ "grantee", ref_pattern
+ )
+ new_permissions_field_name = "field." + self._getFieldName(
+ "permissions", ref_pattern
+ )
new_grant_widgets = {
"grantee": widgets_by_name[new_grantee_field_name],
"permissions": widgets_by_name[new_permissions_field_name],
- }
+ }
return {
"position": widgets_by_name[position_field_name],
"pattern": widgets_by_name[pattern_field_name],
"delete": widgets_by_name.get(delete_field_name),
"grants": grant_widgets,
"new_grant": new_grant_widgets,
- }
+ }
def getNewRuleWidgets(self, ref_prefix):
widgets_by_name = {widget.name: widget for widget in self.widgets}
- new_position_field_name = (
- "field." + self._getFieldName("new-position", ref_prefix))
- new_pattern_field_name = (
- "field." + self._getFieldName("new-pattern", ref_prefix))
+ new_position_field_name = "field." + self._getFieldName(
+ "new-position", ref_prefix
+ )
+ new_pattern_field_name = "field." + self._getFieldName(
+ "new-pattern", ref_prefix
+ )
return {
"position": widgets_by_name[new_position_field_name],
"pattern": widgets_by_name[new_pattern_field_name],
- }
+ }
def parseData(self, data):
"""Rearrange form data to make it easier to process."""
parsed_data = {
"rules": {},
"grants": defaultdict(list),
- }
+ }
for field_name in sorted(
- name for name in data if name.split(".")[0] == "pattern"):
+ name for name in data if name.split(".")[0] == "pattern"
+ ):
_, ref_pattern, _ = self._parseFieldName(field_name)
prefix, _ = self._parseRefPattern(ref_pattern)
position_field_name = self._getFieldName("position", ref_pattern)
@@ -1241,15 +1357,19 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
else:
position = max(0, data[position_field_name] - 1)
action = "change"
- parsed_data["rules"].setdefault(ref_pattern, {
- "position": position,
- "pattern": ref_pattern,
- "new_pattern": prefix + data[field_name],
- "action": action,
- })
+ parsed_data["rules"].setdefault(
+ ref_pattern,
+ {
+ "position": position,
+ "pattern": ref_pattern,
+ "new_pattern": prefix + data[field_name],
+ "action": action,
+ },
+ )
for field_name in sorted(
- name for name in data if name.split(".")[0] == "new-pattern"):
+ name for name in data if name.split(".")[0] == "new-pattern"
+ ):
_, prefix, _ = self._parseFieldName(field_name)
position_field_name = self._getFieldName("position", prefix)
if not data[field_name]:
@@ -1258,18 +1378,23 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
position = max(0, data[position_field_name] - 1)
else:
position = None
- parsed_data["rules"].setdefault(prefix, {
- "position": position,
- "pattern": prefix + data[field_name],
- "action": "add",
- })
+ parsed_data["rules"].setdefault(
+ prefix,
+ {
+ "position": position,
+ "pattern": prefix + data[field_name],
+ "action": "add",
+ },
+ )
for field_name in sorted(
- name for name in data if name.split(".")[0] == "permissions"):
+ name for name in data if name.split(".")[0] == "permissions"
+ ):
_, ref_pattern, grantee = self._parseFieldName(field_name)
grantee_field_name = self._getFieldName("grantee", ref_pattern)
delete_field_name = self._getFieldName(
- "delete", ref_pattern, grantee)
+ "delete", ref_pattern, grantee
+ )
if grantee is None:
grantee = data.get(grantee_field_name)
if grantee is None:
@@ -1279,11 +1404,13 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
action = "delete"
else:
action = "change"
- parsed_data["grants"][ref_pattern].append({
- "grantee": grantee,
- "permissions": data[field_name],
- "action": action,
- })
+ parsed_data["grants"][ref_pattern].append(
+ {
+ "grantee": grantee,
+ "permissions": data[field_name],
+ "action": action,
+ }
+ )
return parsed_data
@@ -1293,7 +1420,8 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
rule_map = {rule.ref_pattern: rule for rule in self.repository.rules}
grant_map = {
(grant.rule.ref_pattern, grant.combined_grantee): grant
- for grant in self.repository.grants}
+ for grant in self.repository.grants
+ }
# Patterns must be processed in rule order so that position changes
# work in a reasonably natural way. Process new rules last.
@@ -1307,19 +1435,23 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
# already been deleted by somebody else.
ordered_rules.append((ref_pattern, parsed_rule, rule.position))
ordered_rules.sort(
- key=lambda item: (item[1]["action"] != "add", item[2], item[0]))
+ key=lambda item: (item[1]["action"] != "add", item[2], item[0])
+ )
for ref_pattern, parsed_rule, position in ordered_rules:
rule = rule_map.get(parsed_rule["pattern"])
action = parsed_rule["action"]
if action not in ("add", "change", "delete"):
raise AssertionError(
- "unknown action: %s" % parsed_rule["action"])
+ "unknown action: %s" % parsed_rule["action"]
+ )
if action == "add" and rule is None:
rule = repository.addRule(
- parsed_rule["pattern"], self.user,
- position=parsed_rule["position"])
+ parsed_rule["pattern"],
+ self.user,
+ position=parsed_rule["position"],
+ )
if ref_pattern == self.tags_prefix:
# Tags are a special case: on creation, they
# automatically get a grant of create permissions to
@@ -1327,11 +1459,14 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
# ability of the repository owner to push protected
# references).
rule.addGrant(
- GitGranteeType.REPOSITORY_OWNER, self.user,
- can_create=True)
+ GitGranteeType.REPOSITORY_OWNER,
+ self.user,
+ can_create=True,
+ )
elif action == "change" and rule is not None:
self.repository.moveRule(
- rule, parsed_rule["position"], self.user)
+ rule, parsed_rule["position"], self.user
+ )
if parsed_rule["new_pattern"] != rule.ref_pattern:
with notify_modified(rule, ["ref_pattern"]):
rule.ref_pattern = parsed_rule["new_pattern"]
@@ -1340,13 +1475,19 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
rule_map[parsed_rule["pattern"]] = None
else:
raise AssertionError(
- "unknown action: %s" % parsed_rule["action"])
+ "unknown action: %s" % parsed_rule["action"]
+ )
for ref_pattern, parsed_grants in sorted(
- parsed_data["grants"].items()):
+ parsed_data["grants"].items()
+ ):
if ref_pattern not in rule_map:
- self.addError(structured(
- "Cannot edit grants for nonexistent rule %s", ref_pattern))
+ self.addError(
+ structured(
+ "Cannot edit grants for nonexistent rule %s",
+ ref_pattern,
+ )
+ )
return
rule = rule_map.get(ref_pattern)
if rule is None:
@@ -1358,21 +1499,27 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
action = parsed_grant["action"]
if action not in ("add", "change", "delete"):
raise AssertionError(
- "unknown action: %s" % parsed_rule["action"])
+ "unknown action: %s" % parsed_rule["action"]
+ )
if action == "add" and grant is None:
rule.addGrant(
- parsed_grant["grantee"], self.user,
- permissions=parsed_grant["permissions"])
- elif (action in ("add", "change") and grant is not None and
- parsed_grant["permissions"] != grant.permissions):
+ parsed_grant["grantee"],
+ self.user,
+ permissions=parsed_grant["permissions"],
+ )
+ elif (
+ action in ("add", "change")
+ and grant is not None
+ and parsed_grant["permissions"] != grant.permissions
+ ):
# Make the requested changes. This can happen in the
# add case if somebody else added the grant since the
# form was last rendered, in which case updating it with
# the permissions from this request seems best.
with notify_modified(
- grant,
- ["can_create", "can_push", "can_force_push"]):
+ grant, ["can_create", "can_push", "can_force_push"]
+ ):
grant.permissions = parsed_grant["permissions"]
elif action == "delete" and grant is not None:
grant.destroySelf(self.user)
@@ -1385,7 +1532,8 @@ class GitRepositoryPermissionsView(LaunchpadFormView):
self.updateRepositoryFromData(self.repository, parsed_data)
self.request.response.addNotification(
- "Saved permissions for %s" % self.context.identity)
+ "Saved permissions for %s" % self.context.identity
+ )
self.next_url = canonical_url(self.context, view_name="+permissions")
@@ -1408,7 +1556,8 @@ class GitRepositoryDeletionView(LaunchpadFormView):
"""
reqs = []
for item, (operation, reason) in self.context.getDeletionRequirements(
- eager_load=True).items():
+ eager_load=True
+ ).items():
allowed = check_permission("launchpad.Edit", item)
reqs.append((item, operation, reason, allowed))
return reqs
@@ -1418,11 +1567,24 @@ class GitRepositoryDeletionView(LaunchpadFormView):
Uses display_deletion_requirements as its source data.
"""
- return len([item for item, action, reason, allowed in
- self.display_deletion_requirements if not allowed]) == 0
-
- @action("Delete", name="delete_repository",
- condition=lambda x, _: x.all_permitted())
+ return (
+ len(
+ [
+ item
+ for item, action, reason, allowed in (
+ self.display_deletion_requirements
+ )
+ if not allowed
+ ]
+ )
+ == 0
+ )
+
+ @action(
+ "Delete",
+ name="delete_repository",
+ condition=lambda x, _: x.all_permitted(),
+ )
def delete_repository_action(self, action, data):
repository = self.context
if self.all_permitted():
@@ -1434,7 +1596,8 @@ class GitRepositoryDeletionView(LaunchpadFormView):
self.request.response.addNotification(message)
else:
self.request.response.addNotification(
- "This repository cannot be deleted.")
+ "This repository cannot be deleted."
+ )
self.next_url = canonical_url(repository)
@property
@@ -1445,12 +1608,17 @@ class GitRepositoryDeletionView(LaunchpadFormView):
"item", "reason" and "allowed".
"""
row_dict = {"delete": [], "alter": []}
- for item, operation, reason, allowed in (
- self.display_deletion_requirements):
- row = {"item": item,
- "reason": reason,
- "allowed": allowed,
- }
+ for (
+ item,
+ operation,
+ reason,
+ allowed,
+ ) in self.display_deletion_requirements:
+ row = {
+ "item": item,
+ "reason": reason,
+ "allowed": allowed,
+ }
row_dict[operation].append(row)
return row_dict
@@ -1474,10 +1642,10 @@ class GitRepositoryActivityView(LaunchpadView):
def displayPermissions(self, values):
"""Assemble a human readable list from the permissions changes."""
permissions = []
- if values.get('can_create'):
- permissions.append('create')
- if values.get('can_push'):
- permissions.append('push')
- if values.get('can_force_push'):
- permissions.append('force-push')
- return ', '.join(permissions)
+ if values.get("can_create"):
+ permissions.append("create")
+ if values.get("can_push"):
+ permissions.append("push")
+ if values.get("can_force_push"):
+ permissions.append("force-push")
+ return ", ".join(permissions)
diff --git a/lib/lp/code/browser/gitsubscription.py b/lib/lp/code/browser/gitsubscription.py
index e4af854..c6f2b45 100644
--- a/lib/lp/code/browser/gitsubscription.py
+++ b/lib/lp/code/browser/gitsubscription.py
@@ -2,32 +2,29 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'GitRepositoryPortletSubscribersContent',
- 'GitSubscriptionAddOtherView',
- 'GitSubscriptionAddView',
- 'GitSubscriptionEditOwnView',
- 'GitSubscriptionEditView',
- ]
+ "GitRepositoryPortletSubscribersContent",
+ "GitSubscriptionAddOtherView",
+ "GitSubscriptionAddView",
+ "GitSubscriptionEditOwnView",
+ "GitSubscriptionEditView",
+]
from zope.component import getUtility
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.interfaces.services import IService
from lp.code.enums import BranchSubscriptionNotificationLevel
from lp.code.interfaces.gitsubscription import IGitSubscription
from lp.registry.interfaces.person import IPersonSet
-from lp.services.webapp import (
- canonical_url,
- LaunchpadView,
- )
+from lp.services.webapp import LaunchpadView, canonical_url
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.services.webapp.escaping import structured
@@ -42,31 +39,40 @@ class GitRepositoryPortletSubscribersContent(LaunchpadView):
# need the expense of running several complex SQL queries.
subscriptions = list(self.context.subscriptions)
person_ids = [sub.person_id for sub in subscriptions]
- list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
- person_ids, need_validity=True))
+ list(
+ getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ person_ids, need_validity=True
+ )
+ )
if self.user is not None:
subscribers = [
- subscription.person for subscription in subscriptions]
+ subscription.person for subscription in subscriptions
+ ]
precache_permission_for_objects(
- self.request, "launchpad.LimitedView", subscribers)
+ self.request, "launchpad.LimitedView", subscribers
+ )
visible_subscriptions = [
- subscription for subscription in subscriptions
- if check_permission("launchpad.LimitedView", subscription.person)]
+ subscription
+ for subscription in subscriptions
+ if check_permission("launchpad.LimitedView", subscription.person)
+ ]
return sorted(
visible_subscriptions,
- key=lambda subscription: subscription.person.displayname)
+ key=lambda subscription: subscription.person.displayname,
+ )
class _GitSubscriptionView(LaunchpadFormView):
"""Contains the common functionality of the Add and Edit views."""
schema = IGitSubscription
- field_names = ['notification_level', 'max_diff_lines', 'review_level']
+ field_names = ["notification_level", "max_diff_lines", "review_level"]
LEVELS_REQUIRING_LINES_SPECIFICATION = (
BranchSubscriptionNotificationLevel.DIFFSONLY,
- BranchSubscriptionNotificationLevel.FULL)
+ BranchSubscriptionNotificationLevel.FULL,
+ )
@property
def user_is_subscribed(self):
@@ -81,8 +87,9 @@ class _GitSubscriptionView(LaunchpadFormView):
cancel_url = next_url
- def add_notification_message(self, initial, notification_level,
- max_diff_lines, review_level):
+ def add_notification_message(
+ self, initial, notification_level, max_diff_lines, review_level
+ ):
if notification_level in self.LEVELS_REQUIRING_LINES_SPECIFICATION:
lines_message = "<li>%s</li>" % max_diff_lines.description
else:
@@ -90,8 +97,11 @@ class _GitSubscriptionView(LaunchpadFormView):
format_str = "%%s<ul><li>%%s</li>%s<li>%%s</li></ul>" % lines_message
message = structured(
- format_str, initial, notification_level.description,
- review_level.description)
+ format_str,
+ initial,
+ notification_level.description,
+ review_level.description,
+ )
self.request.response.addNotification(message)
def optional_max_diff_lines(self, notification_level, max_diff_lines):
@@ -111,24 +121,32 @@ class GitSubscriptionAddView(_GitSubscriptionView):
# subscribed before continuing.
if self.context.hasSubscription(self.user):
self.request.response.addNotification(
- "You are already subscribed to this repository.")
+ "You are already subscribed to this repository."
+ )
else:
notification_level = data["notification_level"]
max_diff_lines = self.optional_max_diff_lines(
- notification_level, data["max_diff_lines"])
+ notification_level, data["max_diff_lines"]
+ )
review_level = data["review_level"]
self.context.subscribe(
- self.user, notification_level, max_diff_lines, review_level,
- self.user)
+ self.user,
+ notification_level,
+ max_diff_lines,
+ review_level,
+ self.user,
+ )
self.add_notification_message(
"You have subscribed to this repository with: ",
- notification_level, max_diff_lines, review_level)
+ notification_level,
+ max_diff_lines,
+ review_level,
+ )
class GitSubscriptionEditOwnView(_GitSubscriptionView):
-
@property
def label(self):
return "Edit subscription to repository"
@@ -144,9 +162,11 @@ class GitSubscriptionEditOwnView(_GitSubscriptionView):
# This is the case of URL hacking or stale page.
return {}
else:
- return {"notification_level": subscription.notification_level,
- "max_diff_lines": subscription.max_diff_lines,
- "review_level": subscription.review_level}
+ return {
+ "notification_level": subscription.notification_level,
+ "max_diff_lines": subscription.max_diff_lines,
+ "review_level": subscription.review_level,
+ }
@action("Change")
def change_details(self, action, data):
@@ -155,18 +175,20 @@ class GitSubscriptionEditOwnView(_GitSubscriptionView):
subscription = self.context.getSubscription(self.user)
subscription.notification_level = data["notification_level"]
subscription.max_diff_lines = self.optional_max_diff_lines(
- subscription.notification_level,
- data["max_diff_lines"])
+ subscription.notification_level, data["max_diff_lines"]
+ )
subscription.review_level = data["review_level"]
self.add_notification_message(
"Subscription updated to: ",
subscription.notification_level,
subscription.max_diff_lines,
- subscription.review_level)
+ subscription.review_level,
+ )
else:
self.request.response.addNotification(
- "You are not subscribed to this repository.")
+ "You are not subscribed to this repository."
+ )
@action("Unsubscribe")
def unsubscribe(self, action, data):
@@ -174,17 +196,23 @@ class GitSubscriptionEditOwnView(_GitSubscriptionView):
if self.context.hasSubscription(self.user):
self.context.unsubscribe(self.user, self.user)
self.request.response.addNotification(
- "You have unsubscribed from this repository.")
+ "You have unsubscribed from this repository."
+ )
else:
self.request.response.addNotification(
- "You are not subscribed to this repository.")
+ "You are not subscribed to this repository."
+ )
class GitSubscriptionAddOtherView(_GitSubscriptionView):
"""View used to subscribe someone other than the current user."""
field_names = [
- "person", "notification_level", "max_diff_lines", "review_level"]
+ "person",
+ "notification_level",
+ "max_diff_lines",
+ "review_level",
+ ]
for_input = True
# Since we are subscribing other people, the current user
@@ -198,35 +226,47 @@ class GitSubscriptionAddOtherView(_GitSubscriptionView):
person = data["person"]
subscription = self.context.getSubscription(person)
if subscription is None and not self.context.userCanBeSubscribed(
- person):
+ person
+ ):
self.setFieldError(
"person",
"Open and delegated teams cannot be subscribed to "
- "private repositories.")
+ "private repositories.",
+ )
@action("Subscribe", name="subscribe_action")
def subscribe_action(self, action, data):
"""Subscribe the specified user to the repository."""
notification_level = data["notification_level"]
max_diff_lines = self.optional_max_diff_lines(
- notification_level, data["max_diff_lines"])
+ notification_level, data["max_diff_lines"]
+ )
review_level = data["review_level"]
person = data["person"]
subscription = self.context.getSubscription(person)
if subscription is None:
self.context.subscribe(
- person, notification_level, max_diff_lines, review_level,
- self.user)
+ person,
+ notification_level,
+ max_diff_lines,
+ review_level,
+ self.user,
+ )
self.add_notification_message(
"%s has been subscribed to this repository with: "
- % person.displayname, notification_level, max_diff_lines,
- review_level)
+ % person.displayname,
+ notification_level,
+ max_diff_lines,
+ review_level,
+ )
else:
self.add_notification_message(
"%s was already subscribed to this repository with: "
% person.displayname,
- subscription.notification_level, subscription.max_diff_lines,
- review_level)
+ subscription.notification_level,
+ subscription.max_diff_lines,
+ review_level,
+ )
class GitSubscriptionEditView(LaunchpadEditFormView):
@@ -236,18 +276,21 @@ class GitSubscriptionEditView(LaunchpadEditFormView):
through the repository action item to edit the user's own subscription.
This is the only current way to edit a team repository subscription.
"""
+
schema = IGitSubscription
field_names = ["notification_level", "max_diff_lines", "review_level"]
@property
def page_title(self):
return (
- "Edit subscription to repository %s" % self.repository.displayname)
+ "Edit subscription to repository %s" % self.repository.displayname
+ )
@property
def label(self):
return (
- "Edit subscription to repository for %s" % self.person.displayname)
+ "Edit subscription to repository for %s" % self.person.displayname
+ )
def initialize(self):
self.repository = self.context.repository
@@ -265,7 +308,8 @@ class GitSubscriptionEditView(LaunchpadEditFormView):
self.repository.unsubscribe(self.person, self.user)
self.request.response.addNotification(
"%s has been unsubscribed from this repository."
- % self.person.displayname)
+ % self.person.displayname
+ )
@property
def next_url(self):
@@ -274,8 +318,10 @@ class GitSubscriptionEditView(LaunchpadEditFormView):
# away.
service = getUtility(IService, "sharing")
repositories = service.getVisibleArtifacts(
- self.person, gitrepositories=[self.repository],
- ignore_permissions=True)["gitrepositories"]
+ self.person,
+ gitrepositories=[self.repository],
+ ignore_permissions=True,
+ )["gitrepositories"]
if not repositories:
url = canonical_url(self.repository.target)
return url
diff --git a/lib/lp/code/browser/revisionstatus.py b/lib/lp/code/browser/revisionstatus.py
index 39574eb..5d5a839 100644
--- a/lib/lp/code/browser/revisionstatus.py
+++ b/lib/lp/code/browser/revisionstatus.py
@@ -15,7 +15,6 @@ class RevisionStatusArtifactNavigation(Navigation, FileNavigationMixin):
class HasRevisionStatusReportsMixin:
-
def getStatusReports(self, commit_sha1):
reports = self.context.getStatusReports(commit_sha1)
return BatchNavigator(reports, self.request)
@@ -24,25 +23,26 @@ class HasRevisionStatusReportsMixin:
"""Show an appropriate icon at the top of the report."""
icon_template = (
'<img width="14" height="14" alt="%(title)s" '
- 'title="%(title)s" src="%(src)s" />')
+ 'title="%(title)s" src="%(src)s" />'
+ )
reports = repository.getStatusReports(commit_sha1)
if all(
- report.result == RevisionStatusResult.SKIPPED
- for report in reports):
+ report.result == RevisionStatusResult.SKIPPED for report in reports
+ ):
title = "Skipped"
source = "/@@/yes-gray"
elif all(
- report.result in (
- RevisionStatusResult.SUCCEEDED,
- RevisionStatusResult.SKIPPED)
- for report in reports):
+ report.result
+ in (RevisionStatusResult.SUCCEEDED, RevisionStatusResult.SKIPPED)
+ for report in reports
+ ):
title = "Succeeded"
source = "/@@/yes"
elif any(
- report.result in (
- RevisionStatusResult.FAILED,
- RevisionStatusResult.CANCELLED)
- for report in reports):
+ report.result
+ in (RevisionStatusResult.FAILED, RevisionStatusResult.CANCELLED)
+ for report in reports
+ ):
title = "Failed"
source = "/@@/no"
else:
diff --git a/lib/lp/code/browser/sourcepackagerecipe.py b/lib/lp/code/browser/sourcepackagerecipe.py
index eed6d6f..89fa7dc 100644
--- a/lib/lp/code/browser/sourcepackagerecipe.py
+++ b/lib/lp/code/browser/sourcepackagerecipe.py
@@ -4,32 +4,29 @@
"""SourcePackageRecipe views."""
__all__ = [
- 'SourcePackageRecipeAddView',
- 'SourcePackageRecipeContextMenu',
- 'SourcePackageRecipeEditView',
- 'SourcePackageRecipeNavigationMenu',
- 'SourcePackageRecipeRequestBuildsView',
- 'SourcePackageRecipeRequestDailyBuildView',
- 'SourcePackageRecipeView',
- ]
+ "SourcePackageRecipeAddView",
+ "SourcePackageRecipeContextMenu",
+ "SourcePackageRecipeEditView",
+ "SourcePackageRecipeNavigationMenu",
+ "SourcePackageRecipeRequestBuildsView",
+ "SourcePackageRecipeRequestDailyBuildView",
+ "SourcePackageRecipeView",
+]
import itertools
+import simplejson
from breezy.plugins.builder.recipe import (
ForbiddenInstructionError,
RecipeParseError,
- )
+)
from lazr.lifecycle.event import ObjectModifiedEvent
from lazr.lifecycle.snapshot import Snapshot
-from lazr.restful.interface import (
- copy_field,
- use_template,
- )
+from lazr.restful.interface import copy_field, use_template
from lazr.restful.interfaces import (
IFieldHTMLRenderer,
IWebServiceClientRequest,
- )
-import simplejson
+)
from storm.locals import Store
from zope import component
from zope.browserpage import ViewPageTemplateFile
@@ -37,47 +34,34 @@ from zope.component import getUtility
from zope.event import notify
from zope.formlib import form
from zope.formlib.widget import Widget
-from zope.interface import (
- implementer,
- Interface,
- providedBy,
- )
+from zope.interface import Interface, implementer, providedBy
from zope.publisher.interfaces import IView
-from zope.schema import (
- Choice,
- Field,
- List,
- Text,
- TextLine,
- )
+from zope.schema import Choice, Field, List, Text, TextLine
from zope.schema.interfaces import ICollection
-from zope.schema.vocabulary import (
- SimpleTerm,
- SimpleVocabulary,
- )
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from lp import _
from lp.app.browser.launchpadform import (
- action,
- has_structured_doc,
LaunchpadEditFormView,
LaunchpadFormView,
+ action,
+ has_structured_doc,
render_radio_widget_part,
- )
+)
from lp.app.browser.lazrjs import (
BooleanChoiceWidget,
InlineEditPickerWidget,
InlinePersonEditPickerWidget,
TextAreaEditorWidget,
TextLineEditorWidget,
- )
+)
from lp.app.browser.tales import format_link
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.validators.name import name_validator
from lp.app.widgets.itemswidgets import (
LabeledMultiCheckBoxWidget,
LaunchpadRadioWidget,
- )
+)
from lp.app.widgets.suggestion import RecipeOwnerWidget
from lp.code.errors import (
BuildAlreadyPending,
@@ -86,49 +70,48 @@ from lp.code.errors import (
PrivateBranchRecipe,
PrivateGitRepositoryRecipe,
TooNewRecipeFormat,
- )
+)
from lp.code.interfaces.branch import IBranch
from lp.code.interfaces.branchtarget import IBranchTarget
from lp.code.interfaces.gitref import IGitRef
from lp.code.interfaces.gitrepository import IGitRepository
from lp.code.interfaces.sourcepackagerecipe import (
+ MINIMAL_RECIPE_TEXT_BZR,
+ MINIMAL_RECIPE_TEXT_GIT,
IRecipeBranchSource,
ISourcePackageRecipe,
ISourcePackageRecipeSource,
- MINIMAL_RECIPE_TEXT_BZR,
- MINIMAL_RECIPE_TEXT_GIT,
- )
+)
from lp.code.vocabularies.sourcepackagerecipe import BuildableDistroSeries
from lp.registry.interfaces.series import SeriesStatus
from lp.services.fields import PersonChoice
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
NavigationMenu,
+ canonical_url,
+ enabled_with_permission,
structured,
- )
+)
from lp.services.webapp.authorization import check_permission
-from lp.services.webapp.breadcrumb import (
- Breadcrumb,
- NameBreadcrumb,
- )
+from lp.services.webapp.breadcrumb import Breadcrumb, NameBreadcrumb
from lp.soyuz.interfaces.archive import ArchiveDisabled
from lp.soyuz.model.archive import validate_ppa
class SourcePackageRecipeBreadcrumb(NameBreadcrumb):
-
@property
def inside(self):
return Breadcrumb(
self.context.owner,
url=canonical_url(
- self.context.owner, view_name="+recipes", rootsite="code"),
- text="Recipes", inside=self.context.owner)
+ self.context.owner, view_name="+recipes", rootsite="code"
+ ),
+ text="Recipes",
+ inside=self.context.owner,
+ )
class SourcePackageRecipeNavigationMenu(NavigationMenu):
@@ -136,17 +119,17 @@ class SourcePackageRecipeNavigationMenu(NavigationMenu):
usedfor = ISourcePackageRecipe
- facet = 'branches'
+ facet = "branches"
- links = ('edit', 'delete')
+ links = ("edit", "delete")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
- return Link('+edit', 'Edit recipe', icon='edit')
+ return Link("+edit", "Edit recipe", icon="edit")
- @enabled_with_permission('launchpad.Delete')
+ @enabled_with_permission("launchpad.Delete")
def delete(self):
- return Link('+delete', 'Delete recipe', icon='trash-icon')
+ return Link("+delete", "Delete recipe", icon="trash-icon")
class SourcePackageRecipeContextMenu(ContextMenu):
@@ -154,30 +137,39 @@ class SourcePackageRecipeContextMenu(ContextMenu):
usedfor = ISourcePackageRecipe
- facet = 'branches'
+ facet = "branches"
- links = ('request_builds', 'request_daily_build',)
+ links = (
+ "request_builds",
+ "request_daily_build",
+ )
def request_builds(self):
"""Provide a link for requesting builds of a recipe."""
- return Link('+request-builds', 'Request build(s)', icon='add')
+ return Link("+request-builds", "Request build(s)", icon="add")
def request_daily_build(self):
"""Provide a link for requesting a daily build of a recipe."""
recipe = self.context
ppa = recipe.daily_build_archive
- if (ppa is None or not ppa.enabled or not recipe.build_daily or not
- recipe.is_stale or not recipe.distroseries):
+ if (
+ ppa is None
+ or not ppa.enabled
+ or not recipe.build_daily
+ or not recipe.is_stale
+ or not recipe.distroseries
+ ):
show_request_build = False
else:
has_upload = ppa.checkArchivePermission(recipe.owner)
show_request_build = has_upload
- show_request_build = (show_request_build and
- check_permission('launchpad.Edit', recipe))
+ show_request_build = show_request_build and check_permission(
+ "launchpad.Edit", recipe
+ )
return Link(
- '+request-daily-build', 'Build now',
- enabled=show_request_build)
+ "+request-daily-build", "Build now", enabled=show_request_build
+ )
class SourcePackageRecipeView(LaunchpadView):
@@ -190,22 +182,29 @@ class SourcePackageRecipeView(LaunchpadView):
self.request.response.addWarningNotification(
structured(
"Daily builds for this recipe will <strong>not</strong> "
- "occur.<br/><br/>There is no PPA."))
+ "occur.<br/><br/>There is no PPA."
+ )
+ )
elif self.dailyBuildWithoutUploadPermission():
self.request.response.addWarningNotification(
structured(
"Daily builds for this recipe will <strong>not</strong> "
"occur.<br/><br/>The owner of the recipe (%s) does not "
"have permission to upload packages into the daily "
- "build PPA (%s)" % (
+ "build PPA (%s)"
+ % (
format_link(recipe.owner),
- format_link(recipe.daily_build_archive))))
+ format_link(recipe.daily_build_archive),
+ )
+ )
+ )
@property
def page_title(self):
- return "%(name)s\'s %(recipe_name)s recipe" % {
- 'name': self.context.owner.displayname,
- 'recipe_name': self.context.name}
+ return "%(name)s's %(recipe_name)s recipe" % {
+ "name": self.context.owner.displayname,
+ "recipe_name": self.context.name,
+ }
label = page_title
@@ -228,116 +227,130 @@ class SourcePackageRecipeView(LaunchpadView):
@property
def person_picker(self):
field = copy_field(
- ISourcePackageRecipe['owner'],
- vocabularyName='UserTeamsParticipationPlusSelfSimpleDisplay')
+ ISourcePackageRecipe["owner"],
+ vocabularyName="UserTeamsParticipationPlusSelfSimpleDisplay",
+ )
return InlinePersonEditPickerWidget(
- self.context, field,
+ self.context,
+ field,
format_link(self.context.owner),
- header='Change owner',
- step_title='Select a new owner')
+ header="Change owner",
+ step_title="Select a new owner",
+ )
@property
def archive_picker(self):
- field = ISourcePackageEditSchema['daily_build_archive']
+ field = ISourcePackageEditSchema["daily_build_archive"]
return InlineEditPickerWidget(
- self.context, field,
+ self.context,
+ field,
format_link(self.context.daily_build_archive),
- header='Change daily build archive',
- step_title='Select a PPA')
+ header="Change daily build archive",
+ step_title="Select a PPA",
+ )
@property
def recipe_text_widget(self):
"""The recipe text as widget HTML."""
- recipe_text = ISourcePackageRecipe['recipe_text']
+ recipe_text = ISourcePackageRecipe["recipe_text"]
return TextAreaEditorWidget(self.context, recipe_text, title="")
@property
def daily_build_widget(self):
return BooleanChoiceWidget(
- self.context, ISourcePackageRecipe['build_daily'],
- tag='span',
- false_text='Built on request',
- true_text='Built daily',
- header='Change build schedule')
+ self.context,
+ ISourcePackageRecipe["build_daily"],
+ tag="span",
+ false_text="Built on request",
+ true_text="Built daily",
+ header="Change build schedule",
+ )
@property
def description_widget(self):
"""The description as a widget."""
- description = ISourcePackageRecipe['description']
- return TextAreaEditorWidget(
- self.context, description, title="")
+ description = ISourcePackageRecipe["description"]
+ return TextAreaEditorWidget(self.context, description, title="")
@property
def name_widget(self):
- name = ISourcePackageRecipe['name']
+ name = ISourcePackageRecipe["name"]
title = "Edit the recipe name"
return TextLineEditorWidget(
- self.context, name, title, 'h1', max_width='95%',
- truncate_lines=1)
+ self.context, name, title, "h1", max_width="95%", truncate_lines=1
+ )
@property
def distroseries_widget(self):
from lp.app.browser.lazrjs import InlineMultiCheckboxWidget
- field = ISourcePackageEditSchema['distroseries']
+
+ field = ISourcePackageEditSchema["distroseries"]
return InlineMultiCheckboxWidget(
self.context,
field,
attribute_type="reference",
- vocabulary='BuildableDistroSeries',
+ vocabulary="BuildableDistroSeries",
label="Distribution series:",
label_tag="dt",
header="Change default distribution series:",
empty_display_value="None",
selected_items=sorted(
- self.context.distroseries, key=lambda ds: ds.displayname),
+ self.context.distroseries, key=lambda ds: ds.displayname
+ ),
items_tag="dd",
- )
+ )
-@component.adapter(ISourcePackageRecipe, ICollection,
- IWebServiceClientRequest)
+@component.adapter(ISourcePackageRecipe, ICollection, IWebServiceClientRequest)
@implementer(IFieldHTMLRenderer)
def distroseries_renderer(context, field, request):
"""Render a distroseries collection as a set of links."""
def render(value):
distroseries = sorted(
- context.distroseries, key=lambda ds: ds.displayname)
+ context.distroseries, key=lambda ds: ds.displayname
+ )
if not distroseries:
- return 'None'
+ return "None"
html = "<ul>"
- html += ''.join(
- ["<li>%s</li>" % format_link(series) for series in distroseries])
+ html += "".join(
+ ["<li>%s</li>" % format_link(series) for series in distroseries]
+ )
html += "</ul>"
return html
+
return render
def builds_for_recipe(recipe):
- """A list of interesting builds.
+ """A list of interesting builds.
- All pending builds are shown, as well as 1-5 recent builds.
- Recent builds are ordered by date finished (if completed) or
- date_started (if date finished is not set due to an error building or
- other circumstance which resulted in the build not being completed).
- This allows started but unfinished builds to show up in the view but
- be discarded as more recent builds become available.
+ All pending builds are shown, as well as 1-5 recent builds.
+ Recent builds are ordered by date finished (if completed) or
+ date_started (if date finished is not set due to an error building or
+ other circumstance which resulted in the build not being completed).
+ This allows started but unfinished builds to show up in the view but
+ be discarded as more recent builds become available.
- Builds that the user does not have permission to see are excluded.
- """
- builds = [build for build in recipe.pending_builds
- if check_permission('launchpad.View', build)]
- for build in recipe.completed_builds:
- if not check_permission('launchpad.View', build):
- continue
- builds.append(build)
- if len(builds) >= 5:
- break
- return builds
-
-
-def new_builds_notification_text(builds, already_pending=None,
- contains_unbuildable=False):
+ Builds that the user does not have permission to see are excluded.
+ """
+ builds = [
+ build
+ for build in recipe.pending_builds
+ if check_permission("launchpad.View", build)
+ ]
+ for build in recipe.completed_builds:
+ if not check_permission("launchpad.View", build):
+ continue
+ builds.append(build)
+ if len(builds) >= 5:
+ break
+ return builds
+
+
+def new_builds_notification_text(
+ builds, already_pending=None, contains_unbuildable=False
+):
nr_builds = len(builds)
if not nr_builds:
builds_text = "All requested recipe builds are already queued."
@@ -348,8 +361,10 @@ def new_builds_notification_text(builds, already_pending=None,
if nr_builds > 0 and already_pending:
builds_text = "<p>%s</p>%s" % (builds_text, already_pending)
if contains_unbuildable:
- builds_text = ("%s<p>The recipe contains an obsolete distroseries, "
- "which has been skipped.</p>" % builds_text)
+ builds_text = (
+ "%s<p>The recipe contains an obsolete distroseries, "
+ "which has been skipped.</p>" % builds_text
+ )
return structured(builds_text)
@@ -362,32 +377,39 @@ class SourcePackageRecipeRequestBuildsView(LaunchpadFormView):
The distroseries function as defaults for requesting a build.
"""
- initial_values = {'distroseries': self.context.distroseries}
+ initial_values = {"distroseries": self.context.distroseries}
if self.context.daily_build_archive and check_permission(
- 'launchpad.Append', self.context.daily_build_archive):
- initial_values['archive'] = self.context.daily_build_archive
+ "launchpad.Append", self.context.daily_build_archive
+ ):
+ initial_values["archive"] = self.context.daily_build_archive
return initial_values
class schema(Interface):
"""Schema for requesting a build."""
+
archive = Choice(
- vocabulary='TargetPPAs', title='Archive', required=False)
+ vocabulary="TargetPPAs", title="Archive", required=False
+ )
distroseries = List(
- Choice(vocabulary='BuildableDistroSeries'),
- title='Distribution series')
+ Choice(vocabulary="BuildableDistroSeries"),
+ title="Distribution series",
+ )
custom_widget_distroseries = LabeledMultiCheckBoxWidget
def validate(self, data):
- if not data['archive']:
+ if not data["archive"]:
self.setFieldError(
- 'archive', "You must specify the archive to build into.")
+ "archive", "You must specify the archive to build into."
+ )
return
- distros = data.get('distroseries', [])
+ distros = data.get("distroseries", [])
if not len(distros):
- self.setFieldError('distroseries',
+ self.setFieldError(
+ "distroseries",
"You need to specify at least one distro series for which "
- "to build.")
+ "to build.",
+ )
return
def requestBuild(self, data):
@@ -399,31 +421,36 @@ class SourcePackageRecipeRequestBuildsView(LaunchpadFormView):
"""
informational = {}
builds = []
- for distroseries in data['distroseries']:
+ for distroseries in data["distroseries"]:
try:
build = self.context.requestBuild(
- data['archive'], self.user, distroseries, manual=True)
+ data["archive"], self.user, distroseries, manual=True
+ )
builds.append(build)
except BuildAlreadyPending as e:
existing_message = informational.get("already_pending")
if existing_message:
new_message = existing_message[:-1] + (
- ", and %s." % e.distroseries)
+ ", and %s." % e.distroseries
+ )
else:
- new_message = ("An identical build is "
- "already pending for %s." % e.distroseries)
+ new_message = (
+ "An identical build is "
+ "already pending for %s." % e.distroseries
+ )
informational["already_pending"] = new_message
return builds, informational
class SourcePackageRecipeRequestBuildsHtmlView(
- SourcePackageRecipeRequestBuildsView):
+ SourcePackageRecipeRequestBuildsView
+):
"""Supports HTML form recipe build requests."""
@property
def title(self):
- return 'Request builds for %s' % self.context.name
+ return "Request builds for %s" % self.context.name
label = title
@@ -431,25 +458,33 @@ class SourcePackageRecipeRequestBuildsHtmlView(
def cancel_url(self):
return canonical_url(self.context)
- @action('Request builds', name='request')
+ @action("Request builds", name="request")
def request_action(self, action, data):
builds, informational = self.requestBuild(data)
self.next_url = self.cancel_url
already_pending = informational.get("already_pending")
notification_text = new_builds_notification_text(
- builds, already_pending)
+ builds, already_pending
+ )
self.request.response.addNotification(notification_text)
class SourcePackageRecipeRequestBuildsAjaxView(
- SourcePackageRecipeRequestBuildsView):
+ SourcePackageRecipeRequestBuildsView
+):
"""Supports AJAX form recipe build requests."""
- def _process_error(self, data=None, builds=None, informational=None,
- errors=None, reason="Validation"):
+ def _process_error(
+ self,
+ data=None,
+ builds=None,
+ informational=None,
+ errors=None,
+ reason="Validation",
+ ):
"""Set up the response and json data to return to the caller."""
self.request.response.setStatus(200, reason)
- self.request.response.setHeader('Content-type', 'application/json')
+ self.request.response.setHeader("Content-type", "application/json")
return_data = dict(builds=builds, errors=errors)
if informational:
return_data.update(informational)
@@ -458,12 +493,12 @@ class SourcePackageRecipeRequestBuildsAjaxView(
def failure(self, action, data, errors):
"""Called by the form if validate() finds any errors.
- We simply convert the errors to json and return that data to the
- caller for display to the user.
+ We simply convert the errors to json and return that data to the
+ caller for display to the user.
"""
return self._process_error(data=data, errors=self.widget_errors)
- @action('Request builds', name='request', failure=failure)
+ @action("Request builds", name="request", failure=failure)
def request_action(self, action, data):
"""User action for requesting a number of builds.
@@ -484,8 +519,11 @@ class SourcePackageRecipeRequestBuildsAjaxView(
if len(builds):
builds_html = self.render()
return self._process_error(
- data=data, builds=builds_html, informational=informational,
- reason="Request Build")
+ data=data,
+ builds=builds_html,
+ informational=informational,
+ reason="Request Build",
+ )
@property
def builds(self):
@@ -506,13 +544,13 @@ class SourcePackageRecipeRequestDailyBuildView(LaunchpadFormView):
def initialize(self):
super().initialize()
- if self.request.method == 'GET':
+ if self.request.method == "GET":
self.request.response.redirect(canonical_url(self.context))
class schema(Interface):
"""Schema for requesting a build."""
- @action('Build now', name='build')
+ @action("Build now", name="build")
def build_action(self, action, data):
recipe = self.context
try:
@@ -524,15 +562,19 @@ class SourcePackageRecipeRequestDailyBuildView(LaunchpadFormView):
if self.request.is_ajax:
template = ViewPageTemplateFile(
- "../templates/sourcepackagerecipe-builds.pt")
+ "../templates/sourcepackagerecipe-builds.pt"
+ )
return template(self)
else:
contains_unbuildable = recipe.containsUnbuildableSeries(
- recipe.daily_build_archive)
+ recipe.daily_build_archive
+ )
self.next_url = canonical_url(recipe)
self.request.response.addNotification(
new_builds_notification_text(
- builds, contains_unbuildable=contains_unbuildable))
+ builds, contains_unbuildable=contains_unbuildable
+ )
+ )
@property
def builds(self):
@@ -542,60 +584,81 @@ class SourcePackageRecipeRequestDailyBuildView(LaunchpadFormView):
class ISourcePackageEditSchema(Interface):
"""Schema for adding or editing a recipe."""
- use_template(ISourcePackageRecipe, include=[
- 'name',
- 'description',
- 'owner',
- 'build_daily',
- 'distroseries',
- ])
- daily_build_archive = Choice(vocabulary='TargetPPAs',
- title='Daily build archive',
+ use_template(
+ ISourcePackageRecipe,
+ include=[
+ "name",
+ "description",
+ "owner",
+ "build_daily",
+ "distroseries",
+ ],
+ )
+ daily_build_archive = Choice(
+ vocabulary="TargetPPAs",
+ title="Daily build archive",
description=(
- 'If built daily, this is the archive where the package '
- 'will be uploaded.'))
+ "If built daily, this is the archive where the package "
+ "will be uploaded."
+ ),
+ )
recipe_text = has_structured_doc(
Text(
- title='Recipe text', required=True,
+ title="Recipe text",
+ required=True,
description="""The text of the recipe.
<a href="/+help-code/recipe-syntax.html" target="help"
>Syntax help
<span class="sprite maybe action-icon">
Help
</span></a>
- """))
+ """,
+ )
+ )
-EXISTING_PPA = 'existing-ppa'
-CREATE_NEW = 'create-new'
+EXISTING_PPA = "existing-ppa"
+CREATE_NEW = "create-new"
-USE_ARCHIVE_VOCABULARY = SimpleVocabulary((
- SimpleTerm(EXISTING_PPA, EXISTING_PPA, _("Use an existing PPA")),
- SimpleTerm(
- CREATE_NEW, CREATE_NEW, _("Create a new PPA for this recipe")),
- ))
+USE_ARCHIVE_VOCABULARY = SimpleVocabulary(
+ (
+ SimpleTerm(EXISTING_PPA, EXISTING_PPA, _("Use an existing PPA")),
+ SimpleTerm(
+ CREATE_NEW, CREATE_NEW, _("Create a new PPA for this recipe")
+ ),
+ )
+)
class ISourcePackageAddSchema(ISourcePackageEditSchema):
- daily_build_archive = Choice(vocabulary='TargetPPAs',
- title='Daily build archive', required=False,
+ daily_build_archive = Choice(
+ vocabulary="TargetPPAs",
+ title="Daily build archive",
+ required=False,
description=(
- 'If built daily, this is the archive where the package '
- 'will be uploaded.'))
+ "If built daily, this is the archive where the package "
+ "will be uploaded."
+ ),
+ )
use_ppa = Choice(
- title=_('Which PPA'),
+ title=_("Which PPA"),
vocabulary=USE_ARCHIVE_VOCABULARY,
description=_("Which PPA to use..."),
- required=True)
+ required=True,
+ )
ppa_name = TextLine(
- title=_("New PPA name"), required=False,
- constraint=name_validator,
- description=_("A new PPA with this name will be created for "
- "the owner of the recipe ."))
+ title=_("New PPA name"),
+ required=False,
+ constraint=name_validator,
+ description=_(
+ "A new PPA with this name will be created for "
+ "the owner of the recipe ."
+ ),
+ )
class ErrorHandled(Exception):
@@ -606,15 +669,17 @@ class RecipeTextValidatorMixin:
"""Class to validate that the Source Package Recipe text is valid."""
def validate(self, data):
- if data['build_daily']:
- if len(data['distroseries']) == 0:
+ if data["build_daily"]:
+ if len(data["distroseries"]) == 0:
self.setFieldError(
- 'distroseries',
- 'You must specify at least one series for daily builds.')
+ "distroseries",
+ "You must specify at least one series for daily builds.",
+ )
try:
self.error_handler(
getUtility(IRecipeBranchSource).getParsedRecipe,
- data['recipe_text'])
+ data["recipe_text"],
+ )
except ErrorHandled:
pass
@@ -623,23 +688,30 @@ class RecipeTextValidatorMixin:
return callable(*args)
except TooNewRecipeFormat:
self.setFieldError(
- 'recipe_text',
- 'The recipe format version specified is not available.')
+ "recipe_text",
+ "The recipe format version specified is not available.",
+ )
except ForbiddenInstructionError as e:
self.setFieldError(
- 'recipe_text',
- 'The recipe instruction "%s" is not permitted here.' %
- e.instruction_name)
+ "recipe_text",
+ 'The recipe instruction "%s" is not permitted here.'
+ % e.instruction_name,
+ )
except NoSuchBranch as e:
self.setFieldError(
- 'recipe_text', '%s is not a branch on Launchpad.' % e.name)
+ "recipe_text", "%s is not a branch on Launchpad." % e.name
+ )
except NoSuchGitRepository as e:
self.setFieldError(
- 'recipe_text',
- '%s is not a Git repository on Launchpad.' % e.name)
- except (RecipeParseError, PrivateBranchRecipe,
- PrivateGitRepositoryRecipe) as e:
- self.setFieldError('recipe_text', str(e))
+ "recipe_text",
+ "%s is not a Git repository on Launchpad." % e.name,
+ )
+ except (
+ RecipeParseError,
+ PrivateBranchRecipe,
+ PrivateGitRepositoryRecipe,
+ ) as e:
+ self.setFieldError("recipe_text", str(e))
raise ErrorHandled()
@@ -648,7 +720,8 @@ class RelatedBranchesWidget(Widget):
"""A widget to render the related branches for a recipe."""
__call__ = ViewPageTemplateFile(
- '../templates/sourcepackagerecipe-related-branches.pt')
+ "../templates/sourcepackagerecipe-related-branches.pt"
+ )
related_package_branch_info = []
related_series_branch_info = []
@@ -657,9 +730,8 @@ class RelatedBranchesWidget(Widget):
return True
def setRenderedValue(self, value):
- self.related_package_branch_info = (
- value['related_package_branch_info'])
- self.related_series_branch_info = value['related_series_branch_info']
+ self.related_package_branch_info = value["related_package_branch_info"]
+ self.related_series_branch_info = value["related_series_branch_info"]
class RecipeRelatedBranchesMixin(LaunchpadFormView):
@@ -672,16 +744,19 @@ class RecipeRelatedBranchesMixin(LaunchpadFormView):
Adds a related branches field to the form.
"""
- self.form_fields += form.Fields(Field(__name__='related_branches'))
- self.widget_errors['related_branches'] = ''
+ self.form_fields += form.Fields(Field(__name__="related_branches"))
+ self.widget_errors["related_branches"] = ""
def setUpWidgets(self, context=None):
# Adds a new related branches widget.
super().setUpWidgets(context)
- self.widgets['related_branches'].display_label = False
- self.widgets['related_branches'].setRenderedValue(dict(
- related_package_branch_info=self.related_package_branch_info,
- related_series_branch_info=self.related_series_branch_info))
+ self.widgets["related_branches"].display_label = False
+ self.widgets["related_branches"].setRenderedValue(
+ dict(
+ related_package_branch_info=self.related_package_branch_info,
+ related_series_branch_info=self.related_series_branch_info,
+ )
+ )
@cachedproperty
def related_series_branch_info(self):
@@ -689,7 +764,8 @@ class RecipeRelatedBranchesMixin(LaunchpadFormView):
if IBranch.providedBy(branch_to_check):
branch_target = IBranchTarget(branch_to_check.target)
return branch_target.getRelatedSeriesBranchInfo(
- branch_to_check, limit_results=5)
+ branch_to_check, limit_results=5
+ )
@cachedproperty
def related_package_branch_info(self):
@@ -697,14 +773,16 @@ class RecipeRelatedBranchesMixin(LaunchpadFormView):
if IBranch.providedBy(branch_to_check):
branch_target = IBranchTarget(branch_to_check.target)
return branch_target.getRelatedPackageBranchInfo(
- branch_to_check, limit_results=5)
+ branch_to_check, limit_results=5
+ )
-class SourcePackageRecipeAddView(RecipeRelatedBranchesMixin,
- RecipeTextValidatorMixin, LaunchpadFormView):
+class SourcePackageRecipeAddView(
+ RecipeRelatedBranchesMixin, RecipeTextValidatorMixin, LaunchpadFormView
+):
"""View for creating Source Package Recipes."""
- title = label = 'Create a new source package recipe'
+ title = label = "Create a new source package recipe"
schema = ISourcePackageAddSchema
custom_widget_distroseries = LabeledMultiCheckBoxWidget
@@ -713,16 +791,18 @@ class SourcePackageRecipeAddView(RecipeRelatedBranchesMixin,
def initialize(self):
super().initialize()
- widget = self.widgets['use_ppa']
+ widget = self.widgets["use_ppa"]
current_value = widget._getFormValue()
self.use_ppa_existing = render_radio_widget_part(
- widget, EXISTING_PPA, current_value)
+ widget, EXISTING_PPA, current_value
+ )
self.use_ppa_new = render_radio_widget_part(
- widget, CREATE_NEW, current_value)
- archive_widget = self.widgets['daily_build_archive']
+ widget, CREATE_NEW, current_value
+ )
+ archive_widget = self.widgets["daily_build_archive"]
self.show_ppa_chooser = len(archive_widget.vocabulary) > 0
if not self.show_ppa_chooser:
- self.widgets['ppa_name'].setRenderedValue('ppa')
+ self.widgets["ppa_name"].setRenderedValue("ppa")
# Force there to be no '(nothing selected)' item in the select.
# We do this as the input isn't listed as 'required' otherwise
# the validator gets all confused when we want to create a new
@@ -732,7 +812,7 @@ class SourcePackageRecipeAddView(RecipeRelatedBranchesMixin,
def setUpFields(self):
super().setUpFields()
# Ensure distro series widget allows input
- self.form_fields['distroseries'].for_input = True
+ self.form_fields["distroseries"].for_input = True
def getBranch(self):
"""The branch or repository on which the recipe is built."""
@@ -742,12 +822,17 @@ class SourcePackageRecipeAddView(RecipeRelatedBranchesMixin,
"""A generator of recipe names."""
# +junk-daily doesn't make a very good recipe name, so use the
# branch name in that case; similarly for personal Git repositories.
- if ((IBranch.providedBy(self.context) and
- self.context.target.allow_recipe_name_from_target) or
- ((IGitRepository.providedBy(self.context) or
- IGitRef.providedBy(self.context)) and
- self.context.namespace.allow_recipe_name_from_target)):
- branch_target_name = self.context.target.name.split('/')[-1]
+ if (
+ IBranch.providedBy(self.context)
+ and self.context.target.allow_recipe_name_from_target
+ ) or (
+ (
+ IGitRepository.providedBy(self.context)
+ or IGitRef.providedBy(self.context)
+ )
+ and self.context.namespace.allow_recipe_name_from_target
+ ):
+ branch_target_name = self.context.target.name.split("/")[-1]
else:
branch_target_name = self.context.name
yield "%s-daily" % branch_target_name
@@ -765,54 +850,70 @@ class SourcePackageRecipeAddView(RecipeRelatedBranchesMixin,
@property
def initial_values(self):
distroseries = BuildableDistroSeries.findSeries(self.user)
- series = [series for series in distroseries if series.status in (
- SeriesStatus.CURRENT, SeriesStatus.DEVELOPMENT)]
+ series = [
+ series
+ for series in distroseries
+ if series.status
+ in (SeriesStatus.CURRENT, SeriesStatus.DEVELOPMENT)
+ ]
if IBranch.providedBy(self.context):
recipe_text = MINIMAL_RECIPE_TEXT_BZR % self.context.identity
elif IGitRepository.providedBy(self.context):
default_ref = None
if self.context.default_branch is not None:
default_ref = self.context.getRefByPath(
- self.context.default_branch)
+ self.context.default_branch
+ )
if default_ref is not None:
branch_name = default_ref.name
else:
branch_name = "ENTER-BRANCH-NAME"
recipe_text = MINIMAL_RECIPE_TEXT_GIT % (
- self.context.identity, branch_name)
+ self.context.identity,
+ branch_name,
+ )
elif IGitRef.providedBy(self.context):
recipe_text = MINIMAL_RECIPE_TEXT_GIT % (
- self.context.repository.identity, self.context.name)
+ self.context.repository.identity,
+ self.context.name,
+ )
else:
raise AssertionError("Unsupported context: %r" % (self.context,))
return {
- 'name': self._find_unused_name(self.user),
- 'recipe_text': recipe_text,
- 'owner': self.user,
- 'distroseries': series,
- 'build_daily': True,
- 'use_ppa': EXISTING_PPA,
- }
+ "name": self._find_unused_name(self.user),
+ "recipe_text": recipe_text,
+ "owner": self.user,
+ "distroseries": series,
+ "build_daily": True,
+ "use_ppa": EXISTING_PPA,
+ }
@property
def cancel_url(self):
return canonical_url(self.context)
- @action('Create Recipe', name='create')
+ @action("Create Recipe", name="create")
def request_action(self, action, data):
- owner = data['owner']
- if data['use_ppa'] == CREATE_NEW:
- ppa_name = data.get('ppa_name', None)
+ owner = data["owner"]
+ if data["use_ppa"] == CREATE_NEW:
+ ppa_name = data.get("ppa_name", None)
ppa = owner.createPPA(
- getUtility(ILaunchpadCelebrities).ubuntu, ppa_name)
+ getUtility(ILaunchpadCelebrities).ubuntu, ppa_name
+ )
else:
- ppa = data['daily_build_archive']
+ ppa = data["daily_build_archive"]
try:
source_package_recipe = self.error_handler(
getUtility(ISourcePackageRecipeSource).new,
- self.user, owner, data['name'],
- data['recipe_text'], data['description'],
- data['distroseries'], ppa, data['build_daily'])
+ self.user,
+ owner,
+ data["name"],
+ data["recipe_text"],
+ data["description"],
+ data["distroseries"],
+ ppa,
+ data["build_daily"],
+ )
Store.of(source_package_recipe).flush()
except ErrorHandled:
return
@@ -821,30 +922,33 @@ class SourcePackageRecipeAddView(RecipeRelatedBranchesMixin,
def validate(self, data):
super().validate(data)
- name = data.get('name', None)
- owner = data.get('owner', None)
+ name = data.get("name", None)
+ owner = data.get("owner", None)
if name and owner:
SourcePackageRecipeSource = getUtility(ISourcePackageRecipeSource)
if SourcePackageRecipeSource.exists(owner, name):
self.setFieldError(
- 'name',
- 'There is already a recipe owned by %s with this name.' %
- owner.displayname)
- if data['use_ppa'] == CREATE_NEW:
- ppa_name = data.get('ppa_name', None)
+ "name",
+ "There is already a recipe owned by %s with this name."
+ % owner.displayname,
+ )
+ if data["use_ppa"] == CREATE_NEW:
+ ppa_name = data.get("ppa_name", None)
if ppa_name is None:
self.setFieldError(
- 'ppa_name', 'You need to specify a name for the PPA.')
+ "ppa_name", "You need to specify a name for the PPA."
+ )
else:
error = validate_ppa(
- owner, getUtility(ILaunchpadCelebrities).ubuntu, ppa_name)
+ owner, getUtility(ILaunchpadCelebrities).ubuntu, ppa_name
+ )
if error is not None:
- self.setFieldError('ppa_name', error)
+ self.setFieldError("ppa_name", error)
-class SourcePackageRecipeEditView(RecipeRelatedBranchesMixin,
- RecipeTextValidatorMixin,
- LaunchpadEditFormView):
+class SourcePackageRecipeEditView(
+ RecipeRelatedBranchesMixin, RecipeTextValidatorMixin, LaunchpadEditFormView
+):
"""View for editing Source Package Recipes."""
def getBranch(self):
@@ -853,7 +957,8 @@ class SourcePackageRecipeEditView(RecipeRelatedBranchesMixin,
@property
def title(self):
- return 'Edit %s source package recipe' % self.context.name
+ return "Edit %s source package recipe" % self.context.name
+
label = title
schema = ISourcePackageEditSchema
@@ -863,52 +968,60 @@ class SourcePackageRecipeEditView(RecipeRelatedBranchesMixin,
super().setUpFields()
# Ensure distro series widget allows input
- self.form_fields['distroseries'].for_input = True
+ self.form_fields["distroseries"].for_input = True
- if check_permission('launchpad.Admin', self.context):
+ if check_permission("launchpad.Admin", self.context):
# Exclude the PPA archive dropdown.
- self.form_fields = self.form_fields.omit('daily_build_archive')
+ self.form_fields = self.form_fields.omit("daily_build_archive")
- owner_field = self.schema['owner']
+ owner_field = self.schema["owner"]
any_owner_choice = PersonChoice(
- __name__='owner', title=owner_field.title,
- description=("As an administrator you are able to assign"
- " this recipe to any person or team."),
- required=True, vocabulary='ValidPersonOrTeam')
+ __name__="owner",
+ title=owner_field.title,
+ description=(
+ "As an administrator you are able to assign"
+ " this recipe to any person or team."
+ ),
+ required=True,
+ vocabulary="ValidPersonOrTeam",
+ )
any_owner_field = form.Fields(
- any_owner_choice, render_context=self.render_context)
+ any_owner_choice, render_context=self.render_context
+ )
# Replace the normal owner field with a more permissive vocab.
- self.form_fields = self.form_fields.omit('owner')
+ self.form_fields = self.form_fields.omit("owner")
self.form_fields = any_owner_field + self.form_fields
@property
def initial_values(self):
return {
- 'distroseries': self.context.distroseries,
- 'recipe_text': self.context.recipe_text,
- }
+ "distroseries": self.context.distroseries,
+ "recipe_text": self.context.recipe_text,
+ }
@property
def cancel_url(self):
return canonical_url(self.context)
- @action('Update Recipe', name='update')
+ @action("Update Recipe", name="update")
def request_action(self, action, data):
changed = False
recipe_before_modification = Snapshot(
- self.context, providing=providedBy(self.context))
+ self.context, providing=providedBy(self.context)
+ )
- recipe_text = data.pop('recipe_text')
+ recipe_text = data.pop("recipe_text")
try:
recipe = self.error_handler(
- getUtility(IRecipeBranchSource).getParsedRecipe, recipe_text)
+ getUtility(IRecipeBranchSource).getParsedRecipe, recipe_text
+ )
if self.context.builder_recipe != recipe:
self.error_handler(self.context.setRecipeText, recipe_text)
changed = True
except ErrorHandled:
return
- distros = data.pop('distroseries')
+ distros = data.pop("distroseries")
if distros != self.context.distroseries:
self.context.distroseries.clear()
for distroseries_item in distros:
@@ -920,9 +1033,13 @@ class SourcePackageRecipeEditView(RecipeRelatedBranchesMixin,
if changed:
field_names = [
- form_field.__name__ for form_field in self.form_fields]
- notify(ObjectModifiedEvent(
- self.context, recipe_before_modification, field_names))
+ form_field.__name__ for form_field in self.form_fields
+ ]
+ notify(
+ ObjectModifiedEvent(
+ self.context, recipe_before_modification, field_names
+ )
+ )
self.next_url = canonical_url(self.context)
@@ -933,24 +1050,25 @@ class SourcePackageRecipeEditView(RecipeRelatedBranchesMixin,
def validate(self, data):
super().validate(data)
- name = data.get('name', None)
- owner = data.get('owner', None)
+ name = data.get("name", None)
+ owner = data.get("owner", None)
if name and owner:
SourcePackageRecipeSource = getUtility(ISourcePackageRecipeSource)
if SourcePackageRecipeSource.exists(owner, name):
recipe = owner.getRecipe(name)
if recipe != self.context:
self.setFieldError(
- 'name',
- 'There is already a recipe owned by %s with this '
- 'name.' % owner.displayname)
+ "name",
+ "There is already a recipe owned by %s with this "
+ "name." % owner.displayname,
+ )
class SourcePackageRecipeDeleteView(LaunchpadFormView):
-
@property
def title(self):
- return 'Delete %s source package recipe' % self.context.name
+ return "Delete %s source package recipe" % self.context.name
+
label = title
class schema(Interface):
@@ -964,6 +1082,6 @@ class SourcePackageRecipeDeleteView(LaunchpadFormView):
def next_url(self):
return canonical_url(self.context.owner)
- @action('Delete recipe', name='delete')
+ @action("Delete recipe", name="delete")
def request_action(self, action, data):
self.context.destroySelf()
diff --git a/lib/lp/code/browser/sourcepackagerecipebuild.py b/lib/lp/code/browser/sourcepackagerecipebuild.py
index 8817d7e..4a675b1 100644
--- a/lib/lp/code/browser/sourcepackagerecipebuild.py
+++ b/lib/lp/code/browser/sourcepackagerecipebuild.py
@@ -4,37 +4,31 @@
"""SourcePackageRecipeBuild views."""
__all__ = [
- 'SourcePackageRecipeBuildContextMenu',
- 'SourcePackageRecipeBuildNavigation',
- 'SourcePackageRecipeBuildView',
- 'SourcePackageRecipeBuildCancelView',
- 'SourcePackageRecipeBuildRescoreView',
- ]
+ "SourcePackageRecipeBuildContextMenu",
+ "SourcePackageRecipeBuildNavigation",
+ "SourcePackageRecipeBuildView",
+ "SourcePackageRecipeBuildCancelView",
+ "SourcePackageRecipeBuildRescoreView",
+]
from zope.interface import Interface
from zope.schema import Int
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
-from lp.buildmaster.enums import (
- BuildQueueStatus,
- BuildStatus,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
+from lp.buildmaster.enums import BuildQueueStatus, BuildStatus
from lp.code.interfaces.sourcepackagerecipebuild import (
ISourcePackageRecipeBuild,
- )
+)
from lp.services.librarian.browser import FileNavigationMixin
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
- )
+ canonical_url,
+ enabled_with_permission,
+)
class SourcePackageRecipeBuildNavigation(Navigation, FileNavigationMixin):
@@ -47,21 +41,27 @@ class SourcePackageRecipeBuildContextMenu(ContextMenu):
usedfor = ISourcePackageRecipeBuild
- facet = 'branches'
+ facet = "branches"
- links = ('cancel', 'rescore')
+ links = ("cancel", "rescore")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def cancel(self):
return Link(
- '+cancel', 'Cancel build', icon='remove',
- enabled=self.context.can_be_cancelled)
+ "+cancel",
+ "Cancel build",
+ icon="remove",
+ enabled=self.context.can_be_cancelled,
+ )
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def rescore(self):
return Link(
- '+rescore', 'Rescore build', icon='edit',
- enabled=self.context.can_be_rescored)
+ "+rescore",
+ "Rescore build",
+ icon="edit",
+ enabled=self.context.can_be_rescored,
+ )
class SourcePackageRecipeBuildView(LaunchpadView):
@@ -70,21 +70,23 @@ class SourcePackageRecipeBuildView(LaunchpadView):
@property
def status(self):
"""A human-friendly status string."""
- if (self.context.status == BuildStatus.NEEDSBUILD
- and self.eta is None):
- return 'No suitable builders'
+ if self.context.status == BuildStatus.NEEDSBUILD and self.eta is None:
+ return "No suitable builders"
return {
- BuildStatus.NEEDSBUILD: 'Pending build',
- BuildStatus.UPLOADING: 'Build uploading',
- BuildStatus.FULLYBUILT: 'Successful build',
+ BuildStatus.NEEDSBUILD: "Pending build",
+ BuildStatus.UPLOADING: "Build uploading",
+ BuildStatus.FULLYBUILT: "Successful build",
BuildStatus.MANUALDEPWAIT: (
- 'Could not build because of missing dependencies'),
+ "Could not build because of missing dependencies"
+ ),
BuildStatus.CHROOTWAIT: (
- 'Could not build because of chroot problem'),
+ "Could not build because of chroot problem"
+ ),
BuildStatus.SUPERSEDED: (
- 'Could not build because source package was superseded'),
- BuildStatus.FAILEDTOUPLOAD: 'Could not be uploaded correctly',
- }.get(self.context.status, self.context.status.title)
+ "Could not build because source package was superseded"
+ ),
+ BuildStatus.FAILEDTOUPLOAD: "Could not be uploaded correctly",
+ }.get(self.context.status, self.context.status.title)
@cachedproperty
def eta(self):
@@ -134,9 +136,10 @@ class SourcePackageRecipeBuildCancelView(LaunchpadFormView):
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
- @action('Cancel build', name='cancel')
+ @action("Cancel build", name="cancel")
def request_action(self, action, data):
"""Cancel the build."""
self.context.cancel()
@@ -147,9 +150,12 @@ class SourcePackageRecipeBuildRescoreView(LaunchpadFormView):
class schema(Interface):
"""Schema for deleting a build."""
+
score = Int(
- title='Score', required=True,
- description='The score of the recipe.')
+ title="Score",
+ required=True,
+ description="The score of the recipe.",
+ )
page_title = label = "Rescore build"
@@ -157,19 +163,21 @@ class SourcePackageRecipeBuildRescoreView(LaunchpadFormView):
if self.context.buildqueue_record is not None:
return super().__call__()
self.request.response.addWarningNotification(
- 'Cannot rescore this build because it is not queued.')
+ "Cannot rescore this build because it is not queued."
+ )
self.request.response.redirect(canonical_url(self.context))
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
- @action('Rescore build', name='rescore')
+ @action("Rescore build", name="rescore")
def request_action(self, action, data):
"""Rescore the build."""
- self.context.buildqueue_record.lastscore = int(data['score'])
+ self.context.buildqueue_record.lastscore = int(data["score"])
@property
def initial_values(self):
- return {'score': str(self.context.buildqueue_record.lastscore)}
+ return {"score": str(self.context.buildqueue_record.lastscore)}
diff --git a/lib/lp/code/browser/sourcepackagerecipelisting.py b/lib/lp/code/browser/sourcepackagerecipelisting.py
index 4b743cf..622723d 100644
--- a/lib/lp/code/browser/sourcepackagerecipelisting.py
+++ b/lib/lp/code/browser/sourcepackagerecipelisting.py
@@ -4,32 +4,30 @@
"""Base class view for sourcepackagerecipe listings."""
__all__ = [
- 'BranchRecipeListingView',
- 'HasRecipesMenuMixin',
- 'PersonRecipeListingView',
- 'ProductRecipeListingView',
- ]
+ "BranchRecipeListingView",
+ "HasRecipesMenuMixin",
+ "PersonRecipeListingView",
+ "ProductRecipeListingView",
+]
from lp.code.browser.decorations import DecoratedBranch
from lp.code.interfaces.branch import IBranch
from lp.services.feeds.browser import FeedsMixin
-from lp.services.webapp import (
- LaunchpadView,
- Link,
- )
+from lp.services.webapp import LaunchpadView, Link
class HasRecipesMenuMixin:
"""A mixin for context menus for objects that implement IHasRecipes."""
def view_recipes(self):
- text = 'View source package recipes'
+ text = "View source package recipes"
enabled = False
if self.context.recipes.count():
enabled = True
return Link(
- '+recipes', text, icon='info', enabled=enabled, site='code')
+ "+recipes", text, icon="info", enabled=enabled, site="code"
+ )
class RecipeListingView(LaunchpadView, FeedsMixin):
@@ -41,8 +39,9 @@ class RecipeListingView(LaunchpadView, FeedsMixin):
@property
def page_title(self):
- return 'Source Package Recipes for %(display_name)s' % {
- 'display_name': self.context.display_name}
+ return "Source Package Recipes for %(display_name)s" % {
+ "display_name": self.context.display_name
+ }
class BranchRecipeListingView(RecipeListingView):
@@ -53,8 +52,9 @@ class BranchRecipeListingView(RecipeListingView):
super().initialize()
# Replace our context with a decorated branch, if it is not already
# decorated.
- if (IBranch.providedBy(self.context) and
- not isinstance(self.context, DecoratedBranch)):
+ if IBranch.providedBy(self.context) and not isinstance(
+ self.context, DecoratedBranch
+ ):
self.context = DecoratedBranch(self.context)
diff --git a/lib/lp/code/browser/summary.py b/lib/lp/code/browser/summary.py
index fdfb1e1..ccdd742 100644
--- a/lib/lp/code/browser/summary.py
+++ b/lib/lp/code/browser/summary.py
@@ -4,8 +4,8 @@
"""View classes for branch summaries."""
__all__ = [
- 'BranchCountSummaryView',
- ]
+ "BranchCountSummaryView",
+]
from lp import _
@@ -25,7 +25,8 @@ class BranchCountSummaryView(LaunchpadView):
"""Return the branch collection for this context."""
collection = IBranchCollection(self.context).visibleByUser(self.user)
collection = collection.withLifecycleStatus(
- *DEFAULT_BRANCH_STATUS_IN_LISTING)
+ *DEFAULT_BRANCH_STATUS_IN_LISTING
+ )
return collection
@cachedproperty
@@ -62,21 +63,23 @@ class BranchCountSummaryView(LaunchpadView):
@property
def branch_text(self):
return get_plural_text(
- self.branch_count, _('active branch'), _('active branches'))
+ self.branch_count, _("active branch"), _("active branches")
+ )
@property
def person_text(self):
return get_plural_text(
- self.person_owner_count, _('person'), _('people'))
+ self.person_owner_count, _("person"), _("people")
+ )
@property
def team_text(self):
- return get_plural_text(self.team_owner_count, _('team'), _('teams'))
+ return get_plural_text(self.team_owner_count, _("team"), _("teams"))
@property
def commit_text(self):
- return get_plural_text(self.commit_count, _('commit'), _('commits'))
+ return get_plural_text(self.commit_count, _("commit"), _("commits"))
@property
def committer_text(self):
- return get_plural_text(self.committer_count, _('person'), _('people'))
+ return get_plural_text(self.committer_count, _("person"), _("people"))
diff --git a/lib/lp/code/browser/tests/test_bazaar.py b/lib/lp/code/browser/tests/test_bazaar.py
index e6d67d5..1b593be 100644
--- a/lib/lp/code/browser/tests/test_bazaar.py
+++ b/lib/lp/code/browser/tests/test_bazaar.py
@@ -9,12 +9,7 @@ from lp.app.enums import InformationType
from lp.code.browser.bazaar import BazaarApplicationView
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.servers import LaunchpadTestRequest
-from lp.testing import (
- ANONYMOUS,
- login,
- login_person,
- TestCaseWithFactory,
- )
+from lp.testing import ANONYMOUS, TestCaseWithFactory, login, login_person
from lp.testing.layers import DatabaseFunctionalLayer
@@ -34,40 +29,44 @@ class TestBazaarViewPreCacheLaunchpadPermissions(TestCaseWithFactory):
# Create a some private branches (stacked and unstacked) that the
# logged in user would not normally see.
private_branch = self.factory.makeAnyBranch(
- information_type=InformationType.USERDATA)
+ information_type=InformationType.USERDATA
+ )
self.factory.makeAnyBranch(stacked_on=private_branch)
branch = self.factory.makeAnyBranch()
- recent_branches = self.getViewBranches('recently_registered_branches')
+ recent_branches = self.getViewBranches("recently_registered_branches")
self.assertEqual(branch, recent_branches[0])
- self.assertTrue(check_permission('launchpad.View', branch))
+ self.assertTrue(check_permission("launchpad.View", branch))
def makeBranchScanned(self, branch):
"""Make the branch appear scanned."""
revision = self.factory.makeRevision()
# Login an administrator so they can update the branch's details.
- login('admin@xxxxxxxxxxxxx')
+ login("admin@xxxxxxxxxxxxx")
branch.updateScannedDetails(revision, 1)
def test_recently_changed(self):
# Create a some private branches (stacked and unstacked) that the
# logged in user would not normally see.
private_branch = self.factory.makeAnyBranch(
- information_type=InformationType.USERDATA)
+ information_type=InformationType.USERDATA
+ )
stacked_private_branch = self.factory.makeAnyBranch(
- stacked_on=private_branch)
+ stacked_on=private_branch
+ )
branch = self.factory.makeAnyBranch()
self.makeBranchScanned(stacked_private_branch)
self.makeBranchScanned(branch)
- recent_branches = self.getViewBranches('recently_changed_branches')
+ recent_branches = self.getViewBranches("recently_changed_branches")
self.assertEqual(branch, recent_branches[0])
- self.assertTrue(check_permission('launchpad.View', branch))
+ self.assertTrue(check_permission("launchpad.View", branch))
def test_recently_imported(self):
# Create an import branch that is stacked on a private branch that the
# logged in user would not normally see. This would never happen in
# reality, but hey, lets test the function actually works.
private_branch = self.factory.makeAnyBranch(
- information_type=InformationType.USERDATA)
+ information_type=InformationType.USERDATA
+ )
# A new code import needs a real user as the sender for the outgoing
# email.
login_person(self.factory.makePerson())
@@ -79,6 +78,6 @@ class TestBazaarViewPreCacheLaunchpadPermissions(TestCaseWithFactory):
branch = code_import.branch
self.makeBranchScanned(stacked_private_branch)
self.makeBranchScanned(branch)
- recent_branches = self.getViewBranches('recently_imported_branches')
+ recent_branches = self.getViewBranches("recently_imported_branches")
self.assertEqual(branch, recent_branches[0])
- self.assertTrue(check_permission('launchpad.View', branch))
+ self.assertTrue(check_permission("launchpad.View", branch))
diff --git a/lib/lp/code/browser/tests/test_branch.py b/lib/lp/code/browser/tests/test_branch.py
index ac77d79..e8d914e 100644
--- a/lib/lp/code/browser/tests/test_branch.py
+++ b/lib/lp/code/browser/tests/test_branch.py
@@ -6,8 +6,8 @@
from datetime import datetime
from textwrap import dedent
-from fixtures import FakeLogger
import pytz
+from fixtures import FakeLogger
from storm.store import Store
from testtools.matchers import Equals
from zope.component import getUtility
@@ -19,15 +19,11 @@ from lp.app.enums import InformationType
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.interfaces.services import IService
from lp.bugs.interfaces.bugtask import (
- BugTaskStatus,
UNRESOLVED_BUGTASK_STATUSES,
- )
+ BugTaskStatus,
+)
from lp.code.browser.branch import BranchMirrorStatusView
-from lp.code.bzr import (
- BranchFormat,
- ControlFormat,
- RepositoryFormat,
- )
+from lp.code.bzr import BranchFormat, ControlFormat, RepositoryFormat
from lp.code.enums import BranchType
from lp.code.model.branchjob import BranchScanJob
from lp.code.tests.helpers import BranchHostingFixture
@@ -44,32 +40,22 @@ from lp.services.webapp.publisher import canonical_url
from lp.services.webapp.servers import LaunchpadTestRequest
from lp.testing import (
BrowserTestCase,
+ StormStatementRecorder,
+ TestCaseWithFactory,
login,
login_person,
logout,
person_logged_in,
- StormStatementRecorder,
- TestCaseWithFactory,
- )
-from lp.testing.layers import (
- DatabaseFunctionalLayer,
- LaunchpadFunctionalLayer,
- )
-from lp.testing.matchers import (
- BrowsesWithQueryLimit,
- Contains,
- HasQueryCount,
- )
+)
+from lp.testing.layers import DatabaseFunctionalLayer, LaunchpadFunctionalLayer
+from lp.testing.matchers import BrowsesWithQueryLimit, Contains, HasQueryCount
from lp.testing.pages import (
extract_text,
find_tag_by_id,
setupBrowser,
setupBrowserForUser,
- )
-from lp.testing.views import (
- create_initialized_view,
- create_view,
- )
+)
+from lp.testing.views import create_initialized_view, create_view
class TestBranchMirrorHidden(TestCaseWithFactory):
@@ -80,10 +66,14 @@ class TestBranchMirrorHidden(TestCaseWithFactory):
def setUp(self):
super().setUp()
config.push(
- "test", dedent("""\
+ "test",
+ dedent(
+ """\
[codehosting]
private_mirror_hosts: private.example.com
- """))
+ """
+ ),
+ )
def tearDown(self):
config.pop("test")
@@ -93,17 +83,20 @@ class TestBranchMirrorHidden(TestCaseWithFactory):
# A branch from a normal location is fine.
branch = self.factory.makeAnyBranch(
branch_type=BranchType.MIRRORED,
- url="http://example.com/good/mirror")
- view = create_initialized_view(branch, '+index')
+ url="http://example.com/good/mirror",
+ )
+ view = create_initialized_view(branch, "+index")
self.assertTrue(view.user is None)
self.assertEqual(
- "http://example.com/good/mirror", view.mirror_location)
+ "http://example.com/good/mirror", view.mirror_location
+ )
def testLocationlessRemoteBranch(self):
# A branch from a normal location is fine.
branch = self.factory.makeAnyBranch(
- branch_type=BranchType.REMOTE, url=None)
- view = create_initialized_view(branch, '+index')
+ branch_type=BranchType.REMOTE, url=None
+ )
+ view = create_initialized_view(branch, "+index")
self.assertTrue(view.user is None)
self.assertIs(None, view.mirror_location)
@@ -112,8 +105,9 @@ class TestBranchMirrorHidden(TestCaseWithFactory):
# anonymous browsers.
branch = self.factory.makeAnyBranch(
branch_type=BranchType.MIRRORED,
- url="http://private.example.com/bzr-mysql/mysql-5.0")
- view = create_initialized_view(branch, '+index')
+ url="http://private.example.com/bzr-mysql/mysql-5.0",
+ )
+ view = create_initialized_view(branch, "+index")
self.assertTrue(view.user is None)
self.assertEqual("<private server>", view.mirror_location)
@@ -123,14 +117,17 @@ class TestBranchMirrorHidden(TestCaseWithFactory):
owner = self.factory.makePerson(email="eric@xxxxxxxxxxx")
branch = self.factory.makeAnyBranch(
branch_type=BranchType.MIRRORED,
- owner=owner, url="http://private.example.com/bzr-mysql/mysql-5.0")
+ owner=owner,
+ url="http://private.example.com/bzr-mysql/mysql-5.0",
+ )
# Now log in the owner.
- login('eric@xxxxxxxxxxx')
- view = create_initialized_view(branch, '+index')
+ login("eric@xxxxxxxxxxx")
+ view = create_initialized_view(branch, "+index")
self.assertEqual(view.user, owner)
self.assertEqual(
"http://private.example.com/bzr-mysql/mysql-5.0",
- view.mirror_location)
+ view.mirror_location,
+ )
def testHiddenBranchAsOtherLoggedInUser(self):
# A branch location with a defined private host is hidden from other
@@ -138,11 +135,13 @@ class TestBranchMirrorHidden(TestCaseWithFactory):
owner = self.factory.makePerson(email="eric@xxxxxxxxxxx")
other = self.factory.makePerson(email="other@xxxxxxxxxxx")
branch = self.factory.makeAnyBranch(
- branch_type=BranchType.MIRRORED, owner=owner,
- url="http://private.example.com/bzr-mysql/mysql-5.0")
+ branch_type=BranchType.MIRRORED,
+ owner=owner,
+ url="http://private.example.com/bzr-mysql/mysql-5.0",
+ )
# Now log in the other person.
- login('other@xxxxxxxxxxx')
- view = create_initialized_view(branch, '+index')
+ login("other@xxxxxxxxxxx")
+ view = create_initialized_view(branch, "+index")
self.assertEqual(view.user, other)
self.assertEqual("<private server>", view.mirror_location)
@@ -155,43 +154,50 @@ class TestBranchView(BrowserTestCase):
"""mirror_status_message is truncated if the text is overly long."""
branch = self.factory.makeBranch(branch_type=BranchType.MIRRORED)
branch.mirrorFailed(
- "on quick brown fox the dog jumps to" *
- BranchMirrorStatusView.MAXIMUM_STATUS_MESSAGE_LENGTH)
- branch_view = create_view(branch, '+mirror-status')
+ "on quick brown fox the dog jumps to"
+ * BranchMirrorStatusView.MAXIMUM_STATUS_MESSAGE_LENGTH
+ )
+ branch_view = create_view(branch, "+mirror-status")
self.assertEqual(
- truncate_text(branch.mirror_status_message,
- branch_view.MAXIMUM_STATUS_MESSAGE_LENGTH) + ' ...',
- branch_view.mirror_status_message)
+ truncate_text(
+ branch.mirror_status_message,
+ branch_view.MAXIMUM_STATUS_MESSAGE_LENGTH,
+ )
+ + " ...",
+ branch_view.mirror_status_message,
+ )
def testMirrorStatusMessage(self):
"""mirror_status_message on the view is the same as on the branch."""
branch = self.factory.makeBranch(branch_type=BranchType.MIRRORED)
branch.mirrorFailed("This is a short error message.")
- branch_view = create_view(branch, '+mirror-status')
+ branch_view = create_view(branch, "+mirror-status")
self.assertTrue(
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)
+ branch.mirror_status_message, branch_view.mirror_status_message
+ )
self.assertEqual(
- "This is a short error message.",
- branch_view.mirror_status_message)
+ "This is a short error message.", branch_view.mirror_status_message
+ )
def testShowMergeLinksOnManyBranchProject(self):
# The merge links are shown on projects that have multiple branches.
- product = self.factory.makeProduct(name='super-awesome-project')
+ product = self.factory.makeProduct(name="super-awesome-project")
branch = self.factory.makeAnyBranch(product=product)
self.factory.makeAnyBranch(product=product)
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertTrue(view.show_merge_links)
def testShowMergeLinksOnJunkBranch(self):
# The merge links are not shown on junk branches because they do not
# support merge proposals.
junk_branch = self.factory.makeBranch(product=None)
- view = create_initialized_view(junk_branch, '+index')
+ view = create_initialized_view(junk_branch, "+index")
self.assertFalse(view.show_merge_links)
def testShowMergeLinksOnSingleBranchProject(self):
@@ -199,91 +205,94 @@ class TestBranchView(BrowserTestCase):
# only has one branch because it's pointless to propose it for merging
# if there's nothing to merge into.
branch = self.factory.makeAnyBranch()
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertFalse(view.show_merge_links)
def testNoProductSeriesPushingTranslations(self):
# By default, a branch view shows no product series pushing
# translations to the branch.
branch = self.factory.makeBranch()
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertEqual(list(view.translations_sources()), [])
def testProductSeriesPushingTranslations(self):
# If a product series exports its translations to the branch,
# the view shows it.
product = self.factory.makeProduct()
- trunk = product.getSeries('trunk')
+ trunk = product.getSeries("trunk")
branch = self.factory.makeBranch(owner=product.owner)
removeSecurityProxy(trunk).translations_branch = branch
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertEqual(list(view.translations_sources()), [trunk])
def test_is_empty_directory(self):
# Branches are considered empty until they get a control format.
branch = self.factory.makeBranch()
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertTrue(view.is_empty_directory)
with person_logged_in(branch.owner):
# Make it look as though the branch has been pushed.
branch.branchChanged(
- None, None, ControlFormat.BZR_METADIR_1, None, None)
+ None, None, ControlFormat.BZR_METADIR_1, None, None
+ )
self.assertFalse(view.is_empty_directory)
def test_empty_directories_use_existing(self):
# Push example should include --use-existing-dir for empty directories.
branch = self.factory.makeBranch(owner=self.user)
text = self.getMainText(branch)
- self.assertIn('push\n--use-existing-dir', text)
+ self.assertIn("push\n--use-existing-dir", text)
with person_logged_in(self.user):
# Make it look as though the branch has been pushed.
branch.branchChanged(
- None, None, ControlFormat.BZR_METADIR_1, None, None)
+ None, None, ControlFormat.BZR_METADIR_1, None, None
+ )
text = self.getMainText(branch)
- self.assertNotIn('push\n--use-existing-dir', text)
+ self.assertNotIn("push\n--use-existing-dir", text)
def test_user_can_upload(self):
# A user can upload if they have edit permissions.
branch = self.factory.makeAnyBranch()
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
login_person(branch.owner)
self.assertTrue(view.user_can_upload)
def test_user_can_upload_admins_can(self):
# Admins can upload to any hosted branch.
branch = self.factory.makeAnyBranch()
- view = create_initialized_view(branch, '+index')
- login('admin@xxxxxxxxxxxxx')
+ view = create_initialized_view(branch, "+index")
+ login("admin@xxxxxxxxxxxxx")
self.assertTrue(view.user_can_upload)
def test_user_can_upload_non_owner(self):
# Someone not associated with the branch cannot upload
branch = self.factory.makeAnyBranch()
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
login_person(self.factory.makePerson())
self.assertFalse(view.user_can_upload)
def test_user_can_upload_mirrored(self):
# Even the owner of a mirrored branch can't upload.
branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED)
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
login_person(branch.owner)
self.assertFalse(view.user_can_upload)
def test_recipes_link_no_recipes(self):
# A branch with no recipes does not show a recipes link.
branch = self.factory.makeAnyBranch()
- view = create_initialized_view(branch, '+index')
- self.assertEqual('No recipes using this branch.', view.recipes_link)
+ view = create_initialized_view(branch, "+index")
+ self.assertEqual("No recipes using this branch.", view.recipes_link)
def test_recipes_link_one_recipe(self):
# A branch with one recipe shows a link to that recipe.
branch = self.factory.makeAnyBranch()
recipe = self.factory.makeSourcePackageRecipe(branches=[branch])
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
expected_link = (
- '<a href="%s">1 recipe</a> using this branch.' %
- canonical_url(recipe))
+ '<a href="%s">1 recipe</a> using this branch.'
+ % canonical_url(recipe)
+ )
self.assertEqual(expected_link, view.recipes_link)
def test_recipes_link_more_recipes(self):
@@ -291,16 +300,17 @@ class TestBranchView(BrowserTestCase):
branch = self.factory.makeAnyBranch()
self.factory.makeSourcePackageRecipe(branches=[branch])
self.factory.makeSourcePackageRecipe(branches=[branch])
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertEqual(
'<a href="+recipes">2 recipes</a> using this branch.',
- view.recipes_link)
+ view.recipes_link,
+ )
def test_show_rescan_link(self):
branch = self.factory.makeAnyBranch()
job = BranchScanJob.create(branch)
job.job._status = JobStatus.FAILED
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
result = view.show_rescan_link
self.assertTrue(result)
@@ -309,13 +319,13 @@ class TestBranchView(BrowserTestCase):
job = BranchScanJob.create(branch)
job.job._status = JobStatus.COMPLETED
job.job.date_finished = UTC_NOW
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
result = view.show_rescan_link
self.assertFalse(result)
def test_show_rescan_link_no_scan_jobs(self):
branch = self.factory.makeAnyBranch()
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
result = view.show_rescan_link
self.assertFalse(result)
@@ -325,7 +335,7 @@ class TestBranchView(BrowserTestCase):
job.job._status = JobStatus.FAILED
job = BranchScanJob.create(branch)
job.job._status = JobStatus.COMPLETED
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
result = view.show_rescan_link
self.assertTrue(result)
@@ -339,7 +349,7 @@ class TestBranchView(BrowserTestCase):
branch = self.factory.makeAnyBranch()
with person_logged_in(branch.owner):
self._addBugLinks(branch)
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertEqual(len(BugTaskStatus), len(view.linked_bugtasks))
self.assertFalse(view.context.is_series_branch)
@@ -349,14 +359,16 @@ class TestBranchView(BrowserTestCase):
branch = self.factory.makeAnyBranch()
reporter = self.factory.makePerson()
bug = self.factory.makeBug(
- owner=reporter, information_type=InformationType.USERDATA)
+ owner=reporter, information_type=InformationType.USERDATA
+ )
with person_logged_in(reporter):
branch.linkBug(bug, reporter)
- view = create_initialized_view(branch, '+index')
- self.assertEqual([bug.id],
- [task.bug.id for task in view.linked_bugtasks])
+ view = create_initialized_view(branch, "+index")
+ self.assertEqual(
+ [bug.id], [task.bug.id for task in view.linked_bugtasks]
+ )
with person_logged_in(branch.owner):
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
self.assertEqual([], view.linked_bugtasks)
def test_linked_bugtasks_series_branch(self):
@@ -367,10 +379,9 @@ class TestBranchView(BrowserTestCase):
product.development_focus.branch = branch
with person_logged_in(branch.owner):
self._addBugLinks(branch)
- view = create_initialized_view(branch, '+index')
+ view = create_initialized_view(branch, "+index")
for bugtask in view.linked_bugtasks:
- self.assertTrue(
- bugtask.status in UNRESOLVED_BUGTASK_STATUSES)
+ self.assertTrue(bugtask.status in UNRESOLVED_BUGTASK_STATUSES)
def test_linked_bugs_nonseries_branch_query_scaling(self):
# As we add linked bugs, the query count for a branch index page stays
@@ -392,7 +403,8 @@ class TestBranchView(BrowserTestCase):
# As we add linked bugs, the query count for a branch index page stays
# constant.
product = self.factory.makeProduct(
- branch_sharing_policy=BranchSharingPolicy.PUBLIC)
+ branch_sharing_policy=BranchSharingPolicy.PUBLIC
+ )
branch = self.factory.makeProductBranch(product=product)
browses_under_limit = BrowsesWithQueryLimit(54, branch.owner)
with person_logged_in(product.owner):
@@ -415,7 +427,9 @@ class TestBranchView(BrowserTestCase):
author="Eric the Viking <eric@xxxxxxxxxxxxxxxxxxxxxxxx>",
log_body=(
"Testing the email address in revisions\n"
- "email Bob (bob@xxxxxxxxxxx) for details."))
+ "email Bob (bob@xxxxxxxxxxx) for details."
+ ),
+ )
branch_revision = branch.createBranchRevision(seq, revision)
branch.updateScannedDetails(revision, seq)
@@ -428,7 +442,7 @@ class TestBranchView(BrowserTestCase):
with person_logged_in(branch.owner):
self._add_revisions(branch)
browser = self.getUserBrowser(canonical_url(branch))
- tag = find_tag_by_id(browser.contents, 'recent-revisions')
+ tag = find_tag_by_id(browser.contents, "recent-revisions")
text = extract_text(tag)
expected_text = """
Recent revisions
@@ -451,7 +465,7 @@ class TestBranchView(BrowserTestCase):
browser = setupBrowser()
logout()
browser.open(branch_url)
- tag = find_tag_by_id(browser.contents, 'recent-revisions')
+ tag = find_tag_by_id(browser.contents, "recent-revisions")
text = extract_text(tag)
expected_text = """
Recent revisions
@@ -471,20 +485,21 @@ class TestBranchView(BrowserTestCase):
with person_logged_in(branch.owner):
revisions = self._add_revisions(branch, 2)
mp = self.factory.makeBranchMergeProposal(
- target_branch=branch, registrant=branch.owner)
+ target_branch=branch, registrant=branch.owner
+ )
mp.markAsMerged(merged_revno=revisions[0].sequence)
# These values are extracted here and used below.
- mp_url = canonical_url(mp, rootsite='code', force_local_path=True)
+ mp_url = canonical_url(mp, rootsite="code", force_local_path=True)
branch_display_name = mp.source_branch.displayname
browser = self.getUserBrowser(canonical_url(branch))
- revision_content = find_tag_by_id(
- browser.contents, 'recent-revisions')
+ revision_content = find_tag_by_id(browser.contents, "recent-revisions")
text = extract_text(revision_content)
- expected_text = """
+ expected_text = (
+ """
Recent revisions
.*
2. By Eric the Viking <eric@xxxxxxxxxxxxxxxxxxxxxxxx>
@@ -496,12 +511,14 @@ class TestBranchView(BrowserTestCase):
Testing the email address in revisions\n
email Bob \\(bob@xxxxxxxxxxx\\) for details.
Merged branch %s
- """ % branch_display_name
+ """
+ % branch_display_name
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(expected_text, text)
- links = revision_content.find_all('a')
- self.assertEqual(mp_url, links[2]['href'])
+ links = revision_content.find_all("a")
+ self.assertEqual(mp_url, links[2]["href"])
def test_recent_revisions_with_merge_proposals_and_bug_links(self):
# Revisions which result from merging in a branch with a merge
@@ -512,7 +529,8 @@ class TestBranchView(BrowserTestCase):
with person_logged_in(branch.owner):
revisions = self._add_revisions(branch, 2)
mp = self.factory.makeBranchMergeProposal(
- target_branch=branch, registrant=branch.owner)
+ target_branch=branch, registrant=branch.owner
+ )
mp.markAsMerged(merged_revno=revisions[0].sequence)
# record linked bug info for use below
@@ -522,7 +540,8 @@ class TestBranchView(BrowserTestCase):
bug = self.factory.makeBug()
mp.source_branch.linkBug(bug, branch.owner)
linked_bug_urls.append(
- canonical_url(bug.default_bugtask, rootsite='bugs'))
+ canonical_url(bug.default_bugtask, rootsite="bugs")
+ )
bug_text = "Bug #%s: %s" % (bug.id, bug.title)
linked_bug_text.append(bug_text)
@@ -534,8 +553,7 @@ class TestBranchView(BrowserTestCase):
browser = self.getUserBrowser(canonical_url(branch))
- revision_content = find_tag_by_id(
- browser.contents, 'recent-revisions')
+ revision_content = find_tag_by_id(browser.contents, "recent-revisions")
text = extract_text(revision_content)
expected_text = """
@@ -551,15 +569,18 @@ class TestBranchView(BrowserTestCase):
email Bob \\(bob@xxxxxxxxxxx\\) for details.
Merged branch %s
%s
- """ % (branch_display_name, linked_bug_rendered_text)
+ """ % (
+ branch_display_name,
+ linked_bug_rendered_text,
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(expected_text, text)
- links = revision_content.find_all('a')
- self.assertEqual(mp_url, links[2]['href'])
- self.assertEqual(branch_url, links[3]['href'])
- self.assertEqual(linked_bug_urls[0], links[4]['href'])
- self.assertEqual(linked_bug_urls[1], links[5]['href'])
+ links = revision_content.find_all("a")
+ self.assertEqual(mp_url, links[2]["href"])
+ self.assertEqual(branch_url, links[3]["href"])
+ self.assertEqual(linked_bug_urls[0], links[4]["href"])
+ self.assertEqual(linked_bug_urls[1], links[5]["href"])
def test_view_for_user_with_artifact_grant(self):
# Users with an artifact grant for a branch related to a private
@@ -567,15 +588,18 @@ class TestBranchView(BrowserTestCase):
owner = self.factory.makePerson()
user = self.factory.makePerson()
product = self.factory.makeProduct(
- owner=owner,
- information_type=InformationType.PROPRIETARY)
+ owner=owner, information_type=InformationType.PROPRIETARY
+ )
with person_logged_in(owner):
product_name = product.name
branch = self.factory.makeBranch(
- product=product, owner=owner,
- information_type=InformationType.PROPRIETARY)
- getUtility(IService, 'sharing').ensureAccessGrants(
- [user], owner, branches=[branch])
+ product=product,
+ owner=owner,
+ information_type=InformationType.PROPRIETARY,
+ )
+ getUtility(IService, "sharing").ensureAccessGrants(
+ [user], owner, branches=[branch]
+ )
with person_logged_in(user):
url = canonical_url(branch)
# The main check: No Unauthorized error should be raised.
@@ -591,11 +615,13 @@ class TestBranchView(BrowserTestCase):
source = self.factory.makeBranch(stacked_on=stacked, product=product)
prereq = self.factory.makeBranch(product=product)
self.factory.makeBranchMergeProposal(
- source_branch=source, target_branch=branch,
- prerequisite_branch=prereq)
+ source_branch=source,
+ target_branch=branch,
+ prerequisite_branch=prereq,
+ )
Store.of(branch).flush()
Store.of(branch).invalidate()
- view = create_view(branch, '+index')
+ view = create_view(branch, "+index")
with StormStatementRecorder() as recorder:
view.landing_candidates
self.assertThat(recorder, HasQueryCount(Equals(14)))
@@ -609,11 +635,13 @@ class TestBranchView(BrowserTestCase):
target = self.factory.makeBranch(stacked_on=stacked, product=product)
prereq = self.factory.makeBranch(product=product)
self.factory.makeBranchMergeProposal(
- source_branch=branch, target_branch=target,
- prerequisite_branch=prereq)
+ source_branch=branch,
+ target_branch=target,
+ prerequisite_branch=prereq,
+ )
Store.of(branch).flush()
Store.of(branch).invalidate()
- view = create_view(branch, '+index')
+ view = create_view(branch, "+index")
with StormStatementRecorder() as recorder:
view.landing_targets
self.assertThat(recorder, HasQueryCount(Equals(13)))
@@ -625,7 +653,8 @@ class TestBranchView(BrowserTestCase):
Store.of(branch).flush()
Store.of(branch).invalidate()
view = create_initialized_view(
- branch, '+branch-portlet-subscriber-content')
+ branch, "+branch-portlet-subscriber-content"
+ )
with StormStatementRecorder() as recorder:
view.render()
self.assertThat(recorder, HasQueryCount(Equals(6)))
@@ -636,7 +665,7 @@ class TestBranchView(BrowserTestCase):
self.factory.makeBranchSubscription(branch=branch)
Store.of(branch).flush()
Store.of(branch).invalidate()
- branch_url = canonical_url(branch, view_name='+index', rootsite='code')
+ branch_url = canonical_url(branch, view_name="+index", rootsite="code")
browser = setupBrowser()
logout()
with StormStatementRecorder() as recorder:
@@ -653,10 +682,11 @@ class TestBranchRescanView(BrowserTestCase):
job = BranchScanJob.create(branch)
job.job._status = JobStatus.FAILED
branch_url = canonical_url(
- branch, view_name='+rescan', rootsite='code')
+ branch, view_name="+rescan", rootsite="code"
+ )
browser = self.getUserBrowser(branch_url, user=branch.owner)
browser.open(branch_url)
- self.assertIn('schedule a rescan', browser.contents)
+ self.assertIn("schedule a rescan", browser.contents)
def test_product_owner_can_see_rescan(self):
project_owner = self.factory.makePerson()
@@ -665,23 +695,24 @@ class TestBranchRescanView(BrowserTestCase):
job = BranchScanJob.create(branch)
job.job._status = JobStatus.FAILED
branch_url = canonical_url(
- branch, view_name='+rescan', rootsite='code')
+ branch, view_name="+rescan", rootsite="code"
+ )
browser = self.getUserBrowser(branch_url, user=project_owner)
browser.open(branch_url)
- self.assertIn('schedule a rescan', browser.contents)
+ self.assertIn("schedule a rescan", browser.contents)
def test_other_user_can_not_see_rescan(self):
branch = self.factory.makeAnyBranch()
job = BranchScanJob.create(branch)
job.job._status = JobStatus.FAILED
branch_url = canonical_url(
- branch, view_name='+rescan', rootsite='code')
- self.assertRaises(
- Unauthorized, self.getUserBrowser, branch_url)
+ branch, view_name="+rescan", rootsite="code"
+ )
+ self.assertRaises(Unauthorized, self.getUserBrowser, branch_url)
class TestBranchViewPrivateArtifacts(BrowserTestCase):
- """ Tests that branches with private team artifacts can be viewed.
+ """Tests that branches with private team artifacts can be viewed.
A Branch may be associated with a private team as follows:
- the owner is a private team
@@ -707,25 +738,27 @@ class TestBranchViewPrivateArtifacts(BrowserTestCase):
def test_view_branch_with_private_owner(self):
# A branch with a private owner is rendered.
private_owner = self.factory.makeTeam(
- displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
+ displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE
+ )
with person_logged_in(private_owner):
branch = self.factory.makeAnyBranch(owner=private_owner)
# Ensure the branch owner is rendered.
- url = canonical_url(branch, rootsite='code')
+ url = canonical_url(branch, rootsite="code")
user = self.factory.makePerson()
browser = self._getBrowser(user)
browser.open(url)
soup = BeautifulSoup(browser.contents)
- self.assertIsNotNone(soup.find('a', text="PrivateTeam"))
+ self.assertIsNotNone(soup.find("a", text="PrivateTeam"))
def test_view_private_branch_with_private_owner(self):
# A private branch with a private owner is rendered.
private_owner = self.factory.makeTeam(
- displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE)
+ displayname="PrivateTeam", visibility=PersonVisibility.PRIVATE
+ )
with person_logged_in(private_owner):
branch = self.factory.makeAnyBranch(owner=private_owner)
# Ensure the branch owner is rendered.
- url = canonical_url(branch, rootsite='code')
+ url = canonical_url(branch, rootsite="code")
user = self.factory.makePerson()
# Subscribe the user so they can see the branch.
with person_logged_in(private_owner):
@@ -733,87 +766,97 @@ class TestBranchViewPrivateArtifacts(BrowserTestCase):
browser = self._getBrowser(user)
browser.open(url)
soup = BeautifulSoup(browser.contents)
- self.assertIsNotNone(soup.find('a', text="PrivateTeam"))
+ self.assertIsNotNone(soup.find("a", text="PrivateTeam"))
def test_anonymous_view_branch_with_private_owner(self):
# A branch with a private owner is not rendered for anon users.
self.useFixture(FakeLogger())
private_owner = self.factory.makeTeam(
- visibility=PersonVisibility.PRIVATE)
+ visibility=PersonVisibility.PRIVATE
+ )
with person_logged_in(private_owner):
branch = self.factory.makeAnyBranch(owner=private_owner)
# Viewing the branch results in an error.
- url = canonical_url(branch, rootsite='code')
+ url = canonical_url(branch, rootsite="code")
browser = self._getBrowser()
self.assertRaises(NotFound, browser.open, url)
def test_view_branch_with_private_subscriber(self):
# A branch with a private subscriber is rendered.
private_subscriber = self.factory.makeTeam(
- name="privateteam", visibility=PersonVisibility.PRIVATE)
+ name="privateteam", visibility=PersonVisibility.PRIVATE
+ )
branch = self.factory.makeAnyBranch()
with person_logged_in(branch.owner):
self.factory.makeBranchSubscription(
- branch, private_subscriber, branch.owner)
+ branch, private_subscriber, branch.owner
+ )
# Ensure the branch subscriber is rendered.
- url = canonical_url(branch, rootsite='code')
+ url = canonical_url(branch, rootsite="code")
user = self.factory.makePerson()
browser = self._getBrowser(user)
browser.open(url)
soup = BeautifulSoup(browser.contents)
self.assertIsNotNone(
- soup.find('div', attrs={'id': 'subscriber-privateteam'}))
+ soup.find("div", attrs={"id": "subscriber-privateteam"})
+ )
def test_anonymous_view_branch_with_private_subscriber(self):
# Private branch subscribers are not rendered for anon users.
private_subscriber = self.factory.makeTeam(
- name="privateteam", visibility=PersonVisibility.PRIVATE)
+ name="privateteam", visibility=PersonVisibility.PRIVATE
+ )
branch = self.factory.makeAnyBranch()
with person_logged_in(private_subscriber):
self.factory.makeBranchSubscription(
- branch, private_subscriber, branch.owner)
+ branch, private_subscriber, branch.owner
+ )
# Viewing the branch doesn't show the private subscriber.
- url = canonical_url(branch, rootsite='code')
+ url = canonical_url(branch, rootsite="code")
browser = self._getBrowser()
browser.open(url)
soup = BeautifulSoup(browser.contents)
self.assertIsNone(
- soup.find('div', attrs={'id': 'subscriber-privateteam'}))
+ soup.find("div", attrs={"id": "subscriber-privateteam"})
+ )
def _createPrivateMergeProposalVotes(self):
private_reviewer = self.factory.makeTeam(
- name="privateteam", visibility=PersonVisibility.PRIVATE)
+ name="privateteam", visibility=PersonVisibility.PRIVATE
+ )
product = self.factory.makeProduct()
branch = self.factory.makeProductBranch(product=product)
target_branch = self.factory.makeProductBranch(product=product)
with person_logged_in(branch.owner):
self.factory.makeBranchMergeProposal(
- source_branch=branch, target_branch=target_branch,
- reviewer=removeSecurityProxy(private_reviewer))
+ source_branch=branch,
+ target_branch=target_branch,
+ reviewer=removeSecurityProxy(private_reviewer),
+ )
return branch
def test_view_branch_with_private_reviewer(self):
# A branch with a private reviewer is rendered.
branch = self._createPrivateMergeProposalVotes()
# Ensure the branch reviewers are rendered.
- url = canonical_url(branch, rootsite='code')
+ url = canonical_url(branch, rootsite="code")
user = self.factory.makePerson()
browser = self._getBrowser(user)
browser.open(url)
soup = BeautifulSoup(browser.contents)
- reviews_list = soup.find('dl', attrs={'class': 'reviews'})
- self.assertIsNotNone(reviews_list.find('a', text='Privateteam'))
+ reviews_list = soup.find("dl", attrs={"class": "reviews"})
+ self.assertIsNotNone(reviews_list.find("a", text="Privateteam"))
def test_anonymous_view_branch_with_private_reviewer(self):
# A branch with a private reviewer is rendered.
branch = self._createPrivateMergeProposalVotes()
# Viewing the branch doesn't show the private reviewers.
- url = canonical_url(branch, rootsite='code')
+ url = canonical_url(branch, rootsite="code")
browser = self._getBrowser()
browser.open(url)
soup = BeautifulSoup(browser.contents)
- reviews_list = soup.find('dl', attrs={'class': 'reviews'})
- self.assertIsNone(reviews_list.find('a', text='Privateteam'))
+ reviews_list = soup.find("dl", attrs={"class": "reviews"})
+ self.assertIsNone(reviews_list.find("a", text="Privateteam"))
def test_unsubscribe_private_branch(self):
# Unsubscribing from a branch with a policy grant still allows the
@@ -822,21 +865,27 @@ class TestBranchViewPrivateArtifacts(BrowserTestCase):
owner = self.factory.makePerson()
subscriber = self.factory.makePerson()
[ap] = getUtility(IAccessPolicySource).find(
- [(product, InformationType.USERDATA)])
+ [(product, InformationType.USERDATA)]
+ )
self.factory.makeAccessPolicyGrant(
- policy=ap, grantee=subscriber, grantor=product.owner)
+ policy=ap, grantee=subscriber, grantor=product.owner
+ )
branch = self.factory.makeBranch(
- product=product, owner=owner,
- information_type=InformationType.USERDATA)
+ product=product,
+ owner=owner,
+ information_type=InformationType.USERDATA,
+ )
with person_logged_in(owner):
self.factory.makeBranchSubscription(branch, subscriber, owner)
- base_url = canonical_url(branch, rootsite='code')
- expected_title = '%s : Code : %s' % (
- branch.name, product.displayname)
- url = '%s/+subscription/%s' % (base_url, subscriber.name)
+ base_url = canonical_url(branch, rootsite="code")
+ expected_title = "%s : Code : %s" % (
+ branch.name,
+ product.displayname,
+ )
+ url = "%s/+subscription/%s" % (base_url, subscriber.name)
browser = self._getBrowser(user=subscriber)
browser.open(url)
- browser.getControl('Unsubscribe').click()
+ browser.getControl("Unsubscribe").click()
self.assertEqual(base_url, browser.url)
self.assertEqual(expected_title, browser.title)
@@ -847,17 +896,19 @@ class TestBranchViewPrivateArtifacts(BrowserTestCase):
owner = self.factory.makePerson()
subscriber = self.factory.makePerson()
branch = self.factory.makeBranch(
- product=product, owner=owner,
- information_type=InformationType.USERDATA)
+ product=product,
+ owner=owner,
+ information_type=InformationType.USERDATA,
+ )
with person_logged_in(owner):
self.factory.makeBranchSubscription(branch, subscriber, owner)
- base_url = canonical_url(branch, rootsite='code')
- product_url = canonical_url(product, rootsite='code')
- url = '%s/+subscription/%s' % (base_url, subscriber.name)
+ base_url = canonical_url(branch, rootsite="code")
+ product_url = canonical_url(product, rootsite="code")
+ url = "%s/+subscription/%s" % (base_url, subscriber.name)
expected_title = "Code : %s" % product.displayname
browser = self._getBrowser(user=subscriber)
browser.open(url)
- browser.getControl('Unsubscribe').click()
+ browser.getControl("Unsubscribe").click()
self.assertEqual(product_url, browser.url)
self.assertEqual(expected_title, browser.title)
@@ -872,28 +923,29 @@ class TestBranchReviewerEditView(TestCaseWithFactory):
# the branch.
branch = self.factory.makeAnyBranch()
self.assertIs(None, branch.reviewer)
- view = create_view(branch, '+reviewer')
- self.assertEqual(branch.owner, view.initial_values['reviewer'])
+ view = create_view(branch, "+reviewer")
+ self.assertEqual(branch.owner, view.initial_values["reviewer"])
def test_initial_reviewer_set(self):
# If the reviewer has been set, it is shown as the initial value.
branch = self.factory.makeAnyBranch()
login_person(branch.owner)
branch.reviewer = self.factory.makePerson()
- view = create_view(branch, '+reviewer')
- self.assertEqual(branch.reviewer, view.initial_values['reviewer'])
+ view = create_view(branch, "+reviewer")
+ self.assertEqual(branch.reviewer, view.initial_values["reviewer"])
def test_set_reviewer(self):
# Test setting the reviewer.
branch = self.factory.makeAnyBranch()
reviewer = self.factory.makePerson()
login_person(branch.owner)
- view = create_initialized_view(branch, '+reviewer')
- view.change_action.success({'reviewer': reviewer})
+ view = create_initialized_view(branch, "+reviewer")
+ view.change_action.success({"reviewer": reviewer})
self.assertEqual(reviewer, branch.reviewer)
# Last modified has been updated.
self.assertSqlAttributeEqualsDate(
- branch, 'date_last_modified', UTC_NOW)
+ branch, "date_last_modified", UTC_NOW
+ )
def test_set_reviewer_as_owner_clears_reviewer(self):
# If the reviewer is set to be the branch owner, the review field is
@@ -901,12 +953,13 @@ class TestBranchReviewerEditView(TestCaseWithFactory):
branch = self.factory.makeAnyBranch()
login_person(branch.owner)
branch.reviewer = self.factory.makePerson()
- view = create_initialized_view(branch, '+reviewer')
- view.change_action.success({'reviewer': branch.owner})
+ view = create_initialized_view(branch, "+reviewer")
+ view.change_action.success({"reviewer": branch.owner})
self.assertIs(None, branch.reviewer)
# Last modified has been updated.
self.assertSqlAttributeEqualsDate(
- branch, 'date_last_modified', UTC_NOW)
+ branch, "date_last_modified", UTC_NOW
+ )
def test_set_reviewer_to_same_does_not_update_last_modified(self):
# If the user has set the reviewer to be same and clicked on save,
@@ -914,8 +967,8 @@ class TestBranchReviewerEditView(TestCaseWithFactory):
# modified is not updated.
modified_date = datetime(2007, 1, 1, tzinfo=pytz.UTC)
branch = self.factory.makeAnyBranch(date_created=modified_date)
- view = create_initialized_view(branch, '+reviewer')
- view.change_action.success({'reviewer': branch.owner})
+ view = create_initialized_view(branch, "+reviewer")
+ view.change_action.success({"reviewer": branch.owner})
self.assertIs(None, branch.reviewer)
# Last modified has not been updated.
self.assertEqual(modified_date, branch.date_last_modified)
@@ -936,7 +989,8 @@ class TestBranchBzrIdentity(TestCaseWithFactory):
login_person(product.owner)
product.development_focus.branch = branch
view = create_initialized_view(
- branch.owner, '+branches', rootsite='code')
+ branch.owner, "+branches", rootsite="code"
+ )
navigator = view.branches()
[decorated_branch] = navigator.branches
self.assertEqual("lp://dev/fooix", decorated_branch.bzr_identity)
@@ -952,7 +1006,7 @@ class TestBranchProposalsVisible(TestCaseWithFactory):
# landing_target is available for the template rendering.
bmp = self.factory.makeBranchMergeProposal()
branch = bmp.source_branch
- view = create_view(branch, '+index')
+ view = create_view(branch, "+index")
self.assertFalse(view.no_merges)
[target] = view.landing_targets
# Check the ids as the target is a DecoratedMergeProposal.
@@ -962,9 +1016,10 @@ class TestBranchProposalsVisible(TestCaseWithFactory):
# If the target is private, the landing targets should not include it.
bmp = self.factory.makeBranchMergeProposal()
branch = bmp.source_branch
- removeSecurityProxy(bmp.target_branch).information_type = (
- InformationType.USERDATA)
- view = create_view(branch, '+index')
+ removeSecurityProxy(
+ bmp.target_branch
+ ).information_type = InformationType.USERDATA
+ view = create_view(branch, "+index")
self.assertTrue(view.no_merges)
self.assertEqual([], view.landing_targets)
@@ -973,7 +1028,7 @@ class TestBranchProposalsVisible(TestCaseWithFactory):
# landing_candidate is available for the template rendering.
bmp = self.factory.makeBranchMergeProposal()
branch = bmp.target_branch
- view = create_view(branch, '+index')
+ view = create_view(branch, "+index")
self.assertFalse(view.no_merges)
[candidate] = view.landing_candidates
# Check the ids as the target is a DecoratedMergeProposal.
@@ -984,9 +1039,10 @@ class TestBranchProposalsVisible(TestCaseWithFactory):
# it.
bmp = self.factory.makeBranchMergeProposal()
branch = bmp.target_branch
- removeSecurityProxy(bmp.source_branch).information_type = (
- InformationType.USERDATA)
- view = create_view(branch, '+index')
+ removeSecurityProxy(
+ bmp.source_branch
+ ).information_type = InformationType.USERDATA
+ view = create_view(branch, "+index")
self.assertTrue(view.no_merges)
self.assertEqual([], view.landing_candidates)
@@ -995,7 +1051,7 @@ class TestBranchProposalsVisible(TestCaseWithFactory):
# there are merges.
branch = self.factory.makeProductBranch()
bmp = self.factory.makeBranchMergeProposal(prerequisite_branch=branch)
- view = create_view(branch, '+index')
+ view = create_view(branch, "+index")
self.assertFalse(view.no_merges)
[proposal] = view.dependent_branches
self.assertEqual(bmp, proposal)
@@ -1005,9 +1061,10 @@ class TestBranchProposalsVisible(TestCaseWithFactory):
# the target is private, then the dependent_branches are not shown.
branch = self.factory.makeProductBranch()
bmp = self.factory.makeBranchMergeProposal(prerequisite_branch=branch)
- removeSecurityProxy(bmp.source_branch).information_type = (
- InformationType.USERDATA)
- view = create_view(branch, '+index')
+ removeSecurityProxy(
+ bmp.source_branch
+ ).information_type = InformationType.USERDATA
+ view = create_view(branch, "+index")
self.assertTrue(view.no_merges)
self.assertEqual([], view.dependent_branches)
@@ -1021,8 +1078,8 @@ class TestBranchEditView(TestCaseWithFactory):
person = self.factory.makePerson()
branch = self.factory.makePersonalBranch(owner=person)
login_person(person)
- view = create_initialized_view(branch, name='+edit')
- self.assertEqual('personal', view.widgets['target'].default_option)
+ view = create_initialized_view(branch, name="+edit")
+ self.assertEqual("personal", view.widgets["target"].default_option)
def test_branch_target_widget_renders_product(self):
# The branch target widget renders correctly for a product branch.
@@ -1030,18 +1087,19 @@ class TestBranchEditView(TestCaseWithFactory):
product = self.factory.makeProduct()
branch = self.factory.makeProductBranch(product=product, owner=person)
login_person(person)
- view = create_initialized_view(branch, name='+edit')
- self.assertEqual('product', view.widgets['target'].default_option)
+ view = create_initialized_view(branch, name="+edit")
+ self.assertEqual("product", view.widgets["target"].default_option)
self.assertEqual(
- product.name, view.widgets['target'].product_widget.selected_value)
+ product.name, view.widgets["target"].product_widget.selected_value
+ )
def test_no_branch_target_widget_for_source_package_branch(self):
# The branch target widget is not rendered for a package branch.
person = self.factory.makePerson()
branch = self.factory.makePackageBranch(owner=person)
login_person(person)
- view = create_initialized_view(branch, name='+edit')
- self.assertIsNone(view.widgets.get('target'))
+ view = create_initialized_view(branch, name="+edit")
+ self.assertIsNone(view.widgets.get("target"))
def test_branch_target_widget_saves_junk(self):
# The branch target widget can retarget to a junk branch.
@@ -1050,34 +1108,36 @@ class TestBranchEditView(TestCaseWithFactory):
branch = self.factory.makeProductBranch(product=product, owner=person)
login_person(person)
form = {
- 'field.target': 'personal',
- 'field.actions.change': 'Change Branch',
+ "field.target": "personal",
+ "field.actions.change": "Change Branch",
}
- view = create_initialized_view(branch, name='+edit', form=form)
+ view = create_initialized_view(branch, name="+edit", form=form)
self.assertEqual(person, branch.target.context)
self.assertEqual(1, len(view.request.response.notifications))
self.assertEqual(
- 'This branch is now a personal branch for %s (%s)'
- % (person.displayname, person.name),
- view.request.response.notifications[0].message)
+ "This branch is now a personal branch for %s (%s)"
+ % (person.displayname, person.name),
+ view.request.response.notifications[0].message,
+ )
def test_save_to_different_junk(self):
# The branch target widget can retarget to a junk branch.
person = self.factory.makePerson()
branch = self.factory.makePersonalBranch(owner=person)
- new_owner = self.factory.makeTeam(name='newowner', members=[person])
+ new_owner = self.factory.makeTeam(name="newowner", members=[person])
login_person(person)
form = {
- 'field.target': 'personal',
- 'field.owner': 'newowner',
- 'field.actions.change': 'Change Branch',
+ "field.target": "personal",
+ "field.owner": "newowner",
+ "field.actions.change": "Change Branch",
}
- view = create_initialized_view(branch, name='+edit', form=form)
+ view = create_initialized_view(branch, name="+edit", form=form)
self.assertEqual(new_owner, branch.target.context)
self.assertEqual(1, len(view.request.response.notifications))
self.assertEqual(
- 'The branch owner has been changed to Newowner (newowner)',
- view.request.response.notifications[0].message)
+ "The branch owner has been changed to Newowner (newowner)",
+ view.request.response.notifications[0].message,
+ )
def test_branch_target_widget_saves_product(self):
# The branch target widget can retarget to a product branch.
@@ -1086,16 +1146,17 @@ class TestBranchEditView(TestCaseWithFactory):
product = self.factory.makeProduct()
login_person(person)
form = {
- 'field.target': 'product',
- 'field.target.product': product.name,
- 'field.actions.change': 'Change Branch',
+ "field.target": "product",
+ "field.target.product": product.name,
+ "field.actions.change": "Change Branch",
}
- view = create_initialized_view(branch, name='+edit', form=form)
+ view = create_initialized_view(branch, name="+edit", form=form)
self.assertEqual(product, branch.target.context)
self.assertEqual(
- 'The branch target has been changed to %s (%s)'
- % (product.displayname, product.name),
- view.request.response.notifications[0].message)
+ "The branch target has been changed to %s (%s)"
+ % (product.displayname, product.name),
+ view.request.response.notifications[0].message,
+ )
def test_forbidden_target_is_error(self):
# An error is displayed if a branch is saved with a target that is not
@@ -1103,18 +1164,24 @@ class TestBranchEditView(TestCaseWithFactory):
owner = self.factory.makePerson()
initial_target = self.factory.makeProduct()
self.factory.makeProduct(
- name="commercial", owner=owner,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ name="commercial",
+ owner=owner,
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
branch = self.factory.makeProductBranch(
- owner=owner, product=initial_target,
- information_type=InformationType.PUBLIC)
+ owner=owner,
+ product=initial_target,
+ information_type=InformationType.PUBLIC,
+ )
browser = self.getUserBrowser(
- canonical_url(branch) + '/+edit', user=owner)
+ canonical_url(branch) + "/+edit", user=owner
+ )
browser.getControl(name="field.target.product").value = "commercial"
browser.getControl("Change Branch").click()
self.assertThat(
browser.contents,
- Contains('Public branches are not allowed for target Commercial.'))
+ Contains("Public branches are not allowed for target Commercial."),
+ )
with person_logged_in(owner):
self.assertEqual(initial_target, branch.target.context)
@@ -1126,7 +1193,8 @@ class TestBranchEditView(TestCaseWithFactory):
admins = getUtility(ILaunchpadCelebrities).admin
admin = admins.teamowner
browser = self.getUserBrowser(
- canonical_url(branch) + '/+edit', user=admin)
+ canonical_url(branch) + "/+edit", user=admin
+ )
browser.getControl("Private", index=1).click()
browser.getControl("Change Branch").click()
with person_logged_in(person):
@@ -1138,13 +1206,17 @@ class TestBranchEditView(TestCaseWithFactory):
owner = self.factory.makePerson()
product = self.factory.makeProduct(owner=owner)
stacked_on = self.factory.makeBranch(
- product=product, owner=owner,
- information_type=InformationType.USERDATA)
+ product=product,
+ owner=owner,
+ information_type=InformationType.USERDATA,
+ )
branch = self.factory.makeBranch(
- product=product, owner=owner, stacked_on=stacked_on)
+ product=product, owner=owner, stacked_on=stacked_on
+ )
with person_logged_in(owner):
browser = self.getUserBrowser(
- canonical_url(branch) + '/+edit', user=owner)
+ canonical_url(branch) + "/+edit", user=owner
+ )
self.assertRaises(LookupError, browser.getControl, "Information Type")
def test_edit_view_ajax_render(self):
@@ -1153,21 +1225,28 @@ class TestBranchEditView(TestCaseWithFactory):
person = self.factory.makePerson()
branch = self.factory.makeProductBranch(owner=person)
- extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+ extra = {"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}
request = LaunchpadTestRequest(
- method='POST', form={
- 'field.actions.change': 'Change Branch',
- 'field.information_type': 'PUBLICSECURITY'},
- **extra)
+ method="POST",
+ form={
+ "field.actions.change": "Change Branch",
+ "field.information_type": "PUBLICSECURITY",
+ },
+ **extra,
+ )
with person_logged_in(person):
view = create_initialized_view(
- branch, name='+edit-information-type',
- request=request, principal=person)
+ branch,
+ name="+edit-information-type",
+ request=request,
+ principal=person,
+ )
request.traversed_objects = [person, branch.product, branch, view]
result = view.render()
- self.assertEqual('', result)
+ self.assertEqual("", result)
self.assertEqual(
- branch.information_type, InformationType.PUBLICSECURITY)
+ branch.information_type, InformationType.PUBLICSECURITY
+ )
class TestBranchEditViewInformationTypes(TestCaseWithFactory):
@@ -1179,7 +1258,7 @@ class TestBranchEditViewInformationTypes(TestCaseWithFactory):
if user is None:
user = removeSecurityProxy(branch).owner
with person_logged_in(user):
- view = create_initialized_view(branch, '+edit', principal=user)
+ view = create_initialized_view(branch, "+edit", principal=user)
self.assertContentEqual(types, view.getInformationTypesToShow())
def test_public_branch(self):
@@ -1187,11 +1266,17 @@ class TestBranchEditViewInformationTypes(TestCaseWithFactory):
# type except embargoed and proprietary.
# The model doesn't enforce this, so it's just a UI thing.
branch = self.factory.makeBranch(
- information_type=InformationType.PUBLIC)
+ information_type=InformationType.PUBLIC
+ )
self.assertShownTypes(
- [InformationType.PUBLIC, InformationType.PUBLICSECURITY,
- InformationType.PRIVATESECURITY, InformationType.USERDATA],
- branch)
+ [
+ InformationType.PUBLIC,
+ InformationType.PUBLICSECURITY,
+ InformationType.PRIVATESECURITY,
+ InformationType.USERDATA,
+ ],
+ branch,
+ )
def test_branch_with_disallowed_type(self):
# We don't force branches with a disallowed type (eg. Proprietary on a
@@ -1200,25 +1285,35 @@ class TestBranchEditViewInformationTypes(TestCaseWithFactory):
product = self.factory.makeProduct()
self.factory.makeAccessPolicy(pillar=product)
branch = self.factory.makeBranch(
- product=product, information_type=InformationType.PROPRIETARY)
+ product=product, information_type=InformationType.PROPRIETARY
+ )
self.assertShownTypes(
- [InformationType.PUBLIC, InformationType.PUBLICSECURITY,
- InformationType.PRIVATESECURITY, InformationType.USERDATA,
- InformationType.PROPRIETARY], branch)
+ [
+ InformationType.PUBLIC,
+ InformationType.PUBLICSECURITY,
+ InformationType.PRIVATESECURITY,
+ InformationType.USERDATA,
+ InformationType.PROPRIETARY,
+ ],
+ branch,
+ )
def test_stacked_on_private(self):
# A branch stacked on a private branch has its choices limited
# to the current type and the stacked-on type.
product = self.factory.makeProduct()
stacked_on_branch = self.factory.makeBranch(
- product=product, information_type=InformationType.USERDATA)
+ product=product, information_type=InformationType.USERDATA
+ )
branch = self.factory.makeBranch(
- product=product, stacked_on=stacked_on_branch,
+ product=product,
+ stacked_on=stacked_on_branch,
owner=product.owner,
- information_type=InformationType.PRIVATESECURITY)
+ information_type=InformationType.PRIVATESECURITY,
+ )
self.assertShownTypes(
- [InformationType.PRIVATESECURITY, InformationType.USERDATA],
- branch)
+ [InformationType.PRIVATESECURITY, InformationType.USERDATA], branch
+ )
def test_branch_for_project_with_embargoed_and_proprietary(self):
# Branches for commercial projects which have a policy of embargoed or
@@ -1228,12 +1323,16 @@ class TestBranchEditViewInformationTypes(TestCaseWithFactory):
self.factory.makeCommercialSubscription(pillar=product)
with person_logged_in(owner):
product.setBranchSharingPolicy(
- BranchSharingPolicy.EMBARGOED_OR_PROPRIETARY)
+ BranchSharingPolicy.EMBARGOED_OR_PROPRIETARY
+ )
branch = self.factory.makeBranch(
- product=product, owner=owner,
- information_type=InformationType.PROPRIETARY)
+ product=product,
+ owner=owner,
+ information_type=InformationType.PROPRIETARY,
+ )
self.assertShownTypes(
- [InformationType.EMBARGOED, InformationType.PROPRIETARY], branch)
+ [InformationType.EMBARGOED, InformationType.PROPRIETARY], branch
+ )
def test_branch_for_project_with_proprietary(self):
# Branches for commercial projects which have a policy of proprietary
@@ -1244,8 +1343,10 @@ class TestBranchEditViewInformationTypes(TestCaseWithFactory):
with person_logged_in(owner):
product.setBranchSharingPolicy(BranchSharingPolicy.PROPRIETARY)
branch = self.factory.makeBranch(
- product=product, owner=owner,
- information_type=InformationType.PROPRIETARY)
+ product=product,
+ owner=owner,
+ information_type=InformationType.PROPRIETARY,
+ )
self.assertShownTypes([InformationType.PROPRIETARY], branch)
@@ -1256,17 +1357,20 @@ class TestBranchUpgradeView(TestCaseWithFactory):
def test_upgrade_branch_action_cannot_upgrade(self):
# A nice error is displayed if a branch cannot be upgraded.
branch = self.factory.makePersonalBranch(
- branch_format=BranchFormat.BZR_BRANCH_6,
- repository_format=RepositoryFormat.BZR_CHK_2A)
+ branch_format=BranchFormat.BZR_BRANCH_6,
+ repository_format=RepositoryFormat.BZR_CHK_2A,
+ )
login_person(branch.owner)
self.addCleanup(logout)
branch.requestUpgrade(branch.owner)
- view = create_initialized_view(branch, '+upgrade')
+ view = create_initialized_view(branch, "+upgrade")
view.upgrade_branch_action.success({})
self.assertEqual(1, len(view.request.notifications))
self.assertEqual(
- 'An upgrade is already in progress for branch %s.' %
- branch.bzr_identity, view.request.notifications[0].message)
+ "An upgrade is already in progress for branch %s."
+ % branch.bzr_identity,
+ view.request.notifications[0].message,
+ )
class TestBranchPrivacyPortlet(TestCaseWithFactory):
@@ -1277,20 +1381,23 @@ class TestBranchPrivacyPortlet(TestCaseWithFactory):
# The privacy portlet shows the information_type.
owner = self.factory.makePerson()
branch = self.factory.makeBranch(
- owner=owner, information_type=InformationType.USERDATA)
+ owner=owner, information_type=InformationType.USERDATA
+ )
with person_logged_in(owner):
- view = create_initialized_view(branch, '+portlet-privacy')
- edit_url = '/' + branch.unique_name + '/+edit-information-type'
+ view = create_initialized_view(branch, "+portlet-privacy")
+ edit_url = "/" + branch.unique_name + "/+edit-information-type"
soup = BeautifulSoup(view.render())
- information_type = soup.find('strong')
- description = soup.find('div', id='information-type-description')
+ information_type = soup.find("strong")
+ description = soup.find("div", id="information-type-description")
self.assertEqual(
- InformationType.USERDATA.title, information_type.decode_contents())
+ InformationType.USERDATA.title, information_type.decode_contents()
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
- InformationType.USERDATA.description,
- description.decode_contents())
+ InformationType.USERDATA.description, description.decode_contents()
+ )
self.assertIsNotNone(
- soup.find('a', id='privacy-link', attrs={'href': edit_url}))
+ soup.find("a", id="privacy-link", attrs={"href": edit_url})
+ )
class TestBranchDiffView(BrowserTestCase):
@@ -1308,7 +1415,8 @@ class TestBranchDiffView(BrowserTestCase):
browser.open(branch_url + "/+diff/2/1")
self.assertEqual(401, int(browser.headers["Status"].split(" ", 1)[0]))
self.assertEqual(
- "Proxying of branch diffs is disabled.\n", browser.contents)
+ "Proxying of branch diffs is disabled.\n", browser.contents
+ )
self.assertEqual([], hosting_fixture.getDiff.calls)
def test_render(self):
@@ -1316,17 +1424,18 @@ class TestBranchDiffView(BrowserTestCase):
hosting_fixture = self.useFixture(BranchHostingFixture(diff=diff))
person = self.factory.makePerson()
branch = self.factory.makeBranch(owner=person, name="some-branch")
- browser = self.getUserBrowser(
- canonical_url(branch) + "/+diff/2/1")
+ browser = self.getUserBrowser(canonical_url(branch) + "/+diff/2/1")
with person_logged_in(person):
self.assertEqual(
[((branch.id, "2"), {"old": "1"})],
- hosting_fixture.getDiff.calls)
+ hosting_fixture.getDiff.calls,
+ )
self.assertEqual("text/x-patch", browser.headers["Content-Type"])
self.assertEqual(str(len(diff)), browser.headers["Content-Length"])
self.assertEqual(
'attachment; filename="some-branch_1_2.diff"',
- browser.headers["Content-Disposition"])
+ browser.headers["Content-Disposition"],
+ )
self.assertEqual(diff, browser.contents)
def test_security(self):
@@ -1336,17 +1445,21 @@ class TestBranchDiffView(BrowserTestCase):
self.useFixture(BranchHostingFixture(diff=diff))
person = self.factory.makePerson()
project = self.factory.makeProduct(
- owner=person, information_type=InformationType.PROPRIETARY)
+ owner=person, information_type=InformationType.PROPRIETARY
+ )
with person_logged_in(person):
branch = self.factory.makeBranch(
- owner=person, product=project,
- information_type=InformationType.PROPRIETARY)
+ owner=person,
+ product=project,
+ information_type=InformationType.PROPRIETARY,
+ )
branch_url = canonical_url(branch)
browser = self.getUserBrowser(branch_url + "/+diff/2/1", user=person)
self.as