← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] ~cjwatson/launchpad:black-app into launchpad:master

 

Colin Watson has proposed merging ~cjwatson/launchpad:black-app into launchpad:master.

Commit message:
lp.app: Apply black

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/424957
-- 
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-app into launchpad:master.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 21314b8..b57c076 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -56,3 +56,5 @@ afcfc15adcf3267d3fd07d7679df00231853c908
 c606443bdb2f342593c9a7c9437cb70c01f85f29
 # apply black to lp.answers
 7ae201d4e317cc9db665a0edb28c2439797daff6
+# apply black to lp.app
+8fd124775592a33c3d2ce9ef8111a9a5f1a5e089
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c9d3f02..7d5c58e 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -42,6 +42,7 @@ repos:
         files: |
           (?x)^lib/lp/(
             answers
+            |app
           )/
 -   repo: https://github.com/PyCQA/isort
     rev: 5.9.2
@@ -60,6 +61,7 @@ repos:
         exclude: |
           (?x)^lib/lp/(
             answers
+            |app
           )/
     -   id: isort
         alias: isort-black
@@ -68,6 +70,7 @@ repos:
         files: |
           (?x)^lib/lp/(
             answers
+            |app
           )/
 -   repo: https://github.com/PyCQA/flake8
     rev: 3.9.2
diff --git a/lib/lp/app/__init__.py b/lib/lp/app/__init__.py
index 0276aec..69ebb95 100644
--- a/lib/lp/app/__init__.py
+++ b/lib/lp/app/__init__.py
@@ -19,5 +19,4 @@ from zope.formlib import itemswidgets
 # for first page load.
 import lp.app.versioninfo  # noqa: F401
 
-
 itemswidgets.EXPLICIT_EMPTY_SELECTION = False
diff --git a/lib/lp/app/browser/badge.py b/lib/lp/app/browser/badge.py
index 2dcf6ba..d3fd5a6 100644
--- a/lib/lp/app/browser/badge.py
+++ b/lib/lp/app/browser/badge.py
@@ -9,16 +9,13 @@ Badges are shown in two main places:
 """
 
 __all__ = [
-    'Badge',
-    'HasBadgeBase',
-    'IHasBadges',
-    'STANDARD_BADGES',
-    ]
+    "Badge",
+    "HasBadgeBase",
+    "IHasBadges",
+    "STANDARD_BADGES",
+]
 
-from zope.interface import (
-    implementer,
-    Interface,
-    )
+from zope.interface import Interface, implementer
 
 from lp.services.privacy.interfaces import IObjectPrivacy
 
@@ -30,8 +27,9 @@ class Badge:
     views, and as larger images on the object content pages.
     """
 
-    def __init__(self, icon_image=None, heading_image=None,
-                 alt='', title='', id=''):
+    def __init__(
+        self, icon_image=None, heading_image=None, alt="", title="", id=""
+    ):
         self.small_image = icon_image
         self.large_image = heading_image
         self.alt = alt
@@ -39,17 +37,19 @@ class Badge:
         self.id = id
 
     def copy(self):
-        return Badge(self.small_image, self.large_image, self.alt,
-                     self.title, self.id)
+        return Badge(
+            self.small_image, self.large_image, self.alt, self.title, self.id
+        )
 
     def renderIconImage(self):
         """Render the small image as an HTML img tag."""
         if self.small_image:
-            return ('<img alt="%s" width="14" height="14" src="%s"'
-                    ' title="%s"/>'
-                    % (self.alt, self.small_image, self.title))
+            return (
+                '<img alt="%s" width="14" height="14" src="%s"'
+                ' title="%s"/>' % (self.alt, self.small_image, self.title)
+            )
         else:
-            return ''
+            return ""
 
     def renderHeadingImage(self):
         """Render the large image as an HTML img tag."""
@@ -57,33 +57,56 @@ class Badge:
             if self.id:
                 id_attribute = 'id="%s"' % self.id
             else:
-                id_attribute = ''
-            return ('<img alt="%s" width="32" height="32" src="%s"'
-                    ' title="%s" %s/>' % (
-                    self.alt, self.large_image, self.title, id_attribute))
+                id_attribute = ""
+            return (
+                '<img alt="%s" width="32" height="32" src="%s"'
+                ' title="%s" %s/>'
+                % (self.alt, self.large_image, self.title, id_attribute)
+            )
         else:
-            return ''
+            return ""
 
 
 STANDARD_BADGES = {
-    'bug': Badge('/@@/bug', '/@@/bug-large',
-                 'bug', 'Linked to a bug', 'bugbadge'),
-    'blueprint': Badge('/@@/blueprint', None,
-                       '(Linked to a blueprint)', 'Linked to a blueprint'),
-    'branch': Badge('/@@/branch', '/@@/branch-large',
-                    '(Linked to a branch)', 'Linked to a branch',
-                    'branchbadge'),
-    'private': Badge('/@@/private', '/@@/private-large',
-                     '(Private)', 'Private', 'privatebadge'),
-    'security': Badge('/@@/security', '/@@/security-large',
-                      '(Security vulnerability)', 'Security vulnerability',
-                      'securitybadge'),
-    'mergeproposal': Badge('/@@/merge-proposal-icon',
-                           '/@@/merge-proposal-large',
-                           '(Has a merge proposal)', 'Has a merge proposal',
-                           'mpbadge'),
-    'patch': Badge(None, None, '(Has a patch)', 'Has a patch', 'haspatch'),
-    }
+    "bug": Badge(
+        "/@@/bug", "/@@/bug-large", "bug", "Linked to a bug", "bugbadge"
+    ),
+    "blueprint": Badge(
+        "/@@/blueprint",
+        None,
+        "(Linked to a blueprint)",
+        "Linked to a blueprint",
+    ),
+    "branch": Badge(
+        "/@@/branch",
+        "/@@/branch-large",
+        "(Linked to a branch)",
+        "Linked to a branch",
+        "branchbadge",
+    ),
+    "private": Badge(
+        "/@@/private",
+        "/@@/private-large",
+        "(Private)",
+        "Private",
+        "privatebadge",
+    ),
+    "security": Badge(
+        "/@@/security",
+        "/@@/security-large",
+        "(Security vulnerability)",
+        "Security vulnerability",
+        "securitybadge",
+    ),
+    "mergeproposal": Badge(
+        "/@@/merge-proposal-icon",
+        "/@@/merge-proposal-large",
+        "(Has a merge proposal)",
+        "Has a merge proposal",
+        "mpbadge",
+    ),
+    "patch": Badge(None, None, "(Has a patch)", "Has a patch", "haspatch"),
+}
 
 
 class IHasBadges(Interface):
@@ -97,8 +120,7 @@ class IHasBadges(Interface):
     """
 
     def getVisibleBadges():
-        """Return a list of `Badge` objects that the logged in user can see.
-        """
+        """Return a list of `Badge` objects that the logged in user can see."""
 
 
 @implementer(IHasBadges)
@@ -113,7 +135,7 @@ class HasBadgeBase:
     """
 
     # All private objects should show the private badge.
-    badges = ('private',)
+    badges = ("private",)
 
     # This class is now a default adapter for IHasBadges.
     def __init__(self, context):
diff --git a/lib/lp/app/browser/folder.py b/lib/lp/app/browser/folder.py
index d38a5be..ad4c620 100644
--- a/lib/lp/app/browser/folder.py
+++ b/lib/lp/app/browser/folder.py
@@ -2,9 +2,9 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'ExportedFolder',
-    'ExportedImageFolder',
-    ]
+    "ExportedFolder",
+    "ExportedImageFolder",
+]
 
 import errno
 import os
@@ -27,7 +27,7 @@ class File:
     def __init__(self, path, name):
         self.path = path
 
-        f = open(path, 'rb')
+        f = open(path, "rb")
         self.data = f.read()
         f.close()
         self.content_type, enc = guess_content_type(path, self.data)
@@ -50,7 +50,7 @@ class ExportedFolder:
     to True to change this.
     """
 
-    rev_part_re = re.compile('rev[0-9a-f]+$')
+    rev_part_re = re.compile("rev[0-9a-f]+$")
 
     export_subdirectories = False
 
@@ -68,7 +68,7 @@ class ExportedFolder:
 
         if not names:
             # Just the root directory, so make this a 404.
-            raise NotFound(self, '')
+            raise NotFound(self, "")
         elif len(names) > 1 and not self.export_subdirectories:
             # Too many path elements, so make this a 404.
             raise NotFound(self, self.names[-1])
@@ -78,7 +78,8 @@ class ExportedFolder:
             # because the Zope name traversal will sanitize './' and '../'
             # before setting the value of self.names.
             return self.prepareDataForServing(
-                os.path.join(self.folder, *names))
+                os.path.join(self.folder, *names)
+            )
 
     def prepareDataForServing(self, filename):
         """Set the response headers and return the data for this resource."""
@@ -97,8 +98,8 @@ class ExportedFolder:
         # TODO: Set an appropriate charset too.  There may be zope code we
         #       can reuse for this.
         response = self.request.response
-        response.setHeader('Content-Type', fileobj.content_type)
-        response.setHeader('Last-Modified', fileobj.lmh)
+        response.setHeader("Content-Type", fileobj.content_type)
+        response.setHeader("Last-Modified", fileobj.lmh)
         setCacheControl(response)
         return fileobj.data
 
@@ -110,8 +111,9 @@ class ExportedFolder:
         """Traverse to the given name."""
         # The two following constraints are enforced by the publisher.
         assert os.path.sep not in name, (
-            'traversed name contains os.path.sep: %s' % name)
-        assert name != '..', 'traversing to ..'
+            "traversed name contains os.path.sep: %s" % name
+        )
+        assert name != "..", "traversing to .."
         self.names.append(name)
         return self
 
@@ -122,7 +124,8 @@ class ExportedFolder:
     def folder(self):
         raise (
             NotImplementedError,
-            'Your subclass of ExportedFolder should have its own folder.')
+            "Your subclass of ExportedFolder should have its own folder.",
+        )
 
 
 class ExportedImageFolder(ExportedFolder):
@@ -133,7 +136,7 @@ class ExportedImageFolder(ExportedFolder):
     """
 
     # The extensions we consider.
-    image_extensions = ('.png', '.gif')
+    image_extensions = (".png", ".gif")
 
     def prepareDataForServing(self, filename):
         """Serve files without their extension.
@@ -142,7 +145,7 @@ class ExportedImageFolder(ExportedFolder):
         the same base name and has an image extension, it will be served.
         """
         root, ext = os.path.splitext(filename)
-        if ext == '' and not os.path.exists(root):
+        if ext == "" and not os.path.exists(root):
             for image_ext in self.image_extensions:
                 if os.path.exists(root + image_ext):
                     filename = filename + image_ext
diff --git a/lib/lp/app/browser/informationtype.py b/lib/lp/app/browser/informationtype.py
index f2848b4..034b6e3 100644
--- a/lib/lp/app/browser/informationtype.py
+++ b/lib/lp/app/browser/informationtype.py
@@ -2,8 +2,8 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'InformationTypePortletMixin',
-    ]
+    "InformationTypePortletMixin",
+]
 
 from lazr.restful.interfaces import IJSONRequestCache
 
@@ -13,7 +13,6 @@ from lp.app.utilities import json_dump_information_types
 
 
 class InformationTypePortletMixin:
-
     def _getContext(self):
         information_typed = IInformationType(self.context, None)
         if information_typed is None:
@@ -25,8 +24,8 @@ class InformationTypePortletMixin:
         if IInformationType.providedBy(context):
             cache = IJSONRequestCache(self.request)
             json_dump_information_types(
-                cache,
-                context.getAllowedInformationTypes(self.user))
+                cache, context.getAllowedInformationTypes(self.user)
+            )
 
     @property
     def information_type(self):
@@ -45,17 +44,21 @@ class InformationTypePortletMixin:
     @property
     def information_type_css(self):
         context = self._getContext()
-        if (IInformationType.providedBy(context) and
-            context.information_type in PRIVATE_INFORMATION_TYPES):
-            return 'sprite private'
+        if (
+            IInformationType.providedBy(context)
+            and context.information_type in PRIVATE_INFORMATION_TYPES
+        ):
+            return "sprite private"
         else:
-            return 'sprite public'
+            return "sprite public"
 
     @property
     def privacy_portlet_css(self):
         context = self._getContext()
-        if (IInformationType.providedBy(context) and
-            context.information_type in PRIVATE_INFORMATION_TYPES):
-            return 'portlet private'
+        if (
+            IInformationType.providedBy(context)
+            and context.information_type in PRIVATE_INFORMATION_TYPES
+        ):
+            return "portlet private"
         else:
-            return 'portlet public'
+            return "portlet public"
diff --git a/lib/lp/app/browser/launchpad.py b/lib/lp/app/browser/launchpad.py
index c887a24..b6f178e 100644
--- a/lib/lp/app/browser/launchpad.py
+++ b/lib/lp/app/browser/launchpad.py
@@ -4,32 +4,29 @@
 """Browser code for the launchpad application."""
 
 __all__ = [
-    'AppFrontPageSearchView',
-    'ExceptionHierarchy',
-    'Hierarchy',
-    'IcingFolder',
-    'iter_view_registrations',
-    'LaunchpadImageFolder',
-    'LaunchpadRootNavigation',
-    'LinkView',
-    'LoginStatus',
-    'Macro',
-    'MaintenanceMessage',
-    'NavigationMenuTabs',
-    'SoftTimeoutView',
-    'get_launchpad_views',
-    ]
+    "AppFrontPageSearchView",
+    "ExceptionHierarchy",
+    "Hierarchy",
+    "IcingFolder",
+    "iter_view_registrations",
+    "LaunchpadImageFolder",
+    "LaunchpadRootNavigation",
+    "LinkView",
+    "LoginStatus",
+    "Macro",
+    "MaintenanceMessage",
+    "NavigationMenuTabs",
+    "SoftTimeoutView",
+    "get_launchpad_views",
+]
 
 
-from datetime import timedelta
 import operator
 import os
 import re
 import time
-from urllib.parse import (
-    parse_qs,
-    urlencode,
-    )
+from datetime import timedelta
+from urllib.parse import parse_qs, urlencode
 
 from zope import i18n
 from zope.component import (
@@ -37,53 +34,31 @@ from zope.component import (
     getUtility,
     queryAdapter,
     queryUtility,
-    )
-from zope.datetime import (
-    DateTimeError,
-    parseDatetimetz,
-    )
+)
+from zope.datetime import DateTimeError, parseDatetimetz
 from zope.i18nmessageid import Message
-from zope.interface import (
-    alsoProvides,
-    implementer,
-    Interface,
-    )
+from zope.interface import Interface, alsoProvides, implementer
 from zope.publisher.defaultview import getDefaultViewName
 from zope.publisher.interfaces import NotFound
 from zope.publisher.interfaces.browser import IBrowserPublisher
 from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
-from zope.schema import (
-    Choice,
-    TextLine,
-    )
+from zope.schema import Choice, TextLine
 from zope.security.interfaces import Unauthorized
 from zope.security.proxy import removeSecurityProxy
-from zope.traversing.interfaces import (
-    IPathAdapter,
-    ITraversable,
-    )
+from zope.traversing.interfaces import IPathAdapter, ITraversable
 
 from lp import _
 from lp.answers.interfaces.questioncollection import IQuestionSet
-from lp.app.browser.folder import (
-    ExportedFolder,
-    ExportedImageFolder,
-    )
+from lp.app.browser.folder import ExportedFolder, ExportedImageFolder
 from lp.app.browser.launchpadform import LaunchpadFormView
-from lp.app.browser.tales import (
-    DurationFormatterAPI,
-    MenuAPI,
-    )
+from lp.app.browser.tales import DurationFormatterAPI, MenuAPI
 from lp.app.errors import (
     GoneError,
     NameLookupFailed,
     NotFoundError,
     POSTToNonCanonicalURL,
-    )
-from lp.app.interfaces.headings import (
-    IHeadingBreadcrumb,
-    IMajorHeadingView,
-    )
+)
+from lp.app.interfaces.headings import IHeadingBreadcrumb, IMajorHeadingView
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.app.interfaces.services import IServiceFactory
 from lp.app.widgets.project import ProjectScopeWidget
@@ -99,7 +74,7 @@ from lp.code.errors import (
     CannotHaveLinkedBranch,
     InvalidNamespace,
     NoLinkedBranch,
-    )
+)
 from lp.code.interfaces.branch import IBranchSet
 from lp.code.interfaces.branchlookup import IBranchLookup
 from lp.code.interfaces.codehosting import IBazaarApplication
@@ -120,7 +95,7 @@ from lp.registry.interfaces.product import (
     InvalidProductName,
     IProduct,
     IProductSet,
-    )
+)
 from lp.registry.interfaces.projectgroup import IProjectGroupSet
 from lp.registry.interfaces.role import IPersonRoles
 from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
@@ -132,18 +107,18 @@ from lp.services.propertycache import cachedproperty
 from lp.services.statistics.interfaces.statistic import ILaunchpadStatisticSet
 from lp.services.temporaryblobstorage.interfaces import (
     ITemporaryStorageManager,
-    )
+)
 from lp.services.utils import utc_now
 from lp.services.verification.interfaces.logintoken import ILoginTokenSet
 from lp.services.webapp import (
-    canonical_name,
-    canonical_url,
     LaunchpadView,
     Link,
     Navigation,
     StandardLaunchpadFacets,
+    canonical_name,
+    canonical_url,
     stepto,
-    )
+)
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.escaping import structured
@@ -155,7 +130,7 @@ from lp.services.webapp.interfaces import (
     ILaunchpadRoot,
     IMultiFacetedBreadcrumb,
     INavigationMenu,
-    )
+)
 from lp.services.webapp.menu import get_facet
 from lp.services.webapp.publisher import RedirectionView
 from lp.services.webapp.url import urlappend
@@ -172,7 +147,7 @@ from lp.testopenid.interfaces.server import ITestOpenIDApplication
 from lp.translations.interfaces.translationgroup import ITranslationGroupSet
 from lp.translations.interfaces.translationimportqueue import (
     ITranslationImportQueue,
-    )
+)
 from lp.translations.interfaces.translations import IRosettaApplication
 
 
@@ -188,10 +163,14 @@ class NavigationMenuTabs(LaunchpadView):
 
     def initialize(self):
         menuapi = MenuAPI(self.context)
-        self.links = sorted((
-            link for link in menuapi.navigation.values()
-            if (link.enabled or config.launchpad.devmode)),
-            key=operator.attrgetter('sort_key'))
+        self.links = sorted(
+            (
+                link
+                for link in menuapi.navigation.values()
+                if (link.enabled or config.launchpad.devmode)
+            ),
+            key=operator.attrgetter("sort_key"),
+        )
         self.title = None
         if len(self.links) > 0:
             facet = menuapi.selectedfacetname()
@@ -202,7 +181,7 @@ class NavigationMenuTabs(LaunchpadView):
 
     def render(self):
         if not self.links:
-            return ''
+            return ""
         else:
             return self.template()
 
@@ -213,7 +192,8 @@ class LinkView(LaunchpadView):
     The link is not rendered if it's not enabled and we are not in development
     mode.
     """
-    MODIFY_ICONS = ('edit', 'remove', 'trash-icon')
+
+    MODIFY_ICONS = ("edit", "remove", "trash-icon")
 
     @property
     def sprite_class(self):
@@ -222,9 +202,9 @@ class LinkView(LaunchpadView):
             # The 3.0 UI design says these are displayed like other icons
             # But they do not have the same use so we want to keep this rule
             # separate.
-            return 'sprite modify'
+            return "sprite modify"
         else:
-            return 'sprite'
+            return "sprite"
 
     def render(self):
         """Render the menu link if it's enabled or we're in dev mode."""
@@ -235,19 +215,19 @@ class LinkView(LaunchpadView):
             # at the end of an embedded template.
             return self.template().strip()
         else:
-            return ''
+            return ""
 
     @property
     def css_class(self):
         """Return the CSS class."""
         value = ["menu-link-%s" % self.context.name]
         if not self.context.linked:
-            value.append('nolink')
+            value.append("nolink")
         if self.context.icon:
             value.append(self.sprite_class)
             value.append(self.context.icon)
         if self.context.hidden:
-            value.append('hidden')
+            value.append("hidden")
         return " ".join(value)
 
     @property
@@ -255,14 +235,14 @@ class LinkView(LaunchpadView):
         """Return the url if linked."""
         if self.context.linked:
             return self.context.url
-        return ''
+        return ""
 
     @property
     def summary(self):
         """Return the summary if linked."""
         if self.context.linked:
             return self.context.summary
-        return ''
+        return ""
 
 
 class Hierarchy(LaunchpadView):
@@ -273,9 +253,13 @@ class Hierarchy(LaunchpadView):
         """The objects for which we want breadcrumbs."""
         # Start the chain with the deepest object that has a breadcrumb.
         try:
-            objects = [next(
-                obj for obj in reversed(self.request.traversed_objects)
-                if IBreadcrumb(obj, None))]
+            objects = [
+                next(
+                    obj
+                    for obj in reversed(self.request.traversed_objects)
+                    if IBreadcrumb(obj, None)
+                )
+            ]
         except StopIteration:
             return []
         # Now iterate. If an object has a breadcrumb, it can override
@@ -318,13 +302,15 @@ class Hierarchy(LaunchpadView):
             for idx, breadcrumb in reversed(list(enumerate(breadcrumbs))):
                 if IMultiFacetedBreadcrumb.providedBy(breadcrumb):
                     facet_crumb = Breadcrumb(
-                        breadcrumb.context, rootsite=facet.rootsite,
-                        text=facet.text)
+                        breadcrumb.context,
+                        rootsite=facet.rootsite,
+                        text=facet.text,
+                    )
                     alsoProvides(facet_crumb, IFacetBreadcrumb)
                     breadcrumbs.insert(idx + 1, facet_crumb)
                     # Ensure that all remaining breadcrumbs are
                     # themselves faceted.
-                    for remaining_crumb in breadcrumbs[idx + 1:]:
+                    for remaining_crumb in breadcrumbs[idx + 1 :]:
                         remaining_crumb.rootsite_override = facet.rootsite
                     break
         if len(breadcrumbs) > 0:
@@ -342,8 +328,9 @@ class Hierarchy(LaunchpadView):
         """
         crumbs = []
         for crumb in self.items:
-            if (IHeadingBreadcrumb.providedBy(crumb)
-                    or IFacetBreadcrumb.providedBy(crumb)):
+            if IHeadingBreadcrumb.providedBy(
+                crumb
+            ) or IFacetBreadcrumb.providedBy(crumb):
                 crumbs = []
                 continue
             crumbs.append(crumb)
@@ -383,13 +370,13 @@ class Hierarchy(LaunchpadView):
         # Views may provide an additional breadcrumb to precede them.
         # This is useful to have an add view link back to its
         # collection despite its parent being the context of the collection.
-        if hasattr(view, 'inside_breadcrumb'):
+        if hasattr(view, "inside_breadcrumb"):
             crumbs.append(view.inside_breadcrumb)
 
-        if hasattr(view, '__name__') and view.__name__ not in default_views:
-            title = getattr(view, 'page_title', None)
+        if hasattr(view, "__name__") and view.__name__ not in default_views:
+            title = getattr(view, "page_title", None)
             if title is None:
-                title = getattr(view, 'label', None)
+                title = getattr(view, "label", None)
             if isinstance(title, Message):
                 title = i18n.translate(title, context=self.request)
             crumbs.append(Breadcrumb(None, url=url, text=title))
@@ -403,14 +390,17 @@ class Hierarchy(LaunchpadView):
         # If the view is an IMajorHeadingView then we do not want
         # to display breadcrumbs either.
         has_major_heading = IMajorHeadingView.providedBy(
-            self._naked_context_view)
+            self._naked_context_view
+        )
         return len(self.items_for_body) > 1 and not has_major_heading
 
     @property
     def heading_breadcrumbs(self):
         crumbs = [
-            crumb for crumb in self.items
-            if IHeadingBreadcrumb.providedBy(crumb)]
+            crumb
+            for crumb in self.items
+            if IHeadingBreadcrumb.providedBy(crumb)
+        ]
         assert len(crumbs) <= 2
         return crumbs
 
@@ -421,38 +411,42 @@ class Hierarchy(LaunchpadView):
         H1, else an H2.
         """
         # The title is static, but only the index view gets an H1.
-        tag = 'h1' if IMajorHeadingView.providedBy(self.context) else 'h2'
+        tag = "h1" if IMajorHeadingView.providedBy(self.context) else "h2"
         # If there is actually no root context, then it's a top-level
         # context-less page so Launchpad.net is shown as the branding.
         crumb_markups = []
         for crumb in self.heading_breadcrumbs:
             crumb_markups.append(
-                structured('<a href="%s">%s</a>', crumb.url, crumb.detail))
+                structured('<a href="%s">%s</a>', crumb.url, crumb.detail)
+            )
         if not crumb_markups:
-            crumb_markups.append(structured('<span>Launchpad.net</span>'))
+            crumb_markups.append(structured("<span>Launchpad.net</span>"))
         content = structured(
-            '<br />'.join(['%s'] * len(crumb_markups)), *crumb_markups)
+            "<br />".join(["%s"] * len(crumb_markups)), *crumb_markups
+        )
         return structured(
-            '<%s id="watermark-heading">%s</%s>',
-            tag, content, tag).escapedtext
+            '<%s id="watermark-heading">%s</%s>', tag, content, tag
+        ).escapedtext
 
     def logo(self):
         """Return the logo image for the top header breadcrumb's context."""
         logo_context = (
-            self.heading_breadcrumbs[0].context if self.heading_breadcrumbs
-            else None)
-        adapter = queryAdapter(logo_context, IPathAdapter, 'image')
+            self.heading_breadcrumbs[0].context
+            if self.heading_breadcrumbs
+            else None
+        )
+        adapter = queryAdapter(logo_context, IPathAdapter, "image")
         if logo_context is not None:
             return structured(
                 '<a href="%s">%s</a>',
-                canonical_url(logo_context, rootsite='mainsite'),
-                structured(adapter.logo())).escapedtext
+                canonical_url(logo_context, rootsite="mainsite"),
+                structured(adapter.logo()),
+            ).escapedtext
         else:
             return adapter.logo()
 
 
 class ExceptionHierarchy(Hierarchy):
-
     @property
     def objects(self):
         """Return an empty list because the traversal is not safe or sane."""
@@ -546,78 +540,78 @@ class MaintenanceMessage:
     toomuchtime = timedelta(seconds=1800)  # 30 minutes
 
     def __call__(self):
-        if os.path.exists('+maintenancetime.txt'):
-            with open('+maintenancetime.txt') as f:
+        if os.path.exists("+maintenancetime.txt"):
+            with open("+maintenancetime.txt") as f:
                 message = f.read()
             try:
                 maintenancetime = parseDatetimetz(message)
             except DateTimeError:
                 # XXX SteveAlexander 2005-09-22: log a warning here.
-                return ''
+                return ""
             timeleft = maintenancetime - utc_now()
             if timeleft > self.toomuchtime:
-                return ''
+                return ""
             elif timeleft < self.notmuchtime:
-                self.timelefttext = 'very very soon'
+                self.timelefttext = "very very soon"
             else:
-                self.timelefttext = 'in %s' % (
-                    DurationFormatterAPI(timeleft).approximateduration())
-        self.featuretext = getFeatureFlag('app.maintenance_message')
+                self.timelefttext = "in %s" % (
+                    DurationFormatterAPI(timeleft).approximateduration()
+                )
+        self.featuretext = getFeatureFlag("app.maintenance_message")
         if self.timelefttext or self.featuretext:
             return self.index()
-        return ''
+        return ""
 
 
 class LaunchpadRootFacets(StandardLaunchpadFacets):
 
     usedfor = ILaunchpadRoot
     enable_only = [
-        'overview',
-        'branches',
-        'bugs',
-        'specifications',
-        'translations',
-        'answers',
-        ]
+        "overview",
+        "branches",
+        "bugs",
+        "specifications",
+        "translations",
+        "answers",
+    ]
 
     def overview(self):
-        target = ''
-        text = 'Launchpad Home'
-        return Link(target, text, site='mainsite')
+        target = ""
+        text = "Launchpad Home"
+        return Link(target, text, site="mainsite")
 
     def translations(self):
-        text = 'Translations'
-        target = 'translations' if self.mainsite_only else ''
-        site = 'mainsite' if self.mainsite_only else 'translations'
+        text = "Translations"
+        target = "translations" if self.mainsite_only else ""
+        site = "mainsite" if self.mainsite_only else "translations"
         return Link(target, text, site=site)
 
     def bugs(self):
-        text = 'Bugs'
-        target = 'bugs' if self.mainsite_only else ''
-        site = 'mainsite' if self.mainsite_only else 'bugs'
+        text = "Bugs"
+        target = "bugs" if self.mainsite_only else ""
+        site = "mainsite" if self.mainsite_only else "bugs"
         return Link(target, text, site=site)
 
     def answers(self):
-        text = 'Answers'
-        target = 'questions' if self.mainsite_only else ''
-        site = 'mainsite' if self.mainsite_only else 'answers'
+        text = "Answers"
+        target = "questions" if self.mainsite_only else ""
+        site = "mainsite" if self.mainsite_only else "answers"
         return Link(target, text, site=site)
 
     def specifications(self):
-        text = 'Blueprints'
-        target = 'specs' if self.mainsite_only else ''
-        site = 'mainsite' if self.mainsite_only else 'blueprints'
+        text = "Blueprints"
+        target = "specs" if self.mainsite_only else ""
+        site = "mainsite" if self.mainsite_only else "blueprints"
         return Link(target, text, site=site)
 
     def branches(self):
-        text = 'Code'
-        target = '+code' if self.mainsite_only else ''
-        site = 'mainsite' if self.mainsite_only else 'code'
+        text = "Code"
+        target = "+code" if self.mainsite_only else ""
+        site = "mainsite" if self.mainsite_only else "code"
         return Link(target, text, site=site)
 
 
 class LoginStatus:
-
     def __init__(self, context, request):
         self.context = context
         self.request = request
@@ -625,8 +619,7 @@ class LoginStatus:
 
     @property
     def login_shown(self):
-        return (self.user is None and
-                '+login' not in self.request['PATH_INFO'])
+        return self.user is None and "+login" not in self.request["PATH_INFO"]
 
     @property
     def logged_in(self):
@@ -634,18 +627,18 @@ class LoginStatus:
 
     @property
     def login_url(self):
-        query_string = self.request.get('QUERY_STRING', '')
+        query_string = self.request.get("QUERY_STRING", "")
 
         # If we have a query string, remove some things we don't want, and
         # keep it around.
         if query_string:
             query_dict = parse_qs(query_string, keep_blank_values=True)
-            query_dict.pop('loggingout', None)
+            query_dict.pop("loggingout", None)
             query_string = urlencode(sorted(query_dict.items()), doseq=True)
             # If we still have a query_string after things we don't want
             # have been removed, add it onto the url.
             if query_string:
-                query_string = '?' + query_string
+                query_string = "?" + query_string
 
         # The approach we're taking is to combine the application url with
         # the path_info, taking out path steps that are to do with virtual
@@ -661,70 +654,73 @@ class LoginStatus:
         # We're going to use PATH_INFO to remove any spurious '+index' at the
         # end of the URL.  But, PATH_INFO will contain virtual hosting
         # configuration, if there is any.
-        path_info = self.request['PATH_INFO']
+        path_info = self.request["PATH_INFO"]
 
         # Remove any virtual hosting segments.
         path_steps = []
         in_virtual_hosting_section = False
-        for step in path_info.split('/'):
-            if step.startswith('++vh++'):
+        for step in path_info.split("/"):
+            if step.startswith("++vh++"):
                 in_virtual_hosting_section = True
                 continue
-            if step == '++':
+            if step == "++":
                 in_virtual_hosting_section = False
                 continue
             if not in_virtual_hosting_section:
                 path_steps.append(step)
-        path = '/'.join(path_steps)
+        path = "/".join(path_steps)
 
         # Make the URL stop at the end of path_info so that we don't get
         # spurious '+index' at the end.
-        full_url = '%s%s' % (application_url, path)
-        if full_url.endswith('/'):
+        full_url = "%s%s" % (application_url, path)
+        if full_url.endswith("/"):
             full_url = full_url[:-1]
-        logout_url_end = '/+logout'
-        openid_callback_url_end = '/+openid-callback'
+        logout_url_end = "/+logout"
+        openid_callback_url_end = "/+openid-callback"
         if full_url.endswith(logout_url_end):
-            full_url = full_url[:-len(logout_url_end)]
+            full_url = full_url[: -len(logout_url_end)]
         elif full_url.endswith(openid_callback_url_end):
-            full_url = full_url[:-len(openid_callback_url_end)]
+            full_url = full_url[: -len(openid_callback_url_end)]
         else:
             # No need to remove anything from full_url.
             pass
-        return '%s/+login%s' % (full_url, query_string)
+        return "%s/+login%s" % (full_url, query_string)
 
 
 class LaunchpadRootNavigation(Navigation):
 
     usedfor = ILaunchpadRoot
 
-    @stepto('support')
+    @stepto("support")
     def redirect_support(self):
         """Redirect /support to launchpad Answers site."""
         target_url = canonical_url(
-            getUtility(ILaunchpadCelebrities).launchpad, rootsite='answers')
+            getUtility(ILaunchpadCelebrities).launchpad, rootsite="answers"
+        )
         return self.redirectSubTree(target_url, status=301)
 
-    @stepto('legal')
+    @stepto("legal")
     def redirect_legal(self):
         """Redirect /legal to help.launchpad.net/Legal site."""
         return self.redirectSubTree(
-            'https://help.launchpad.net/Legal', status=301)
+            "https://help.launchpad.net/Legal";, status=301
+        )
 
-    @stepto('faq')
+    @stepto("faq")
     def redirect_faq(self):
         """Redirect /faq to launchpad-project/+faqs."""
         return self.redirectSubTree(
-            'https://answers.launchpad.net/launchpad-project/+faqs',
-            status=301)
+            "https://answers.launchpad.net/launchpad-project/+faqs";, status=301
+        )
 
-    @stepto('feedback')
+    @stepto("feedback")
     def redirect_feedback(self):
         """Redirect /feedback to help.launchpad.net/Feedback site."""
         return self.redirectSubTree(
-            'https://help.launchpad.net/Feedback', status=301)
+            "https://help.launchpad.net/Feedback";, status=301
+        )
 
-    @stepto('+branch')
+    @stepto("+branch")
     def redirect_branch(self):
         """Redirect /+branch/<foo> to the branch named 'foo'.
 
@@ -741,12 +737,12 @@ class LaunchpadRootNavigation(Navigation):
         # (in the case that there is an error resolving the branch url).
         # Note: the http referer may be None if someone has hacked a url
         # directly rather than following a /+branch/<foo> link.
-        target_url = self.request.getHeader('referer')
-        path = '/'.join(self.request.stepstogo)
+        target_url = self.request.getHeader("referer")
+        path = "/".join(self.request.stepstogo)
         try:
             branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
             target_url = canonical_url(branch, request=self.request)
-            if trailing != '':
+            if trailing != "":
                 target_url = urlappend(target_url, trailing)
         except NoLinkedBranch:
             # A valid ICanHasLinkedBranch target exists but there's no
@@ -759,9 +755,14 @@ class LaunchpadRootNavigation(Navigation):
             if target_url is None:
                 raise NotFoundError
             self.request.response.addNotification(
-                "The target %s does not have a linked branch." % path)
-        except (CannotHaveLinkedBranch, InvalidNamespace,
-                InvalidProductName, NotFoundError) as e:
+                "The target %s does not have a linked branch." % path
+            )
+        except (
+            CannotHaveLinkedBranch,
+            InvalidNamespace,
+            InvalidProductName,
+            NotFoundError,
+        ) as e:
             # If are aren't arriving at this invalid branch URL from another
             # page then we just raise a NotFoundError to generate a 404,
             # otherwise we end up in a bad recursion loop. The target url will
@@ -769,13 +770,13 @@ class LaunchpadRootNavigation(Navigation):
             if target_url is None:
                 raise NotFoundError
             error_msg = str(e)
-            if error_msg == '':
+            if error_msg == "":
                 error_msg = "Invalid branch lp:%s." % path
             self.request.response.addErrorNotification(error_msg)
 
         return self.redirectSubTree(target_url)
 
-    @stepto('+code')
+    @stepto("+code")
     def redirect_branch_or_repo(self):
         """Redirect /+code/<foo> to the branch or repository named 'foo'.
 
@@ -788,7 +789,7 @@ class LaunchpadRootNavigation(Navigation):
         Unlike +branch, this works for both git and bzr repositories/branches.
         """
         target_url = None
-        path = '/'.join(self.request.stepstogo)
+        path = "/".join(self.request.stepstogo)
 
         # Try a Git repository lookup first, since the schema is simpler and
         # so it's quicker.
@@ -798,8 +799,12 @@ class LaunchpadRootNavigation(Navigation):
                 target_url = canonical_url(repository, request=self.request)
                 if trailing:
                     target_url = urlappend(target_url, trailing)
-        except (InvalidNamespace, InvalidProductName, NameLookupFailed,
-                Unauthorized):
+        except (
+            InvalidNamespace,
+            InvalidProductName,
+            NameLookupFailed,
+            Unauthorized,
+        ):
             # Either the git repository wasn't found, or it was found but we
             # lack authority to access it. In either case, attempt a bzr lookup
             # so don't set target_url
@@ -809,7 +814,7 @@ class LaunchpadRootNavigation(Navigation):
         try:
             branch, trailing = getUtility(IBranchLookup).getByLPPath(path)
             bzr_url = canonical_url(branch, request=self.request)
-            if trailing != '':
+            if trailing != "":
                 bzr_url = urlappend(bzr_url, trailing)
 
             if target_url and branch.product is not None:
@@ -823,8 +828,13 @@ class LaunchpadRootNavigation(Navigation):
                     target_url = bzr_url
             else:
                 target_url = bzr_url
-        except (NoLinkedBranch, CannotHaveLinkedBranch, InvalidNamespace,
-                InvalidProductName, NotFoundError):
+        except (
+            NoLinkedBranch,
+            CannotHaveLinkedBranch,
+            InvalidNamespace,
+            InvalidProductName,
+            NotFoundError,
+        ):
             # No bzr branch found either.
             pass
 
@@ -837,74 +847,76 @@ class LaunchpadRootNavigation(Navigation):
 
         return self.redirectSubTree(target_url)
 
-    @stepto('+builds')
+    @stepto("+builds")
     def redirect_buildfarm(self):
         """Redirect old /+builds requests to new URL, /builders."""
-        new_url = '/builders'
+        new_url = "/builders"
         return self.redirectSubTree(
-            urlappend(new_url, '/'.join(self.request.stepstogo)))
+            urlappend(new_url, "/".join(self.request.stepstogo))
+        )
 
     # XXX cprov 2009-03-19 bug=345877: path segments starting with '+'
     # should never correspond to a valid traversal, they confuse the
     # hierarchical navigation model.
     stepto_utilities = {
-        '+announcements': IAnnouncementSet,
-        'archives': IArchiveSet,
-        '+services': IServiceFactory,
-        'binarypackagenames': IBinaryPackageNameSet,
-        'branches': IBranchSet,
-        'bugs': IMaloneApplication,
-        'builders': IBuilderSet,
-        '+charm-bases': ICharmBaseSet,
-        '+charm-recipes': ICharmRecipeSet,
-        '+code-index': IBazaarApplication,
-        '+code-imports': ICodeImportSet,
-        'codeofconduct': ICodeOfConductSet,
-        '+countries': ICountrySet,
-        'distros': IDistributionSet,
-        '+git': IGitRepositorySet,
-        'karmaaction': IKarmaActionSet,
-        '+imports': ITranslationImportQueue,
-        '+languages': ILanguageSet,
-        'livefses': ILiveFSSet,
-        '+nameblacklist': INameBlacklistSet,
-        'package-sets': IPackagesetSet,
-        'people': IPersonSet,
-        'pillars': IPillarNameSet,
-        '+polls': IPollSet,
-        '+processors': IProcessorSet,
-        'projects': IProductSet,
-        'projectgroups': IProjectGroupSet,
-        '+snaps': ISnapSet,
-        '+snap-bases': ISnapBaseSet,
-        '+snappy-series': ISnappySeriesSet,
-        'sourcepackagenames': ISourcePackageNameSet,
-        'specs': ISpecificationSet,
-        'sprints': ISprintSet,
-        '+statistics': ILaunchpadStatisticSet,
-        'token': ILoginTokenSet,
-        '+groups': ITranslationGroupSet,
-        'translations': IRosettaApplication,
-        'testopenid': ITestOpenIDApplication,
-        'questions': IQuestionSet,
-        'temporary-blobs': ITemporaryStorageManager,
+        "+announcements": IAnnouncementSet,
+        "archives": IArchiveSet,
+        "+services": IServiceFactory,
+        "binarypackagenames": IBinaryPackageNameSet,
+        "branches": IBranchSet,
+        "bugs": IMaloneApplication,
+        "builders": IBuilderSet,
+        "+charm-bases": ICharmBaseSet,
+        "+charm-recipes": ICharmRecipeSet,
+        "+code-index": IBazaarApplication,
+        "+code-imports": ICodeImportSet,
+        "codeofconduct": ICodeOfConductSet,
+        "+countries": ICountrySet,
+        "distros": IDistributionSet,
+        "+git": IGitRepositorySet,
+        "karmaaction": IKarmaActionSet,
+        "+imports": ITranslationImportQueue,
+        "+languages": ILanguageSet,
+        "livefses": ILiveFSSet,
+        "+nameblacklist": INameBlacklistSet,
+        "package-sets": IPackagesetSet,
+        "people": IPersonSet,
+        "pillars": IPillarNameSet,
+        "+polls": IPollSet,
+        "+processors": IProcessorSet,
+        "projects": IProductSet,
+        "projectgroups": IProjectGroupSet,
+        "+snaps": ISnapSet,
+        "+snap-bases": ISnapBaseSet,
+        "+snappy-series": ISnappySeriesSet,
+        "sourcepackagenames": ISourcePackageNameSet,
+        "specs": ISpecificationSet,
+        "sprints": ISprintSet,
+        "+statistics": ILaunchpadStatisticSet,
+        "token": ILoginTokenSet,
+        "+groups": ITranslationGroupSet,
+        "translations": IRosettaApplication,
+        "testopenid": ITestOpenIDApplication,
+        "questions": IQuestionSet,
+        "temporary-blobs": ITemporaryStorageManager,
         # These three have been renamed, and no redirects done, as the old
         # urls now point to the product pages.
         #'bazaar': IBazaarApplication,
         #'malone': IMaloneApplication,
         #'rosetta': IRosettaApplication,
-        }
+    }
 
-    @stepto('products')
+    @stepto("products")
     def products(self):
         return self.redirectSubTree(
-            canonical_url(getUtility(IProductSet)), status=301)
+            canonical_url(getUtility(IProductSet)), status=301
+        )
 
     def traverse(self, name):
         if name in self.stepto_utilities:
             return getUtility(self.stepto_utilities[name])
 
-        if name == '~':
+        if name == "~":
             person = getUtility(ILaunchBag).user
             if person is None:
                 raise Unauthorized()
@@ -912,17 +924,20 @@ class LaunchpadRootNavigation(Navigation):
             # bugs.l.n/~/+assignedbugs goes to the person's canonical
             # assigned list.
             return self.redirectSubTree(
-                canonical_url(self.context) + "~"
+                canonical_url(self.context)
+                + "~"
                 + canonical_name(person.name),
-                status=302)
-        elif name.startswith('~'):  # Allow traversal to ~foo for People
+                status=302,
+            )
+        elif name.startswith("~"):  # Allow traversal to ~foo for People
             if canonical_name(name) != name:
                 # (for instance, uppercase username?)
-                if self.request.method == 'POST':
+                if self.request.method == "POST":
                     raise POSTToNonCanonicalURL
                 return self.redirectSubTree(
                     canonical_url(self.context) + canonical_name(name),
-                    status=301)
+                    status=301,
+                )
             else:
                 person = getUtility(IPersonSet).getByName(name[1:])
                 if person is None:
@@ -930,16 +945,16 @@ class LaunchpadRootNavigation(Navigation):
                 # Check to see if this is a team, and if so, whether the
                 # logged in user is allowed to view the team, by virtue of
                 # team membership or Launchpad administration.
-                if (person.is_team and
-                    not check_permission('launchpad.LimitedView', person)):
+                if person.is_team and not check_permission(
+                    "launchpad.LimitedView", person
+                ):
                     return None
                 # Only admins are permitted to see suspended users.
                 if person.account_status == AccountStatus.SUSPENDED:
-                    if not check_permission('launchpad.Moderate', person):
-                        raise GoneError(
-                            'User is suspended: %s' % name)
+                    if not check_permission("launchpad.Moderate", person):
+                        raise GoneError("User is suspended: %s" % name)
                 if person.account_status == AccountStatus.PLACEHOLDER:
-                    if not check_permission('launchpad.Moderate', person):
+                    if not check_permission("launchpad.Moderate", person):
                         return None
                 return person
 
@@ -949,20 +964,24 @@ class LaunchpadRootNavigation(Navigation):
         # will break much sooner than that) or updates sent to
         # {dapper,edgy}-updates. Probably all irrelevant, as I suspect the
         # number of people using the plugin in edgy and dapper is 0.
-        if name == 'bazaar' and IXMLRPCRequest.providedBy(self.request):
+        if name == "bazaar" and IXMLRPCRequest.providedBy(self.request):
             return getUtility(IBazaarApplication)
 
         # account for common typing mistakes
         if canonical_name(name) != name:
-            if self.request.method == 'POST':
+            if self.request.method == "POST":
                 raise POSTToNonCanonicalURL
             return self.redirectSubTree(
-                (canonical_url(self.context, request=self.request) +
-                 canonical_name(name)),
-                status=301)
+                (
+                    canonical_url(self.context, request=self.request)
+                    + canonical_name(name)
+                ),
+                status=301,
+            )
 
         pillar = getUtility(IPillarNameSet).getByName(
-            name, ignore_inactive=False)
+            name, ignore_inactive=False
+        )
 
         if pillar is None:
             return None
@@ -986,30 +1005,34 @@ class LaunchpadRootNavigation(Navigation):
                 if user is None:
                     return None
                 user = IPersonRoles(user)
-                if (not user.in_commercial_admin and not user.in_admin and
-                    not user.in_registry_experts):
+                if (
+                    not user.in_commercial_admin
+                    and not user.in_admin
+                    and not user.in_registry_experts
+                ):
                     return None
-        if check_permission('launchpad.LimitedView', pillar):
+        if check_permission("launchpad.LimitedView", pillar):
             if pillar.name != name:
                 # This pillar was accessed through one of its aliases, so we
                 # must redirect to its canonical URL.
                 return self.redirectSubTree(
-                    canonical_url(pillar, self.request), status=301)
+                    canonical_url(pillar, self.request), status=301
+                )
             return pillar
         return None
 
     def _getBetaRedirectionView(self):
         # If the inhibit_beta_redirect cookie is set, don't redirect.
-        if self.request.cookies.get('inhibit_beta_redirect', '0') == '1':
+        if self.request.cookies.get("inhibit_beta_redirect", "0") == "1":
             return None
 
         # If we are looking at the front page, don't redirect.
-        if self.request['PATH_INFO'] == '/':
+        if self.request["PATH_INFO"] == "/":
             return None
 
         # If this is a HTTP POST, we don't want to issue a redirect.
         # Doing so would go against the HTTP standard.
-        if self.request.method == 'POST':
+        if self.request.method == "POST":
             return None
 
         # If this is a web service request, don't redirect.
@@ -1017,7 +1040,7 @@ class LaunchpadRootNavigation(Navigation):
             return None
 
         # If the request is for a bug then redirect straight to that bug.
-        bug_match = re.match(r"/bugs/(\d+)$", self.request['PATH_INFO'])
+        bug_match = re.match(r"/bugs/(\d+)$", self.request["PATH_INFO"])
         if bug_match:
             bug_number = bug_match.group(1)
             bug_set = getUtility(IBugSet)
@@ -1030,8 +1053,9 @@ class LaunchpadRootNavigation(Navigation):
             # Empty the traversal stack, since we're redirecting.
             self.request.setTraversalStack([])
             # And perform a temporary redirect.
-            return RedirectionView(canonical_url(bug.default_bugtask),
-                self.request, status=303)
+            return RedirectionView(
+                canonical_url(bug.default_bugtask), self.request, status=303
+            )
         # Explicit catchall - do not redirect.
         return None
 
@@ -1043,19 +1067,19 @@ class LaunchpadRootNavigation(Navigation):
 
 
 class SoftTimeoutView(LaunchpadView):
-
     def __call__(self):
         """Generate a soft timeout by sleeping enough time."""
         start_time = time.time()
         celebrities = getUtility(ILaunchpadCelebrities)
-        if (self.user is None or
-            not self.user.inTeam(celebrities.launchpad_developers)):
+        if self.user is None or not self.user.inTeam(
+            celebrities.launchpad_developers
+        ):
             raise Unauthorized
 
-        self.request.response.setHeader('content-type', 'text/plain')
+        self.request.response.setHeader("content-type", "text/plain")
         soft_timeout = intOrZero(config.database.soft_request_timeout)
         if soft_timeout == 0:
-            return 'No soft timeout threshold is set.'
+            return "No soft timeout threshold is set."
 
         time.sleep(soft_timeout / 1000.0)
         time_to_generate_page = (time.time() - start_time) * 1000
@@ -1065,8 +1089,9 @@ class SoftTimeoutView(LaunchpadView):
             time.sleep(0.1)
             time_to_generate_page = (time.time() - start_time) * 1000
         return (
-            'Soft timeout threshold is set to %s ms. This page took'
-            ' %s ms to render.' % (soft_timeout, time_to_generate_page))
+            "Soft timeout threshold is set to %s ms. This page took"
+            " %s ms to render." % (soft_timeout, time_to_generate_page)
+        )
 
 
 class IcingFolder(ExportedFolder):
@@ -1074,16 +1099,13 @@ class IcingFolder(ExportedFolder):
 
     export_subdirectories = True
 
-    folder = os.path.join(
-        config.root, 'lib/canonical/launchpad/icing/')
+    folder = os.path.join(config.root, "lib/canonical/launchpad/icing/")
 
 
 class LaunchpadImageFolder(ExportedImageFolder):
-    """Export the Launchpad images - supporting retrieval without extension.
-    """
+    """Export the Launchpad images - supporting retrieval without extension."""
 
-    folder = os.path.join(
-        config.root, 'lib/canonical/launchpad/images/')
+    folder = os.path.join(config.root, "lib/canonical/launchpad/images/")
 
 
 class LaunchpadTourFolder(ExportedFolder):
@@ -1093,7 +1115,8 @@ class LaunchpadTourFolder(ExportedFolder):
     """
 
     folder = os.path.join(
-        os.path.dirname(os.path.realpath(__file__)), '../tour/')
+        os.path.dirname(os.path.realpath(__file__)), "../tour/"
+    )
 
     export_subdirectories = True
 
@@ -1103,16 +1126,21 @@ class LaunchpadTourFolder(ExportedFolder):
         The source directory contains source material that we don't want
         published over the web.
         """
-        if name == 'source':
+        if name == "source":
             raise NotFound(request, name)
         return super().publishTraverse(request, name)
 
     def browserDefault(self, request):
         """Redirect to index.html if the directory itself is requested."""
         if len(self.names) == 0:
-            return RedirectionView(
-                "%s+tour/index" % canonical_url(self.context),
-                self.request, status=302), ()
+            return (
+                RedirectionView(
+                    "%s+tour/index" % canonical_url(self.context),
+                    self.request,
+                    status=302,
+                ),
+                (),
+            )
         else:
             return self, ()
 
@@ -1120,13 +1148,12 @@ class LaunchpadTourFolder(ExportedFolder):
 class LaunchpadAPIDocFolder(ExportedFolder):
     """Export the API documentation."""
 
-    folder = os.path.join(
-        config.root, 'lib/canonical/launchpad/apidoc/')
+    folder = os.path.join(config.root, "lib/canonical/launchpad/apidoc/")
 
     def browserDefault(self, request):
         """Traverse to index.html if the directory itself is requested."""
         if len(self.names) == 0:
-            return self, ('index.html', )
+            return self, ("index.html",)
         else:
             return self, ()
 
@@ -1134,10 +1161,13 @@ class LaunchpadAPIDocFolder(ExportedFolder):
 class IAppFrontPageSearchForm(Interface):
     """Schema for the app-specific front page search question forms."""
 
-    search_text = TextLine(title=_('Search text'), required=False)
+    search_text = TextLine(title=_("Search text"), required=False)
 
-    scope = Choice(title=_('Search scope'), required=False,
-                   vocabulary='DistributionOrProductOrProjectGroup')
+    scope = Choice(
+        title=_("Search scope"),
+        required=False,
+        vocabulary="DistributionOrProductOrProjectGroup",
+    )
 
 
 class AppFrontPageSearchView(LaunchpadFormView):
@@ -1149,14 +1179,14 @@ class AppFrontPageSearchView(LaunchpadFormView):
     def scope_css_class(self):
         """The CSS class for used in the scope widget."""
         if self.scope_error:
-            return 'error'
+            return "error"
         else:
             return None
 
     @property
     def scope_error(self):
         """The error message for the scope widget."""
-        return self.getFieldError('scope')
+        return self.getFieldError("scope")
 
 
 def get_launchpad_views(cookies):
@@ -1166,13 +1196,13 @@ def get_launchpad_views(cookies):
     :return: A dict of all the view states.
     """
     views = {
-        'small_maps': True,
-        }
-    cookie = cookies.get('launchpad_views', '')
+        "small_maps": True,
+    }
+    cookie = cookies.get("launchpad_views", "")
     if len(cookie) > 0:
-        pairs = cookie.split('&')
+        pairs = cookie.split("&")
         for pair in pairs:
-            parts = pair.split('=')
+            parts = pair.split("=")
             if len(parts) != 2:
                 # The cookie is malformed, possibly hacked.
                 continue
@@ -1182,7 +1212,7 @@ def get_launchpad_views(cookies):
                 continue
             # 'false' is the value that the browser script sets to disable a
             # part of a page. Any other value is considered to be 'true'.
-            views[key] = value != 'false'
+            views[key] = value != "false"
     return views
 
 
diff --git a/lib/lp/app/browser/launchpadform.py b/lib/lp/app/browser/launchpadform.py
index 220b013..201c948 100644
--- a/lib/lp/app/browser/launchpadform.py
+++ b/lib/lp/app/browser/launchpadform.py
@@ -5,43 +5,34 @@
 """
 
 __all__ = [
-    'action',
-    'has_structured_doc',
-    'LaunchpadEditFormView',
-    'LaunchpadFormView',
-    'render_radio_widget_part',
-    'ReturnToReferrerMixin',
-    'safe_action',
-    ]
+    "action",
+    "has_structured_doc",
+    "LaunchpadEditFormView",
+    "LaunchpadFormView",
+    "render_radio_widget_part",
+    "ReturnToReferrerMixin",
+    "safe_action",
+]
 
-from lazr.lifecycle.event import ObjectModifiedEvent
-from lazr.lifecycle.snapshot import Snapshot
 import simplejson
 import transaction
+from lazr.lifecycle.event import ObjectModifiedEvent
+from lazr.lifecycle.snapshot import Snapshot
 from zope.event import notify
 from zope.formlib import form
+
 # imported so it may be exported
 from zope.formlib.form import action
-from zope.formlib.interfaces import (
-    IInputWidget,
-    IWidgetFactory,
-    )
+from zope.formlib.interfaces import IInputWidget, IWidgetFactory
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.widgets import (
     CheckBoxWidget,
     DropdownWidget,
     RadioWidget,
     TextAreaWidget,
-    )
-from zope.interface import (
-    classImplements,
-    implementer,
-    providedBy,
-    )
-from zope.traversing.interfaces import (
-    ITraversable,
-    TraversalError,
-    )
+)
+from zope.interface import classImplements, implementer, providedBy
+from zope.traversing.interfaces import ITraversable, TraversalError
 
 from lp.services.webapp.escaping import html_escape
 from lp.services.webapp.interfaces import (
@@ -50,12 +41,8 @@ from lp.services.webapp.interfaces import (
     IMultiLineWidgetLayout,
     INotificationResponse,
     UnsafeFormGetSubmissionError,
-    )
-from lp.services.webapp.publisher import (
-    canonical_url,
-    LaunchpadView,
-    )
-
+)
+from lp.services.webapp.publisher import LaunchpadView, canonical_url
 
 classImplements(CheckBoxWidget, ICheckBoxWidgetLayout)
 classImplements(DropdownWidget, IAlwaysSubmittedWidget)
@@ -70,7 +57,7 @@ _first_widget_marker = object()
 class LaunchpadFormView(LaunchpadView):
 
     # The prefix used for all form inputs.
-    prefix = 'field'
+    prefix = "field"
 
     # The form schema
     schema = None
@@ -88,7 +75,7 @@ class LaunchpadFormView(LaunchpadView):
     # to disable setting of initial focus.
     initial_focus_widget = _first_widget_marker
 
-    label = ''
+    label = ""
 
     actions = ()
 
@@ -114,7 +101,8 @@ class LaunchpadFormView(LaunchpadView):
 
         data = {}
         errors, form_action = form.handleSubmit(
-            self.actions, data, self._validate)
+            self.actions, data, self._validate
+        )
 
         # no action selected, so return
         if form_action is None:
@@ -122,8 +110,8 @@ class LaunchpadFormView(LaunchpadView):
 
         # Check to see if an attempt was made to submit a non-safe
         # action with a GET query.
-        is_safe = getattr(form_action, 'is_safe', False)
-        if not is_safe and self.request.method != 'POST':
+        is_safe = getattr(form_action, "is_safe", False)
+        if not is_safe and self.request.method != "POST":
             raise UnsafeFormGetSubmissionError(form_action.__name__)
 
         if errors:
@@ -144,11 +132,14 @@ class LaunchpadFormView(LaunchpadView):
         """Add any notification messages to the response headers."""
         if not INotificationResponse.providedBy(request.response):
             return
-        notifications = ([(notification.level, notification.message)
-             for notification in request.response.notifications])
+        notifications = [
+            (notification.level, notification.message)
+            for notification in request.response.notifications
+        ]
         if notifications:
             request.response.setHeader(
-                'X-Lazr-Notifications', simplejson.dumps(notifications))
+                "X-Lazr-Notifications", simplejson.dumps(notifications)
+            )
 
     def render(self):
         """Return the body of the response.
@@ -181,10 +172,14 @@ class LaunchpadFormView(LaunchpadView):
         pass
 
     def setUpFields(self):
-        assert self.schema is not None, (
-            "Schema must be set for LaunchpadFormView")
-        self.form_fields = form.Fields(self.schema, for_input=self.for_input,
-                                       render_context=self.render_context)
+        assert (
+            self.schema is not None
+        ), "Schema must be set for LaunchpadFormView"
+        self.form_fields = form.Fields(
+            self.schema,
+            for_input=self.for_input,
+            render_context=self.render_context,
+        )
         self.extendFields()
         if self.field_names is not None:
             self.form_fields = self.form_fields.select(*self.field_names)
@@ -198,7 +193,8 @@ class LaunchpadFormView(LaunchpadView):
             # important for some existing forms.
             if field.custom_widget is None:
                 widget = getattr(
-                    self, 'custom_widget_%s' % field.__name__, None)
+                    self, "custom_widget_%s" % field.__name__, None
+                )
                 if widget is not None:
                     if IWidgetFactory.providedBy(widget):
                         field.custom_widget = widget
@@ -208,9 +204,14 @@ class LaunchpadFormView(LaunchpadView):
         if context is None:
             context = self.context
         self.widgets = form.setUpWidgets(
-            self.form_fields, self.prefix, context, self.request,
-            data=self.initial_values, adapters=self.adapters,
-            ignore_request=False)
+            self.form_fields,
+            self.prefix,
+            context,
+            self.request,
+            data=self.initial_values,
+            adapters=self.adapters,
+            ignore_request=False,
+        )
         for field_name, help_link in self.help_links.items():
             self.widgets[field_name].help_link = help_link
 
@@ -329,7 +330,8 @@ class LaunchpadFormView(LaunchpadView):
         for error in form.getWidgetsData(widgets, self.prefix, data):
             self.errors.append(error)
         for error in form.checkInvariants(
-                self.form_fields, data, self.invariant_context):
+            self.form_fields, data, self.invariant_context
+        ):
             self.addError(error)
         return self.errors
 
@@ -355,11 +357,11 @@ class LaunchpadFormView(LaunchpadView):
                     count += 1
 
         if count == 0:
-            return ''
+            return ""
         elif count == 1:
-            return 'There is 1 error.'
+            return "There is 1 error."
         else:
-            return 'There are %d errors.' % count
+            return "There are %d errors." % count
 
     def ajax_failure_handler(self, action, data, errors):
         """Called by the form if validate() finds any errors.
@@ -372,7 +374,7 @@ class LaunchpadFormView(LaunchpadView):
         if not self.request.is_ajax:
             return
         self.request.response.setStatus(400, "Validation")
-        self.request.response.setHeader('Content-type', 'application/json')
+        self.request.response.setHeader("Content-type", "application/json")
         errors = {}
         for widget in self.widgets:
             widget_error = self.getFieldError(widget.context.getName())
@@ -381,7 +383,8 @@ class LaunchpadFormView(LaunchpadView):
         return_data = dict(
             form_wide_errors=self.form_wide_errors,
             errors=errors,
-            error_summary=self.error_count)
+            error_summary=self.error_count,
+        )
         return simplejson.dumps(return_data)
 
     def validate(self, data):
@@ -420,16 +423,16 @@ class LaunchpadFormView(LaunchpadView):
                 widget = None
 
         if widget is None:
-            return ''
+            return ""
         else:
-            return ("<!--\n"
-                    "setFocusByName('%s');\n"
-                    "// -->" % widget.name)
+            return "<!--\n" "setFocusByName('%s');\n" "// -->" % widget.name
 
     def isSingleLineLayout(self, field_name):
         widget = self.widgets[field_name]
-        return not (IMultiLineWidgetLayout.providedBy(widget) or
-                    ICheckBoxWidgetLayout.providedBy(widget))
+        return not (
+            IMultiLineWidgetLayout.providedBy(widget)
+            or ICheckBoxWidgetLayout.providedBy(widget)
+        )
 
     def isMultiLineLayout(self, field_name):
         widget = self.widgets[field_name]
@@ -437,8 +440,9 @@ class LaunchpadFormView(LaunchpadView):
 
     def isCheckBoxLayout(self, field_name):
         widget = self.widgets[field_name]
-        return (ICheckBoxWidgetLayout.providedBy(widget) and
-                not IMultiLineWidgetLayout.providedBy(widget))
+        return ICheckBoxWidgetLayout.providedBy(
+            widget
+        ) and not IMultiLineWidgetLayout.providedBy(widget)
 
     def showOptionalMarker(self, field_name):
         """Should the (Optional) marker be shown?"""
@@ -449,14 +453,15 @@ class LaunchpadFormView(LaunchpadView):
             return False
 
         # Do not show for readonly fields.
-        context = getattr(widget, 'context', None)
-        if getattr(context, 'readonly', None):
+        context = getattr(widget, "context", None)
+        if getattr(context, "readonly", None):
             return False
 
         # Do not show the marker for required widgets or always submitted
         # widgets.  Everything else gets the marker.
-        return not (widget.required or
-                    IAlwaysSubmittedWidget.providedBy(widget))
+        return not (
+            widget.required or IAlwaysSubmittedWidget.providedBy(widget)
+        )
 
 
 class LaunchpadEditFormView(LaunchpadFormView):
@@ -479,15 +484,21 @@ class LaunchpadEditFormView(LaunchpadFormView):
             context = self.context
         if notify_modified:
             context_before_modification = Snapshot(
-                context, providing=providedBy(context))
+                context, providing=providedBy(context)
+            )
 
-        was_changed = form.applyChanges(context, self.form_fields,
-                                        data, self.adapters)
+        was_changed = form.applyChanges(
+            context, self.form_fields, data, self.adapters
+        )
         if was_changed and notify_modified:
-            field_names = [form_field.__name__
-                           for form_field in self.form_fields]
-            notify(ObjectModifiedEvent(
-                context, context_before_modification, field_names))
+            field_names = [
+                form_field.__name__ for form_field in self.form_fields
+            ]
+            notify(
+                ObjectModifiedEvent(
+                    context, context_before_modification, field_names
+                )
+            )
         return was_changed
 
 
@@ -532,23 +543,27 @@ class ReturnToReferrerMixin:
         """See `LaunchpadFormView`."""
         # The referer header we want is only available before the view's
         # form submits to itself. This field is a hidden input in the form.
-        referrer = self.request.form.get('_return_url')
+        referrer = self.request.form.get("_return_url")
         returnNotChanged = True
         if referrer is None:
             # "referer" is misspelled in the HTTP specification.
-            referrer = self.request.getHeader('referer')
+            referrer = self.request.getHeader("referer")
         else:
-            attribute_name = self.request.form.get('_return_attribute_name')
-            attribute_value = self.request.form.get('_return_attribute_value')
-            if (attribute_name is not None
+            attribute_name = self.request.form.get("_return_attribute_name")
+            attribute_value = self.request.form.get("_return_attribute_value")
+            if (
+                attribute_name is not None
                 and attribute_value is not None
-                and getattr(self.context, attribute_name) != attribute_value):
+                and getattr(self.context, attribute_name) != attribute_value
+            ):
                 returnNotChanged = False
 
-        if (referrer is not None
+        if (
+            referrer is not None
             and returnNotChanged
             and referrer.startswith(self.request.getApplicationURL())
-            and referrer != self.request.getHeader('location')):
+            and referrer != self.request.getHeader("location")
+        ):
             return referrer
         else:
             return canonical_url(self.context)
@@ -559,7 +574,7 @@ class ReturnToReferrerMixin:
 
 def has_structured_doc(field):
     """Set an annotation to mark that the field's doc should be structured."""
-    field.setTaggedValue('has_structured_doc', True)
+    field.setTaggedValue("has_structured_doc", True)
     return field
 
 
@@ -575,13 +590,14 @@ class WidgetHasStructuredDoc:
         self.widget = widget
 
     def traverse(self, name, furtherPath):
-        if name != 'has-structured-doc':
+        if name != "has-structured-doc":
             raise TraversalError("Unknown query %r" % name)
         if len(furtherPath) > 0:
             raise TraversalError(
                 "There should be no further path segments after "
-                "query:has-structured-doc")
-        return self.widget.context.queryTaggedValue('has_structured_doc')
+                "query:has-structured-doc"
+            )
+        return self.widget.context.queryTaggedValue("has_structured_doc")
 
 
 def render_radio_widget_part(widget, term_value, current_value, label=None):
@@ -599,5 +615,9 @@ def render_radio_widget_part(widget, term_value, current_value, label=None):
         label = term.title
     value = term.token
     return render(
-        index=term.value, text=label, value=value, name=widget.name,
-        cssClass='')
+        index=term.value,
+        text=label,
+        value=value,
+        name=widget.name,
+        cssClass="",
+    )
diff --git a/lib/lp/app/browser/lazrjs.py b/lib/lp/app/browser/lazrjs.py
index c48cc09..23a6bc0 100644
--- a/lib/lp/app/browser/lazrjs.py
+++ b/lib/lp/app/browser/lazrjs.py
@@ -4,38 +4,32 @@
 """Wrappers for lazr-js widgets."""
 
 __all__ = [
-    'BooleanChoiceWidget',
-    'EnumChoiceWidget',
-    'InlineEditPickerWidget',
-    'InlinePersonEditPickerWidget',
-    'InlineMultiCheckboxWidget',
-    'standard_text_html_representation',
-    'TextAreaEditorWidget',
-    'TextLineEditorWidget',
-    'vocabulary_to_choice_edit_items',
-    ]
+    "BooleanChoiceWidget",
+    "EnumChoiceWidget",
+    "InlineEditPickerWidget",
+    "InlinePersonEditPickerWidget",
+    "InlineMultiCheckboxWidget",
+    "standard_text_html_representation",
+    "TextAreaEditorWidget",
+    "TextLineEditorWidget",
+    "vocabulary_to_choice_edit_items",
+]
 
+import simplejson
 from lazr.enum import IEnumeratedType
 from lazr.restful.declarations import LAZR_WEBSERVICE_EXPORTED
 from lazr.restful.utils import get_current_browser_request
-import simplejson
 from zope.browserpage import ViewPageTemplateFile
 from zope.component import getUtility
-from zope.schema.interfaces import (
-    ICollection,
-    IVocabulary,
-    )
+from zope.schema.interfaces import ICollection, IVocabulary
 from zope.schema.vocabulary import getVocabularyRegistry
-from zope.security.checker import (
-    canAccess,
-    canWrite,
-    )
+from zope.security.checker import canAccess, canWrite
 
 from lp.app.browser.stringformatter import FormattersAPI
 from lp.app.browser.vocabulary import (
     get_person_picker_entry_metadata,
     vocabulary_filters,
-    )
+)
 from lp.services.propertycache import cachedproperty
 from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.webapp.publisher import canonical_url
@@ -45,8 +39,15 @@ from lp.services.webapp.vocabulary import IHugeVocabulary
 class WidgetBase:
     """Useful methods for all widgets."""
 
-    def __init__(self, context, exported_field, content_box_id,
-                 edit_view, edit_url, edit_title):
+    def __init__(
+        self,
+        context,
+        exported_field,
+        content_box_id,
+        edit_view,
+        edit_url,
+        edit_title,
+    ):
         self.context = context
         self.exported_field = exported_field
 
@@ -62,7 +63,7 @@ class WidgetBase:
             edit_url = canonical_url(self.context, view_name=edit_view)
         self.edit_url = edit_url
         if edit_title is None:
-            edit_title = ''
+            edit_title = ""
         self.edit_title = edit_title
 
         # The mutator method name is used to determine whether or not the
@@ -75,8 +76,8 @@ class WidgetBase:
             # about.
             self.api_attribute = self.attribute_name
         else:
-            self.api_attribute = ws_stack['as']
-            mutator_info = ws_stack.get('mutator_annotations')
+            self.api_attribute = ws_stack["as"]
+            mutator_info = ws_stack.get("mutator_annotations")
             if mutator_info is not None:
                 mutator_method, mutator_extra = mutator_info
                 self.mutator_method_name = mutator_method.__name__
@@ -113,18 +114,31 @@ class WidgetBase:
 class TextWidgetBase(WidgetBase):
     """Abstract base for the single and multiline text editor widgets."""
 
-    def __init__(self, context, exported_field, title, content_box_id,
-                 edit_view, edit_url, edit_title):
+    def __init__(
+        self,
+        context,
+        exported_field,
+        title,
+        content_box_id,
+        edit_view,
+        edit_url,
+        edit_title,
+    ):
         super().__init__(
-            context, exported_field, content_box_id,
-            edit_view, edit_url, edit_title)
+            context,
+            exported_field,
+            content_box_id,
+            edit_view,
+            edit_url,
+            edit_title,
+        )
         self.accept_empty = simplejson.dumps(self.optional_field)
         self.title = title
-        self.widget_css_selector = simplejson.dumps('#' + self.content_box_id)
+        self.widget_css_selector = simplejson.dumps("#" + self.content_box_id)
 
     @property
     def json_attribute_uri(self):
-        return simplejson.dumps(self.resource_uri + '/' + self.api_attribute)
+        return simplejson.dumps(self.resource_uri + "/" + self.api_attribute)
 
 
 class DefinedTagMixin:
@@ -134,24 +148,40 @@ class DefinedTagMixin:
     def open_tag(self):
         if self.css_class:
             return '<%s id="%s" class="%s">' % (
-                self.tag, self.content_box_id, self.css_class)
+                self.tag,
+                self.content_box_id,
+                self.css_class,
+            )
         else:
             return '<%s id="%s">' % (self.tag, self.content_box_id)
 
     @property
     def close_tag(self):
-        return '</%s>' % self.tag
+        return "</%s>" % self.tag
 
 
 class TextLineEditorWidget(TextWidgetBase, DefinedTagMixin):
     """Wrapper for the lazr-js inlineedit/editor.js widget."""
 
-    __call__ = ViewPageTemplateFile('../templates/text-line-editor.pt')
-
-    def __init__(self, context, exported_field, title, tag, css_class=None,
-                 content_box_id=None, edit_view="+edit", edit_url=None,
-                 edit_title='', max_width=None, truncate_lines=0,
-                 default_text=None, initial_value_override=None, width=None):
+    __call__ = ViewPageTemplateFile("../templates/text-line-editor.pt")
+
+    def __init__(
+        self,
+        context,
+        exported_field,
+        title,
+        tag,
+        css_class=None,
+        content_box_id=None,
+        edit_view="+edit",
+        edit_url=None,
+        edit_title="",
+        max_width=None,
+        truncate_lines=0,
+        default_text=None,
+        initial_value_override=None,
+        width=None,
+    ):
         """Create a widget wrapper.
 
         :param context: The object that is being edited.
@@ -178,8 +208,14 @@ class TextLineEditorWidget(TextWidgetBase, DefinedTagMixin):
         :param width: Initial widget width.
         """
         super().__init__(
-            context, exported_field, title, content_box_id,
-            edit_view, edit_url, edit_title)
+            context,
+            exported_field,
+            title,
+            content_box_id,
+            edit_view,
+            edit_url,
+            edit_title,
+        )
         self.tag = tag
         self.css_class = css_class
         self.max_width = max_width
@@ -200,26 +236,35 @@ class TextLineEditorWidget(TextWidgetBase, DefinedTagMixin):
     def text_css_class(self):
         clazz = "yui3-editable_text-text"
         if self.truncate_lines and self.truncate_lines > 0:
-            clazz += ' ellipsis'
+            clazz += " ellipsis"
             if self.truncate_lines == 1:
-                clazz += ' single-line'
+                clazz += " single-line"
         return clazz
 
     @property
     def text_css_style(self):
         if self.max_width:
-            return 'max-width: %s;' % self.max_width
-        return ''
+            return "max-width: %s;" % self.max_width
+        return ""
 
 
 class TextAreaEditorWidget(TextWidgetBase):
     """Wrapper for the multine-line lazr-js inlineedit/editor.js widget."""
 
-    __call__ = ViewPageTemplateFile('../templates/text-area-editor.pt')
-
-    def __init__(self, context, exported_field, title, content_box_id=None,
-                 edit_view="+edit", edit_url=None, edit_title='',
-                 hide_empty=True, linkify_text=True):
+    __call__ = ViewPageTemplateFile("../templates/text-area-editor.pt")
+
+    def __init__(
+        self,
+        context,
+        exported_field,
+        title,
+        content_box_id=None,
+        edit_view="+edit",
+        edit_url=None,
+        edit_title="",
+        hide_empty=True,
+        linkify_text=True,
+    ):
         """Create the widget wrapper.
 
         :param context: The object that is being edited.
@@ -239,18 +284,24 @@ class TextAreaEditorWidget(TextWidgetBase):
             things that look like links made into anchors.
         """
         super().__init__(
-            context, exported_field, title, content_box_id,
-            edit_view, edit_url, edit_title)
+            context,
+            exported_field,
+            title,
+            content_box_id,
+            edit_view,
+            edit_url,
+            edit_title,
+        )
         self.hide_empty = hide_empty
         self.linkify_text = linkify_text
 
     @property
     def tag_class(self):
         """The CSS class for the widget."""
-        classes = ['lazr-multiline-edit']
+        classes = ["lazr-multiline-edit"]
         if self.hide_empty and not self.value:
-            classes.append('hidden')
-        return ' '.join(classes)
+            classes.append("hidden")
+        return " ".join(classes)
 
     @cachedproperty
     def value(self):
@@ -265,14 +316,22 @@ class InlineEditPickerWidget(WidgetBase):
     VocabularyPickerWidget.
     """
 
-    __call__ = ViewPageTemplateFile('../templates/inline-picker.pt')
-
-    def __init__(self, context, exported_field, default_html,
-                 content_box_id=None, header='Select an item',
-                 step_title='Search',
-                 null_display_value='None',
-                 edit_view="+edit", edit_url=None, edit_title='',
-                 help_link=None):
+    __call__ = ViewPageTemplateFile("../templates/inline-picker.pt")
+
+    def __init__(
+        self,
+        context,
+        exported_field,
+        default_html,
+        content_box_id=None,
+        header="Select an item",
+        step_title="Search",
+        null_display_value="None",
+        edit_view="+edit",
+        edit_url=None,
+        edit_title="",
+        help_link=None,
+    ):
         """Create a widget wrapper.
 
         :param context: The object that is being edited.
@@ -291,8 +350,13 @@ class InlineEditPickerWidget(WidgetBase):
         :param edit_title: Used to set the title attribute of the anchor.
         """
         super().__init__(
-            context, exported_field, content_box_id,
-            edit_view, edit_url, edit_title)
+            context,
+            exported_field,
+            content_box_id,
+            edit_view,
+            edit_url,
+            edit_title,
+        )
         self.default_html = default_html
         self.header = header
         self.step_title = step_title
@@ -301,13 +365,14 @@ class InlineEditPickerWidget(WidgetBase):
 
         # JSON encoded attributes.
         self.json_content_box_id = simplejson.dumps(self.content_box_id)
-        self.json_attribute = simplejson.dumps(self.api_attribute + '_link')
+        self.json_attribute = simplejson.dumps(self.api_attribute + "_link")
         self.json_vocabulary_name = simplejson.dumps(
-            self.exported_field.vocabularyName)
+            self.exported_field.vocabularyName
+        )
 
     @property
     def picker_type(self):
-        return 'default'
+        return "default"
 
     @property
     def selected_value_metadata(self):
@@ -315,7 +380,7 @@ class InlineEditPickerWidget(WidgetBase):
 
     @property
     def selected_value(self):
-        """ String representation of field value associated with the picker.
+        """String representation of field value associated with the picker.
 
         Default implementation is to return the 'name' attribute.
         """
@@ -323,7 +388,7 @@ class InlineEditPickerWidget(WidgetBase):
             return None
         val = getattr(self.context, self.exported_field.__name__)
         if val is not None:
-            return getattr(val, 'name', None)
+            return getattr(val, "name", None)
         return None
 
     @property
@@ -333,12 +398,14 @@ class InlineEditPickerWidget(WidgetBase):
     def getConfig(self):
         return dict(
             picker_type=self.picker_type,
-            header=self.header, step_title=self.step_title,
+            header=self.header,
+            step_title=self.step_title,
             selected_value=self.selected_value,
             selected_value_metadata=self.selected_value_metadata,
             null_display_value=self.null_display_value,
             show_search_box=self.show_search_box,
-            vocabulary_filters=self.vocabulary_filters)
+            vocabulary_filters=self.vocabulary_filters,
+        )
 
     @property
     def json_config(self):
@@ -347,8 +414,7 @@ class InlineEditPickerWidget(WidgetBase):
     @cachedproperty
     def vocabulary(self):
         registry = getVocabularyRegistry()
-        return registry.get(
-            IVocabulary, self.exported_field.vocabularyName)
+        return registry.get(IVocabulary, self.exported_field.vocabularyName)
 
     @cachedproperty
     def vocabulary_filters(self):
@@ -360,15 +426,24 @@ class InlineEditPickerWidget(WidgetBase):
 
 
 class InlinePersonEditPickerWidget(InlineEditPickerWidget):
-    def __init__(self, context, exported_field, default_html,
-                 content_box_id=None, header='Select an item',
-                 step_title='Search', show_create_team=False,
-                 assign_me_text='Pick me',
-                 remove_person_text='Remove person',
-                 remove_team_text='Remove team',
-                 null_display_value='None',
-                 edit_view="+edit", edit_url=None, edit_title='',
-                 help_link=None):
+    def __init__(
+        self,
+        context,
+        exported_field,
+        default_html,
+        content_box_id=None,
+        header="Select an item",
+        step_title="Search",
+        show_create_team=False,
+        assign_me_text="Pick me",
+        remove_person_text="Remove person",
+        remove_team_text="Remove team",
+        null_display_value="None",
+        edit_view="+edit",
+        edit_url=None,
+        edit_title="",
+        help_link=None,
+    ):
         """Create a widget wrapper.
 
         :param context: The object that is being edited.
@@ -391,9 +466,18 @@ class InlinePersonEditPickerWidget(InlineEditPickerWidget):
         :param help_link: Used to set a link for help for the widget.
         """
         super().__init__(
-            context, exported_field, default_html, content_box_id, header,
-            step_title, null_display_value,
-            edit_view, edit_url, edit_title, help_link)
+            context,
+            exported_field,
+            default_html,
+            content_box_id,
+            header,
+            step_title,
+            null_display_value,
+            edit_view,
+            edit_url,
+            edit_title,
+            help_link,
+        )
 
         self._show_create_team = show_create_team
         self.assign_me_text = assign_me_text
@@ -402,7 +486,7 @@ class InlinePersonEditPickerWidget(InlineEditPickerWidget):
 
     @property
     def picker_type(self):
-        return 'person'
+        return "person"
 
     @property
     def selected_value_metadata(self):
@@ -422,13 +506,16 @@ class InlinePersonEditPickerWidget(InlineEditPickerWidget):
 
     def getConfig(self):
         config = super().getConfig()
-        config.update(dict(
-            show_remove_button=self.optional_field,
-            show_assign_me_button=self.show_assign_me_button,
-            show_create_team=self.show_create_team,
-            assign_me_text=self.assign_me_text,
-            remove_person_text=self.remove_person_text,
-            remove_team_text=self.remove_team_text))
+        config.update(
+            dict(
+                show_remove_button=self.optional_field,
+                show_assign_me_button=self.show_assign_me_button,
+                show_create_team=self.show_create_team,
+                assign_me_text=self.assign_me_text,
+                remove_person_text=self.remove_person_text,
+                remove_team_text=self.remove_team_text,
+            )
+        )
         return config
 
 
@@ -436,15 +523,27 @@ class InlineMultiCheckboxWidget(WidgetBase):
     """Wrapper for the lazr-js multicheckbox widget."""
 
     __call__ = ViewPageTemplateFile(
-                        '../templates/inline-multicheckbox-widget.pt')
-
-    def __init__(self, context, exported_field,
-                 label, label_tag="span", attribute_type="default",
-                 vocabulary=None, header=None,
-                 empty_display_value="None", selected_items=list(),
-                 items_tag="span", items_style='',
-                 content_box_id=None, edit_view="+edit", edit_url=None,
-                 edit_title=''):
+        "../templates/inline-multicheckbox-widget.pt"
+    )
+
+    def __init__(
+        self,
+        context,
+        exported_field,
+        label,
+        label_tag="span",
+        attribute_type="default",
+        vocabulary=None,
+        header=None,
+        empty_display_value="None",
+        selected_items=list(),
+        items_tag="span",
+        items_style="",
+        content_box_id=None,
+        edit_view="+edit",
+        edit_url=None,
+        edit_title="",
+    ):
         """Create a widget wrapper.
 
         :param context: The object that is being edited.
@@ -474,21 +573,28 @@ class InlineMultiCheckboxWidget(WidgetBase):
 
         """
         super().__init__(
-            context, exported_field, content_box_id,
-            edit_view, edit_url, edit_title)
+            context,
+            exported_field,
+            content_box_id,
+            edit_view,
+            edit_url,
+            edit_title,
+        )
 
         linkify_items = attribute_type == "reference"
 
         if header is None:
             header = self.exported_field.title + ":"
-        self.header = header,
+        self.header = (header,)
         self.empty_display_value = empty_display_value
         self.label = label
         self.label_open_tag = "<%s>" % label_tag
         self.label_close_tag = "</%s>" % label_tag
         self.items = selected_items
-        self.items_open_tag = ("<%s id='%s'>" %
-            (items_tag, self.content_box_id + "-items"))
+        self.items_open_tag = "<%s id='%s'>" % (
+            items_tag,
+            self.content_box_id + "-items",
+        )
         self.items_close_tag = "</%s>" % items_tag
         self.linkify_items = linkify_items
 
@@ -503,20 +609,21 @@ class InlineMultiCheckboxWidget(WidgetBase):
 
         # Construct checkbox data dict for each item in the vocabulary.
         items = []
-        style = ';'.join(['font-weight: normal', items_style])
+        style = ";".join(["font-weight: normal", items_style])
         for item in vocabulary:
-            item_value = getattr(item, 'value', item)
+            item_value = getattr(item, "value", item)
             checked = item_value in selected_items
             if linkify_items:
                 save_value = canonical_url(item_value, force_local_path=True)
             else:
                 save_value = item_value.name
             new_item = {
-                'name': item.title,
-                'token': item.token,
-                'style': style,
-                'checked': checked,
-                'value': save_value}
+                "name": item.title,
+                "token": item.token,
+                "style": style,
+                "checked": checked,
+                "value": save_value,
+            }
             items.append(new_item)
         self.has_choices = len(items)
 
@@ -526,13 +633,14 @@ class InlineMultiCheckboxWidget(WidgetBase):
         self.json_attribute_type = simplejson.dumps(attribute_type)
         self.json_items = simplejson.dumps(items)
         self.json_description = simplejson.dumps(
-            self.exported_field.description)
+            self.exported_field.description
+        )
 
     @property
     def config(self):
         return dict(
             header=self.header,
-            )
+        )
 
     @property
     def json_config(self):
@@ -540,9 +648,16 @@ class InlineMultiCheckboxWidget(WidgetBase):
 
 
 def vocabulary_to_choice_edit_items(
-    vocab, include_description=False, css_class_prefix=None,
-    disabled_items=None, excluded_items=None,
-    as_json=False, name_fn=None, value_fn=None, description_fn=None):
+    vocab,
+    include_description=False,
+    css_class_prefix=None,
+    disabled_items=None,
+    excluded_items=None,
+    as_json=False,
+    name_fn=None,
+    value_fn=None,
+    description_fn=None,
+):
     """Convert an enumerable to JSON for a ChoiceEdit.
 
     :vocab: The enumeration to iterate over.
@@ -558,7 +673,7 @@ def vocabulary_to_choice_edit_items(
     for item in vocab:
         # Introspect to make sure we're dealing with the object itself.
         # SimpleTerm objects have the object itself at item.value.
-        if hasattr(item, 'value'):
+        if hasattr(item, "value"):
             item = item.value
         if excluded_items and item in excluded_items:
             continue
@@ -571,22 +686,25 @@ def vocabulary_to_choice_edit_items(
         else:
             value = item.title
         if description_fn is None:
-            description_fn = lambda item: getattr(item, 'description', '')
-        description = ''
+            description_fn = lambda item: getattr(item, "description", "")
+        description = ""
         if include_description:
             description = description_fn(item)
         new_item = {
-            'name': name,
-            'value': value,
-            'description': description,
-            'description_css_class': 'choice-description',
-            'style': '', 'help': '', 'disabled': False}
+            "name": name,
+            "value": value,
+            "description": description,
+            "description_css_class": "choice-description",
+            "style": "",
+            "help": "",
+            "disabled": False,
+        }
         for disabled_item in disabled_items:
             if disabled_item == item:
-                new_item['disabled'] = True
+                new_item["disabled"] = True
                 break
         if css_class_prefix is not None:
-            new_item['css_class'] = css_class_prefix + item.name
+            new_item["css_class"] = css_class_prefix + item.name
         items.append(new_item)
 
     if as_json:
@@ -601,7 +719,7 @@ def standard_text_html_representation(value, linkify_text=True):
     For this we obfuscate email and render as html.
     """
     if value is None:
-        return ''
+        return ""
     nomail = FormattersAPI(value).obfuscate_email()
     return FormattersAPI(nomail).text_to_html(linkify_text=linkify_text)
 
@@ -609,12 +727,23 @@ def standard_text_html_representation(value, linkify_text=True):
 class BooleanChoiceWidget(WidgetBase, DefinedTagMixin):
     """A ChoiceEdit for a boolean field."""
 
-    __call__ = ViewPageTemplateFile('../templates/boolean-choice-widget.pt')
-
-    def __init__(self, context, exported_field,
-                 tag, false_text, true_text, css_class=None, prefix=None,
-                 edit_view="+edit", edit_url=None, edit_title='',
-                 content_box_id=None, header='Select an item'):
+    __call__ = ViewPageTemplateFile("../templates/boolean-choice-widget.pt")
+
+    def __init__(
+        self,
+        context,
+        exported_field,
+        tag,
+        false_text,
+        true_text,
+        css_class=None,
+        prefix=None,
+        edit_view="+edit",
+        edit_url=None,
+        edit_title="",
+        content_box_id=None,
+        header="Select an item",
+    ):
         """Create a widget wrapper.
 
         :param context: The object that is being edited.
@@ -635,8 +764,13 @@ class BooleanChoiceWidget(WidgetBase, DefinedTagMixin):
         :param header: The large text at the top of the choice popup.
         """
         super().__init__(
-            context, exported_field, content_box_id,
-            edit_view, edit_url, edit_title)
+            context,
+            exported_field,
+            content_box_id,
+            edit_view,
+            edit_url,
+            edit_title,
+        )
         self.header = header
         self.tag = tag
         self.css_class = css_class
@@ -655,14 +789,26 @@ class BooleanChoiceWidget(WidgetBase, DefinedTagMixin):
     @property
     def config(self):
         return dict(
-            contentBox='#' + self.content_box_id,
+            contentBox="#" + self.content_box_id,
             value=self.current_value,
             title=self.header,
             items=[
-                dict(name=self.true_text, value=True, style='', help='',
-                     disabled=False),
-                dict(name=self.false_text, value=False, style='', help='',
-                     disabled=False)])
+                dict(
+                    name=self.true_text,
+                    value=True,
+                    style="",
+                    help="",
+                    disabled=False,
+                ),
+                dict(
+                    name=self.false_text,
+                    value=False,
+                    style="",
+                    help="",
+                    disabled=False,
+                ),
+            ],
+        )
 
     @property
     def json_config(self):
@@ -672,12 +818,21 @@ class BooleanChoiceWidget(WidgetBase, DefinedTagMixin):
 class EnumChoiceWidget(WidgetBase):
     """A popup choice widget."""
 
-    __call__ = ViewPageTemplateFile('../templates/enum-choice-widget.pt')
-
-    def __init__(self, context, exported_field, header,
-                 content_box_id=None, enum=None,
-                 edit_view="+edit", edit_url=None, edit_title='',
-                 css_class_prefix='', include_description=False):
+    __call__ = ViewPageTemplateFile("../templates/enum-choice-widget.pt")
+
+    def __init__(
+        self,
+        context,
+        exported_field,
+        header,
+        content_box_id=None,
+        enum=None,
+        edit_view="+edit",
+        edit_url=None,
+        edit_title="",
+        css_class_prefix="",
+        include_description=False,
+    ):
         """Create a widget wrapper.
 
         :param context: The object that is being edited.
@@ -695,8 +850,13 @@ class EnumChoiceWidget(WidgetBase):
         :param css_class_prefix: Added to the start of the enum titles.
         """
         super().__init__(
-            context, exported_field, content_box_id,
-            edit_view, edit_url, edit_title)
+            context,
+            exported_field,
+            content_box_id,
+            edit_view,
+            edit_url,
+            edit_title,
+        )
         self.header = header
         value = getattr(self.context, self.attribute_name)
         self.css_class = "value %s%s" % (css_class_prefix, value.name)
@@ -705,18 +865,21 @@ class EnumChoiceWidget(WidgetBase):
             # Get the enum from the exported field.
             enum = exported_field.vocabulary
         if IEnumeratedType(enum, None) is None:
-            raise ValueError('%r does not provide IEnumeratedType' % enum)
+            raise ValueError("%r does not provide IEnumeratedType" % enum)
         self.items = vocabulary_to_choice_edit_items(
-            enum, include_description=include_description,
-            css_class_prefix=css_class_prefix)
+            enum,
+            include_description=include_description,
+            css_class_prefix=css_class_prefix,
+        )
 
     @property
     def config(self):
         return dict(
-            contentBox='#' + self.content_box_id,
+            contentBox="#" + self.content_box_id,
             value=self.value,
             title=self.header,
-            items=self.items)
+            items=self.items,
+        )
 
     @property
     def json_config(self):
diff --git a/lib/lp/app/browser/linkchecker.py b/lib/lp/app/browser/linkchecker.py
index fbdacc1..2ad22eb 100644
--- a/lib/lp/app/browser/linkchecker.py
+++ b/lib/lp/app/browser/linkchecker.py
@@ -2,8 +2,8 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'LinkCheckerAPI',
-    ]
+    "LinkCheckerAPI",
+]
 
 import simplejson
 from zope.component import getUtility
@@ -16,7 +16,7 @@ from lp.code.errors import (
     InvalidNamespace,
     NoLinkedBranch,
     NoSuchBranch,
-    )
+)
 from lp.code.interfaces.branchlookup import IBranchLookup
 from lp.code.interfaces.gitlookup import IGitLookup
 from lp.registry.interfaces.product import InvalidProductName
@@ -54,7 +54,7 @@ class LinkCheckerAPI(LaunchpadView):
 
     def __call__(self):
         result = {}
-        links_to_check_data = self.request.get('link_hrefs')
+        links_to_check_data = self.request.get("link_hrefs")
         if links_to_check_data is None:
             return simplejson.dumps(result)
         links_to_check = simplejson.loads(links_to_check_data)
@@ -64,7 +64,7 @@ class LinkCheckerAPI(LaunchpadView):
             link_info = self.link_checkers[link_type](links)
             result[link_type] = link_info
 
-        self.request.response.setHeader('Content-type', 'application/json')
+        self.request.response.setHeader("Content-type", "application/json")
         return simplejson.dumps(result)
 
     def check_branch_links(self, links):
@@ -73,15 +73,20 @@ class LinkCheckerAPI(LaunchpadView):
         bzr_branch_lookup = getUtility(IBranchLookup)
         git_branch_lookup = getUtility(IGitLookup)
         for link in links:
-            path = link.strip('/')[len('+code/'):]
+            path = link.strip("/")[len("+code/") :]
             if git_branch_lookup.getByPath(path)[0] is None:
                 try:
                     bzr_branch_lookup.getByLPPath(path)
-                except (CannotHaveLinkedBranch, InvalidNamespace,
-                        InvalidProductName, NoLinkedBranch, NoSuchBranch,
-                        NotFoundError) as e:
+                except (
+                    CannotHaveLinkedBranch,
+                    InvalidNamespace,
+                    InvalidProductName,
+                    NoLinkedBranch,
+                    NoSuchBranch,
+                    NotFoundError,
+                ) as e:
                     invalid_links[link] = self._error_message(e)
-        return {'invalid': invalid_links}
+        return {"invalid": invalid_links}
 
     def check_bug_links(self, links):
         """Checks links of the form /bugs/100"""
@@ -89,24 +94,25 @@ class LinkCheckerAPI(LaunchpadView):
         valid_links = {}
         user = self.user
         # List of all the bugs we are checking.
-        bugs_ids = {int(link[len('/bugs/'):]) for link in links}
+        bugs_ids = {int(link[len("/bugs/") :]) for link in links}
         if bugs_ids:
             params = BugTaskSearchParams(
-                user=user, status=None,
-                bug=any(*bugs_ids))
+                user=user, status=None, bug=any(*bugs_ids)
+            )
             bugtasks = getUtility(IBugTaskSet).search(params)
             for task in bugtasks:
-                valid_links['/bugs/' + str(task.bug.id)] = task.bug.title
+                valid_links["/bugs/" + str(task.bug.id)] = task.bug.title
                 # Remove valid bugs from the list of all the bugs.
                 if task.bug.id in bugs_ids:
                     bugs_ids.remove(task.bug.id)
             # We should now have only invalid bugs in bugs list
             for bug in bugs_ids:
-                invalid_links['/bugs/%d' % bug] = (
-                    "Bug %s cannot be found" % bug)
-        return {'valid': valid_links, 'invalid': invalid_links}
+                invalid_links["/bugs/%d" % bug] = (
+                    "Bug %s cannot be found" % bug
+                )
+        return {"valid": valid_links, "invalid": invalid_links}
 
     def _error_message(self, ex):
-        if hasattr(ex, 'display_message'):
+        if hasattr(ex, "display_message"):
             return ex.display_message
         return str(ex)
diff --git a/lib/lp/app/browser/multistep.py b/lib/lp/app/browser/multistep.py
index 6215eca..a6b7aee 100644
--- a/lib/lp/app/browser/multistep.py
+++ b/lib/lp/app/browser/multistep.py
@@ -4,9 +4,9 @@
 """Multiple step views."""
 
 __all__ = [
-    'MultiStepView',
-    'StepView',
-    ]
+    "MultiStepView",
+    "StepView",
+]
 
 
 from zope.formlib import form
@@ -16,23 +16,19 @@ from zope.interface import Interface
 from zope.schema import TextLine
 
 from lp import _
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
-from lp.services.webapp import (
-    canonical_url,
-    LaunchpadView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
+from lp.services.webapp import LaunchpadView, canonical_url
 
 
 class _IStepMachinery(Interface):
     """A schema solely for the multistep wizard machinery."""
+
     __visited_steps__ = TextLine(
-        title=_('Visited steps'),
+        title=_("Visited steps"),
         description=_(
-            "Used to keep track of the steps visited in a multistep form.")
-        )
+            "Used to keep track of the steps visited in a multistep form."
+        ),
+    )
 
 
 class MultiStepView(LaunchpadView):
@@ -85,7 +81,7 @@ class MultiStepView(LaunchpadView):
     def initialize(self):
         """Initialize the view and handle stepping through sub-views."""
         view = self.first_step(self.context, self.request)
-        assert isinstance(view, StepView), 'Not a StepView: %s' % view
+        assert isinstance(view, StepView), "Not a StepView: %s" % view
         # We should be calling injectStepNameInRequest() after initialize() in
         # both this case and inside the loop below, otherwise the form will be
         # processed when it's first rendered, thus showing warning/error
@@ -102,14 +98,14 @@ class MultiStepView(LaunchpadView):
 
         action_required = None
         for name in self.request.form.keys():
-            if name.startswith('field.actions.'):
+            if name.startswith("field.actions."):
                 action_required = (name, self.request.form[name])
                 break
 
         action_taken = view.action_taken
         while view.next_step is not None:
             view = view.next_step(self.context, self.request)
-            assert isinstance(view, StepView), 'Not a StepView: %s' % view
+            assert isinstance(view, StepView), "Not a StepView: %s" % view
             view.initialize()
             view.step_number = self.step_number
             view.total_steps = self.total_steps
@@ -126,8 +122,8 @@ class MultiStepView(LaunchpadView):
             # in invalid form data via a dictionary instead of
             # using a test browser.
             raise AssertionError(
-                'MultiStepView did not find action for %s=%r'
-                % action_required)
+                "MultiStepView did not find action for %s=%r" % action_required
+            )
 
     def render(self):
         return self.view.render()
@@ -146,13 +142,15 @@ class StepView(LaunchpadFormView):
     If views want to change the label of their Continue button, they should
     override `main_action_label`.
     """
+
     # Use a custom widget in order to make it invisible.
     custom_widget___visited_steps__ = CustomWidgetFactory(
-        TextWidget, visible=False)
+        TextWidget, visible=False
+    )
 
     _field_names = []
-    step_name = ''
-    main_action_label = 'Continue'
+    step_name = ""
+    main_action_label = "Continue"
     next_step = None
 
     # Step information.  These get filled in by the controller view.
@@ -161,7 +159,7 @@ class StepView(LaunchpadFormView):
 
     # Define this here so it can be overridden in subclasses, if for some
     # crazy reason you need step names with vertical bars in them.
-    SEPARATOR = '|'
+    SEPARATOR = "|"
 
     def extendFields(self):
         """See `LaunchpadFormView`."""
@@ -170,7 +168,7 @@ class StepView(LaunchpadFormView):
     @property
     def field_names(self):
         """Do not override."""
-        return self._field_names + ['__visited_steps__']
+        return self._field_names + ["__visited_steps__"]
 
     def validateStep(self, data):
         """Validation specific to a given step.
@@ -182,7 +180,7 @@ class StepView(LaunchpadFormView):
     def main_action(self, data):
         raise NotImplementedError
 
-    @action(main_action_label, name='continue')
+    @action(main_action_label, name="continue")
     def continue_action(self, action, data):
         """The action of the continue button.
 
@@ -207,14 +205,14 @@ class StepView(LaunchpadFormView):
 
     def injectStepNameInRequest(self):
         """Inject this step's name into the request if necessary."""
-        visited_steps = self.request.form.get('field.__visited_steps__')
+        visited_steps = self.request.form.get("field.__visited_steps__")
         if not visited_steps:
-            self.request.form['field.__visited_steps__'] = self.step_name
+            self.request.form["field.__visited_steps__"] = self.step_name
         elif self.step_name not in visited_steps:
             steps = visited_steps.split(self.SEPARATOR)
             steps.append(self.step_name)
             new_steps = self.SEPARATOR.join(steps)
-            self.request.form['field.__visited_steps__'] = new_steps
+            self.request.form["field.__visited_steps__"] = new_steps
         else:
             # We already visited this step, so there's no need to inject our
             # step_name in the request again.
@@ -230,7 +228,7 @@ class StepView(LaunchpadFormView):
         that to find out whether or not to process them, so we use an extra
         hidden input to store the views the user has visited already.
         """
-        steps = data['__visited_steps__'].split('|')
+        steps = data["__visited_steps__"].split("|")
         return self.step_name in steps
 
     def render(self):
@@ -239,7 +237,7 @@ class StepView(LaunchpadFormView):
         actions = []
         for operation in self.actions:
             # Only change the label of our 'continue' action.
-            if operation.__name__ == 'field.actions.continue':
+            if operation.__name__ == "field.actions.continue":
                 operation.label = self.main_action_label
             actions.append(operation)
         self.actions = actions
diff --git a/lib/lp/app/browser/root.py b/lib/lp/app/browser/root.py
index 694c8d2..7d5acda 100644
--- a/lib/lp/app/browser/root.py
+++ b/lib/lp/app/browser/root.py
@@ -3,17 +3,17 @@
 """Browser code for the Launchpad root page."""
 
 __all__ = [
-    'LaunchpadRootIndexView',
-    'LaunchpadSearchView',
-    ]
+    "LaunchpadRootIndexView",
+    "LaunchpadSearchView",
+]
 
 
 import re
 import time
 
 import feedparser
-from lazr.batchnavigator.z3batching import batch
 import requests
+from lazr.batchnavigator.z3batching import batch
 from zope.component import getUtility
 from zope.formlib.interfaces import ConversionError
 from zope.interface import Interface
@@ -23,11 +23,7 @@ from zope.schema.vocabulary import getVocabularyRegistry
 
 from lp import _
 from lp.answers.interfaces.questioncollection import IQuestionSet
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    safe_action,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action, safe_action
 from lp.app.errors import NotFoundError
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.app.validators.name import sanitize_name
@@ -41,9 +37,9 @@ from lp.services.features import getFeatureFlag
 from lp.services.memcache.interfaces import IMemcacheClient
 from lp.services.propertycache import cachedproperty
 from lp.services.sitesearch.interfaces import (
-    active_search_service,
     SiteSearchResponseError,
-    )
+    active_search_service,
+)
 from lp.services.statistics.interfaces.statistic import ILaunchpadStatisticSet
 from lp.services.timeout import urlfetch
 from lp.services.webapp import LaunchpadView
@@ -52,14 +48,13 @@ from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.publisher import canonical_url
 from lp.services.webapp.vhosts import allvhosts
 
-
-shipit_faq_url = 'http://www.ubuntu.com/getubuntu/shipit-faq'
+shipit_faq_url = "http://www.ubuntu.com/getubuntu/shipit-faq";
 
 
 class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
     """An view for the default view of the LaunchpadRoot."""
 
-    page_title = 'Launchpad'
+    page_title = "Launchpad"
     featured_projects = []
     featured_projects_top = None
 
@@ -82,7 +77,8 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
         # The maximum number of projects to be displayed as defined by the
         # number of items plus one top featured project.
         self.featured_projects = list(
-            getUtility(IPillarNameSet).featured_projects)
+            getUtility(IPillarNameSet).featured_projects
+        )
         self._setFeaturedProjectsTop()
 
     def _setFeaturedProjectsTop(self):
@@ -91,36 +87,38 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
         if project_count > 0:
             top_project = self._get_day_of_year() % project_count
             self.featured_projects_top = self.featured_projects.pop(
-                top_project)
+                top_project
+            )
 
     @cachedproperty
     def apphomes(self):
         return {
-            'answers': canonical_url(self.context, rootsite='answers'),
-            'blueprints': canonical_url(self.context, rootsite='blueprints'),
-            'bugs': canonical_url(self.context, rootsite='bugs'),
-            'code': canonical_url(self.context, rootsite='code'),
-            'translations': canonical_url(self.context,
-                                          rootsite='translations'),
-            'ubuntu': canonical_url(
-                getUtility(ILaunchpadCelebrities).ubuntu),
-            }
+            "answers": canonical_url(self.context, rootsite="answers"),
+            "blueprints": canonical_url(self.context, rootsite="blueprints"),
+            "bugs": canonical_url(self.context, rootsite="bugs"),
+            "code": canonical_url(self.context, rootsite="code"),
+            "translations": canonical_url(
+                self.context, rootsite="translations"
+            ),
+            "ubuntu": canonical_url(getUtility(ILaunchpadCelebrities).ubuntu),
+        }
 
     @property
     def branch_count(self):
         """The total branch count of public branches in all of Launchpad."""
-        return getUtility(ILaunchpadStatisticSet).value('public_branch_count')
+        return getUtility(ILaunchpadStatisticSet).value("public_branch_count")
 
     @property
     def gitrepository_count(self):
         """The total Git repository count in all of Launchpad."""
         return getUtility(ILaunchpadStatisticSet).value(
-            'public_git_repository_count')
+            "public_git_repository_count"
+        )
 
     @property
     def bug_count(self):
         """The total bug count in all of Launchpad."""
-        return getUtility(ILaunchpadStatisticSet).value('bug_count')
+        return getUtility(ILaunchpadStatisticSet).value("bug_count")
 
     @property
     def project_count(self):
@@ -129,19 +127,20 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
 
     @property
     def translation_count(self):
-        """The total count of translatable strings in all of Launchpad """
-        return getUtility(ILaunchpadStatisticSet).value('pomsgid_count')
+        """The total count of translatable strings in all of Launchpad"""
+        return getUtility(ILaunchpadStatisticSet).value("pomsgid_count")
 
     @property
     def blueprint_count(self):
         """The total blueprint count in all of Launchpad."""
         return getUtility(ILaunchpadStatisticSet).value(
-            'public_specification_count')
+            "public_specification_count"
+        )
 
     @property
     def answer_count(self):
         """The total blueprint count in all of Launchpad."""
-        return getUtility(ILaunchpadStatisticSet).value('question_count')
+        return getUtility(ILaunchpadStatisticSet).value("question_count")
 
     @property
     def show_whatslaunchpad(self):
@@ -165,7 +164,7 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
 
         FeedParser takes care of sanitizing the HTML contained in the feed.
         """
-        key = '%s:homepage-blog-posts' % config.instance_name
+        key = "%s:homepage-blog-posts" % config.instance_name
         cached_data = getUtility(IMemcacheClient).get(key)
         if cached_data:
             return cached_data
@@ -179,12 +178,14 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
         max_count = config.launchpad.homepage_recent_posts_count
         # FeedParser takes care of HTML sanitisation.
         for entry in feed.entries[:max_count]:
-            posts.append({
-                'title': entry.title,
-                'description': entry.description,
-                'link': entry.link,
-                'date': time.strftime('%d %b %Y', entry.published_parsed),
-                })
+            posts.append(
+                {
+                    "title": entry.title,
+                    "description": entry.description,
+                    "link": entry.link,
+                    "date": time.strftime("%d %b %Y", entry.published_parsed),
+                }
+            )
         # The cache of posts expires after an hour.
         getUtility(IMemcacheClient).set(key, posts, expire=3600)
         return posts
@@ -192,7 +193,8 @@ class LaunchpadRootIndexView(HasAnnouncementsView, LaunchpadView):
 
 class LaunchpadSearchFormView(LaunchpadView):
     """A view to display the global search form in any page."""
-    id_suffix = '-secondary'
+
+    id_suffix = "-secondary"
     text = None
     focusedElementScript = None
     form_wide_errors = None
@@ -204,12 +206,13 @@ class LaunchpadSearchFormView(LaunchpadView):
     @property
     def rooturl(self):
         """Return the site's root url."""
-        return allvhosts.configs['mainsite'].rooturl
+        return allvhosts.configs["mainsite"].rooturl
 
 
 class LaunchpadPrimarySearchFormView(LaunchpadSearchFormView):
     """A view to display the global search form in the page."""
-    id_suffix = ''
+
+    id_suffix = ""
 
     @property
     def text(self):
@@ -239,35 +242,56 @@ class LaunchpadPrimarySearchFormView(LaunchpadSearchFormView):
     @property
     def error(self):
         """The context view's text field error."""
-        return self.context.getFieldError('text')
+        return self.context.getFieldError("text")
 
     @property
     def error_class(self):
         """Return the 'error' if there is an error, or None."""
         if self.error:
-            return 'error'
+            return "error"
         return None
 
 
 class ILaunchpadSearch(Interface):
     """The Schema for performing searches across all Launchpad."""
 
-    text = TextLine(
-        title=_('Search text'), required=False, max_length=250)
+    text = TextLine(title=_("Search text"), required=False, max_length=250)
 
 
 class LaunchpadSearchView(LaunchpadFormView):
     """A view to search for Launchpad pages and objects."""
+
     schema = ILaunchpadSearch
-    field_names = ['text']
+    field_names = ["text"]
 
     shipit_keywords = {
-        'ubuntu', 'kubuntu', 'edubuntu',
-        'ship', 'shipit', 'send', 'get', 'mail', 'free',
-        'cd', 'cds', 'dvd', 'dvds', 'disc'}
+        "ubuntu",
+        "kubuntu",
+        "edubuntu",
+        "ship",
+        "shipit",
+        "send",
+        "get",
+        "mail",
+        "free",
+        "cd",
+        "cds",
+        "dvd",
+        "dvds",
+        "disc",
+    }
     shipit_anti_keywords = {
-        'burn', 'burning', 'enable', 'error', 'errors', 'image', 'iso',
-        'read', 'rip', 'write'}
+        "burn",
+        "burning",
+        "enable",
+        "error",
+        "errors",
+        "image",
+        "iso",
+        "read",
+        "rip",
+        "write",
+    }
 
     def __init__(self, context, request):
         """Initialize the view.
@@ -283,39 +307,39 @@ class LaunchpadSearchView(LaunchpadFormView):
         self._pages = None
         self.search_params = self._getDefaultSearchParams()
         # The Search Action should always run.
-        self.request.form['field.actions.search'] = 'Search'
+        self.request.form["field.actions.search"] = "Search"
 
     def _getDefaultSearchParams(self):
         """Return a dict of the search param set to their default state."""
         return {
-            'text': None,
-            'start': 0,
-            }
+            "text": None,
+            "start": 0,
+        }
 
     def _updateSearchParams(self):
         """Sanitize the search_params and add the BatchNavigator params."""
-        if self.search_params['text'] is not None:
-            text = self.search_params['text'].strip()
-            if text == '':
-                self.search_params['text'] = None
+        if self.search_params["text"] is not None:
+            text = self.search_params["text"].strip()
+            if text == "":
+                self.search_params["text"] = None
             else:
-                self.search_params['text'] = text
-        request_start = self.request.get('start', self.search_params['start'])
+                self.search_params["text"] = text
+        request_start = self.request.get("start", self.search_params["start"])
         try:
             start = int(request_start)
         except (ValueError, TypeError):
             return
-        self.search_params['start'] = start
+        self.search_params["start"] = start
 
     @property
     def text(self):
         """Return the text or None."""
-        return self.search_params['text']
+        return self.search_params["text"]
 
     @property
     def start(self):
         """Return the start index of the batch."""
-        return self.search_params['start']
+        return self.search_params["start"]
 
     @property
     def page_title(self):
@@ -326,7 +350,7 @@ class LaunchpadSearchView(LaunchpadFormView):
     def page_heading(self):
         """Heading to display above the search results."""
         if self.text is None:
-            return 'Search Launchpad'
+            return "Search Launchpad"
         else:
             return 'Pages matching "%s" in Launchpad' % self.text
 
@@ -334,11 +358,15 @@ class LaunchpadSearchView(LaunchpadFormView):
     def batch_heading(self):
         """Heading to display in the batch navigation."""
         if self.has_exact_matches:
-            return ('other page matching "%s"' % self.text,
-                    'other pages matching "%s"' % self.text)
+            return (
+                'other page matching "%s"' % self.text,
+                'other pages matching "%s"' % self.text,
+            )
         else:
-            return ('page matching "%s"' % self.text,
-                    'pages matching "%s"' % self.text)
+            return (
+                'page matching "%s"' % self.text,
+                'pages matching "%s"' % self.text,
+            )
 
     @property
     def focusedElementScript(self):
@@ -387,8 +415,13 @@ class LaunchpadSearchView(LaunchpadFormView):
     @property
     def has_exact_matches(self):
         """Return True if something exactly matched the search terms."""
-        kinds = (self.bug, self.question, self.pillar,
-                 self.person_or_team, self.has_shipit)
+        kinds = (
+            self.bug,
+            self.question,
+            self.pillar,
+            self.person_or_team,
+            self.has_shipit,
+        )
         return self.containsMatchingKind(kinds)
 
     @property
@@ -399,18 +432,24 @@ class LaunchpadSearchView(LaunchpadFormView):
     @property
     def has_matches(self):
         """Return True if something matched the search terms, or False."""
-        kinds = (self.bug, self.question, self.pillar,
-                 self.person_or_team, self.has_shipit, self.pages)
+        kinds = (
+            self.bug,
+            self.question,
+            self.pillar,
+            self.person_or_team,
+            self.has_shipit,
+            self.pages,
+        )
         return self.containsMatchingKind(kinds)
 
     @property
     def url(self):
         """Return the requested URL."""
-        if 'QUERY_STRING' in self.request:
-            query_string = self.request['QUERY_STRING']
+        if "QUERY_STRING" in self.request:
+            query_string = self.request["QUERY_STRING"]
         else:
-            query_string = ''
-        return self.request.getURL() + '?' + query_string
+            query_string = ""
+        return self.request.getURL() + "?" + query_string
 
     def containsMatchingKind(self, kinds):
         """Return True if one of the items in kinds is not None, or False."""
@@ -424,17 +463,18 @@ class LaunchpadSearchView(LaunchpadFormView):
         errors = list(self.errors)
         for error in errors:
             if isinstance(error, ConversionError):
-                self.setFieldError(
-                    'text', 'Can not convert your search term.')
+                self.setFieldError("text", "Can not convert your search term.")
             elif isinstance(error, str):
                 continue
-            elif (error.field_name == 'text'
-                and isinstance(error.errors, TooLong)):
+            elif error.field_name == "text" and isinstance(
+                error.errors, TooLong
+            ):
                 self.setFieldError(
-                    'text', 'The search text cannot exceed 250 characters.')
+                    "text", "The search text cannot exceed 250 characters."
+                )
 
     @safe_action
-    @action('Search', name='search')
+    @action("Search", name="search")
     def search_action(self, action, data):
         """The Action executed when the user uses the search button.
 
@@ -462,13 +502,14 @@ class LaunchpadSearchView(LaunchpadFormView):
             if name_token is not None:
                 self._person_or_team = self._getPersonOrTeam(name_token)
                 self._pillar = self._getDistributionOrProductOrProjectGroup(
-                    name_token)
+                    name_token
+                )
 
         self._pages = self.searchPages(self.text, start=self.start)
 
     def _getNumericToken(self, text):
         """Return the first group of numbers in the search text, or None."""
-        numeric_pattern = re.compile(r'(\d+)')
+        numeric_pattern = re.compile(r"(\d+)")
         match = numeric_pattern.search(text)
         if match is None:
             return None
@@ -480,16 +521,18 @@ class LaunchpadSearchView(LaunchpadFormView):
         Launchpad names may contain ^[a-z0-9][a-z0-9\+\.\-]+$.
         See `valid_name_pattern`.
         """
-        hypen_pattern = re.compile(r'[ _]')
-        name = hypen_pattern.sub('-', text.strip().lower())
+        hypen_pattern = re.compile(r"[ _]")
+        name = hypen_pattern.sub("-", text.strip().lower())
         return sanitize_name(name)
 
     def _getPersonOrTeam(self, name):
         """Return the matching active person or team."""
         person_or_team = getUtility(IPersonSet).getByName(name)
-        if (person_or_team is not None
+        if (
+            person_or_team is not None
             and person_or_team.is_valid_person_or_team
-            and check_permission('launchpad.View', person_or_team)):
+            and check_permission("launchpad.View", person_or_team)
+        ):
             return person_or_team
         return None
 
@@ -497,7 +540,8 @@ class LaunchpadSearchView(LaunchpadFormView):
         """Return the matching distribution, product or project, or None."""
         vocabulary_registry = getVocabularyRegistry()
         vocab = vocabulary_registry.get(
-            None, 'DistributionOrProductOrProjectGroup')
+            None, "DistributionOrProductOrProjectGroup"
+        )
         try:
             pillar = vocab.getTermByToken(name).value
             if check_permission("launchpad.View", pillar):
@@ -513,12 +557,11 @@ class LaunchpadSearchView(LaunchpadFormView):
         :param start: The index of the page that starts the set of pages.
         :return: A SiteSearchBatchNavigator or None.
         """
-        if query_terms in [None, '']:
+        if query_terms in [None, ""]:
             return None
         site_search = active_search_service()
         try:
-            page_matches = site_search.search(
-                terms=query_terms, start=start)
+            page_matches = site_search.search(terms=query_terms, start=start)
         except SiteSearchResponseError:
             # There was a connectivity or search service issue that means
             # there is no data available at this moment.
@@ -527,7 +570,8 @@ class LaunchpadSearchView(LaunchpadFormView):
         if len(page_matches) == 0:
             return None
         navigator = SiteSearchBatchNavigator(
-            page_matches, self.request, start=start)
+            page_matches, self.request, start=start
+        )
         navigator.setHeadings(*self.batch_heading)
         return navigator
 
@@ -596,12 +640,20 @@ class SiteSearchBatchNavigator(BatchNavigator):
     # good chance of getting over 100,000 results.
     show_last_link = False
 
-    singular_heading = 'page'
-    plural_heading = 'pages'
-
-    def __init__(self, results, request, start=0, size=20, callback=None,
-                 transient_parameters=None, force_start=False,
-                 range_factory=None):
+    singular_heading = "page"
+    plural_heading = "pages"
+
+    def __init__(
+        self,
+        results,
+        request,
+        start=0,
+        size=20,
+        callback=None,
+        transient_parameters=None,
+        force_start=False,
+        range_factory=None,
+    ):
         """See `BatchNavigator`.
 
         :param results: A `PageMatches` object that contains the matching
@@ -613,10 +665,16 @@ class SiteSearchBatchNavigator(BatchNavigator):
         :param callback: Not used.
         """
         results = WindowedList(results, start, results.total)
-        super().__init__(results, request,
-            start=start, size=size, callback=callback,
+        super().__init__(
+            results,
+            request,
+            start=start,
+            size=size,
+            callback=callback,
             transient_parameters=transient_parameters,
-            force_start=force_start, range_factory=range_factory)
+            force_start=force_start,
+            range_factory=range_factory,
+        )
 
     def determineSize(self, size, batch_params_source):
         # Force the default and users requested sizes to 20.
diff --git a/lib/lp/app/browser/stringformatter.py b/lib/lp/app/browser/stringformatter.py
index 4ca97e1..f54f44a 100644
--- a/lib/lp/app/browser/stringformatter.py
+++ b/lib/lp/app/browser/stringformatter.py
@@ -4,48 +4,38 @@
 """TALES formatter for strings."""
 
 __all__ = [
-    'add_word_breaks',
-    'break_long_words',
-    'extract_bug_numbers',
-    'extract_email_addresses',
-    'FormattersAPI',
-    'linkify_bug_numbers',
-    'parse_diff',
-    're_substitute',
-    'split_paragraphs',
-    ]
+    "add_word_breaks",
+    "break_long_words",
+    "extract_bug_numbers",
+    "extract_email_addresses",
+    "FormattersAPI",
+    "linkify_bug_numbers",
+    "parse_diff",
+    "re_substitute",
+    "split_paragraphs",
+]
 
-from base64 import urlsafe_b64encode
-from itertools import zip_longest
 import re
 import sys
+from base64 import urlsafe_b64encode
+from itertools import zip_longest
 
-from breezy.patches import hunk_from_header
-from lxml import html
 import markdown
 import six
+from breezy.patches import hunk_from_header
+from lxml import html
 from zope.component import getUtility
 from zope.error.interfaces import IErrorReportingUtility
 from zope.interface import implementer
-from zope.traversing.interfaces import (
-    ITraversable,
-    TraversalError,
-    )
+from zope.traversing.interfaces import ITraversable, TraversalError
 
 from lp.answers.interfaces.faq import IFAQSet
 from lp.registry.interfaces.person import IPersonSet
 from lp.services.config import config
 from lp.services.features import getFeatureFlag
-from lp.services.utils import (
-    obfuscate_email,
-    re_email_address,
-    )
+from lp.services.utils import obfuscate_email, re_email_address
 from lp.services.webapp import canonical_url
-from lp.services.webapp.escaping import (
-    html_escape,
-    html_unescape,
-    structured,
-    )
+from lp.services.webapp.escaping import html_escape, html_unescape, structured
 from lp.services.webapp.interfaces import ILaunchBag
 
 
@@ -94,13 +84,13 @@ def re_substitute(pattern, replace_match, replace_nomatch, string):
     position = 0
     for match in re.finditer(pattern, string):
         if match.start() != position:
-            parts.append(replace_nomatch(string[position:match.start()]))
+            parts.append(replace_nomatch(string[position : match.start()]))
         parts.append(replace_match(match))
         position = match.end()
     remainder = string[position:]
     if remainder:
         parts.append(replace_nomatch(remainder))
-    return ''.join(parts)
+    return "".join(parts)
 
 
 def next_word_chunk(word, pos, minlen, maxlen):
@@ -117,10 +107,10 @@ def next_word_chunk(word, pos, minlen, maxlen):
     endpos = pos
     while endpos < len(word):
         # advance by one character
-        if word[endpos] == '&':
+        if word[endpos] == "&":
             # make sure we grab the entity as a whole
-            semicolon = word.find(';', endpos)
-            assert semicolon >= 0, 'badly formed entity: %r' % word[endpos:]
+            semicolon = word.find(";", endpos)
+            assert semicolon >= 0, "badly formed entity: %r" % word[endpos:]
             endpos = semicolon + 1
         else:
             endpos += 1
@@ -149,17 +139,20 @@ def add_word_breaks(word):
     while pos < len(word):
         chunk, pos = next_word_chunk(word, pos, 7, 15)
         broken.append(chunk)
-    return '<wbr />'.join(broken)
+    return "<wbr />".join(broken)
 
 
-break_text_pat = re.compile(r'''
+break_text_pat = re.compile(
+    r"""
   (?P<tag>
     <[^>]*>
   ) |
   (?P<longword>
     (?<![^\s<>])(?:[^\s<>&]|&[^;]*;){20,}
   )
-''', re.VERBOSE)
+""",
+    re.VERBOSE,
+)
 
 
 def break_long_words(text):
@@ -169,17 +162,18 @@ def break_long_words(text):
     """
 
     def replace(match):
-        if match.group('tag'):
+        if match.group("tag"):
             return match.group()
-        elif match.group('longword'):
+        elif match.group("longword"):
             return add_word_breaks(match.group())
         else:
-            raise AssertionError('text matched but neither named group found')
+            raise AssertionError("text matched but neither named group found")
+
     return break_text_pat.sub(replace, text)
 
 
 def extract_bug_numbers(text):
-    '''Unique bug numbers matching the "LP: #n(, #n)*" pattern in the text.'''
+    """Unique bug numbers matching the "LP: #n(, #n)*" pattern in the text."""
     # FormattersAPI._linkify_substitution requires a match object
     # that has named groups "bug" and "bugnum".  The matching text for
     # the "bug" group is used as the link text and "bugnum" forms part
@@ -199,16 +193,16 @@ def extract_bug_numbers(text):
     unique_bug_matches = dict()
 
     line_matches = re.finditer(
-        r'LP:\s*(?P<buglist>(.+?[^,]))($|\n)', text,
-        re.DOTALL | re.IGNORECASE)
+        r"LP:\s*(?P<buglist>(.+?[^,]))($|\n)", text, re.DOTALL | re.IGNORECASE
+    )
 
     for line_match in line_matches:
         bug_matches = re.finditer(
-            r'\s*((?P<bug>#(?P<bugnum>\d+)),?\s*)',
-            line_match.group('buglist'))
+            r"\s*((?P<bug>#(?P<bugnum>\d+)),?\s*)", line_match.group("buglist")
+        )
 
         for bug_match in bug_matches:
-            bugnum = bug_match.group('bugnum')
+            bugnum = bug_match.group("bugnum")
             if bugnum in unique_bug_matches:
                 # We got this bug already, ignore it.
                 continue
@@ -221,7 +215,7 @@ def linkify_bug_numbers(text):
     """Linkify to a bug if LP: #number appears in the (changelog) text."""
     unique_bug_matches = extract_bug_numbers(text)
     for bug_match in unique_bug_matches.values():
-        replace_text = bug_match.group('bug')
+        replace_text = bug_match.group("bug")
         if replace_text is not None:
             # XXX julian 2008-01-10
             # Note that re.sub would be far more efficient to use
@@ -235,13 +229,13 @@ def linkify_bug_numbers(text):
             # #999.  The liklihood of this happening is very, very
             # small, however.
             text = text.replace(
-                replace_text,
-                FormattersAPI._linkify_substitution(bug_match))
+                replace_text, FormattersAPI._linkify_substitution(bug_match)
+            )
     return text
 
 
 def extract_email_addresses(text):
-    '''Unique email addresses in the text.'''
+    """Unique email addresses in the text."""
     matches = re.finditer(re_email_address, text)
     return list({match.group() for match in matches})
 
@@ -258,18 +252,20 @@ def parse_diff(text):
     mod_row = 0
     for row, line in enumerate(text.splitlines()[:max_format_lines]):
         row += 1
-        if (line.startswith('===') or
-                line.startswith('diff') or
-                line.startswith('index')):
-            yield 'diff-file text', row, None, None, line
+        if (
+            line.startswith("===")
+            or line.startswith("diff")
+            or line.startswith("index")
+        ):
+            yield "diff-file text", row, None, None, line
             header_next = True
-        elif (header_next and
-              (line.startswith('+++') or
-              line.startswith('---'))):
-            yield 'diff-header text', row, None, None, line
-        elif line.startswith('@@'):
+        elif header_next and (
+            line.startswith("+++") or line.startswith("---")
+        ):
+            yield "diff-header text", row, None, None, line
+        elif line.startswith("@@"):
             try:
-                hunk = hunk_from_header((line + '\n').encode('UTF-8'))
+                hunk = hunk_from_header((line + "\n").encode("UTF-8"))
                 # The positions indicate the per-file line numbers of
                 # the next row.
                 orig_row = hunk.orig_pos
@@ -278,22 +274,22 @@ def parse_diff(text):
                 getUtility(IErrorReportingUtility).raising(sys.exc_info())
                 orig_row = 1
                 mod_row = 1
-            yield 'diff-chunk text', row, None, None, line
+            yield "diff-chunk text", row, None, None, line
             header_next = False
-        elif line.startswith('+'):
-            yield 'diff-added text', row, None, mod_row, line
+        elif line.startswith("+"):
+            yield "diff-added text", row, None, mod_row, line
             mod_row += 1
-        elif line.startswith('-'):
-            yield 'diff-removed text', row, orig_row, None, line
+        elif line.startswith("-"):
+            yield "diff-removed text", row, orig_row, None, line
             orig_row += 1
-        elif line.startswith('#'):
+        elif line.startswith("#"):
             # This doesn't occur in normal unified diffs, but does
             # appear in merge directives, which use text/x-diff or
             # text/x-patch.
-            yield 'diff-comment text', row, None, None, line
+            yield "diff-comment text", row, None, None, line
             header_next = False
         else:
-            yield 'text', row, orig_row, mod_row, line
+            yield "text", row, orig_row, mod_row, line
             orig_row += 1
             mod_row += 1
             header_next = False
@@ -308,7 +304,7 @@ class FormattersAPI:
 
     def nl_to_br(self):
         """Quote HTML characters, then replace newlines with <br /> tags."""
-        return html_escape(self._stringtoformat).replace('\n', '<br />\n')
+        return html_escape(self._stringtoformat).replace("\n", "<br />\n")
 
     def escape(self):
         return html_escape(self._stringtoformat)
@@ -328,16 +324,15 @@ class FormattersAPI:
         """
         groups = match.groups()
         assert len(groups) == 1
-        return '&nbsp;' * len(groups[0])
+        return "&nbsp;" * len(groups[0])
 
     @staticmethod
-    def _linkify_bug_number(text, bugnum, trailers=''):
+    def _linkify_bug_number(text, bugnum, trailers=""):
         # Don't look up the bug or display anything about the bug, just
         # linkify to the general bug url.
-        url = '/bugs/%s' % bugnum
+        url = "/bugs/%s" % bugnum
         # The text will have already been cgi escaped.
-        return '<a href="%s" class="bug-link">%s</a>%s' % (
-            url, text, trailers)
+        return '<a href="%s" class="bug-link">%s</a>%s' % (url, text, trailers)
 
     @staticmethod
     def _handle_parens_in_trailers(url, trailers):
@@ -346,13 +341,13 @@ class FormattersAPI:
         If there are opening parens in the url that are matched by closing
         parens at the start of the trailer, those closing parens should be
         part of the url."""
-        assert trailers != '', ("Trailers must not be an empty string.")
-        opencount = url.count('(')
-        closedcount = url.count(')')
+        assert trailers != "", "Trailers must not be an empty string."
+        opencount = url.count("(")
+        closedcount = url.count(")")
         missing = opencount - closedcount
         slice_idx = 0
         while slice_idx < missing:
-            if trailers[slice_idx] == ')':
+            if trailers[slice_idx] == ")":
                 slice_idx += 1
             else:
                 break
@@ -369,47 +364,49 @@ class FormattersAPI:
         match = FormattersAPI._re_url_trailers.search(url)
         if match:
             trailers = match.group(1)
-            url = url[:-len(trailers)]
+            url = url[: -len(trailers)]
             return FormattersAPI._handle_parens_in_trailers(url, trailers)
         else:
             # No match, return URL with empty string for trailers
-            return url, ''
+            return url, ""
 
     @staticmethod
     def _linkify_url_should_be_ignored(url):
         """Don't linkify URIs consisting of just the protocol."""
 
         protocol_bases = [
-            'about',
-            'gopher',
-            'http',
-            'https',
-            'sftp',
-            'news',
-            'ftp',
-            'mailto',
-            'irc',
-            'jabber',
-            'apt',
-            ]
+            "about",
+            "gopher",
+            "http",
+            "https",
+            "sftp",
+            "news",
+            "ftp",
+            "mailto",
+            "irc",
+            "jabber",
+            "apt",
+        ]
 
         for base in protocol_bases:
-            if url in ('%s' % base, '%s:' % base, '%s://' % base):
+            if url in ("%s" % base, "%s:" % base, "%s://" % base):
                 return True
         return False
 
     @staticmethod
     def _linkify_substitution(match):
-        if match.group('bug') is not None:
+        if match.group("bug") is not None:
             return FormattersAPI._linkify_bug_number(
-                match.group('bug'), match.group('bugnum'))
-        elif match.group('url') is not None:
+                match.group("bug"), match.group("bugnum")
+            )
+        elif match.group("url") is not None:
             # The text will already have been cgi escaped.  We temporarily
             # unescape it so that we can strip common trailing characters
             # that aren't part of the URL.
-            full_url = match.group('url')
+            full_url = match.group("url")
             url, trailers = FormattersAPI._split_url_and_trailers(
-                html_unescape(full_url))
+                html_unescape(full_url)
+            )
             # We use nofollow for these links to reduce the value of
             # adding spam URLs to our comments; it's a way of moderately
             # devaluing the return on effort for spammers that consider
@@ -422,56 +419,67 @@ class FormattersAPI:
                     'href="%(url)s">%(linked_text)s</a>%(trailers)s',
                     url=url,
                     linked_text=structured(add_word_breaks(html_escape(url))),
-                    trailers=trailers).escapedtext
+                    trailers=trailers,
+                ).escapedtext
             else:
                 return full_url
-        elif match.group('faq') is not None:
+        elif match.group("faq") is not None:
             # This is *BAD*.  We shouldn't be doing database lookups to
             # linkify text.
-            text = match.group('faq')
-            faqnum = match.group('faqnum')
+            text = match.group("faq")
+            faqnum = match.group("faqnum")
             faqset = getUtility(IFAQSet)
             faq = faqset.getFAQ(faqnum)
             if not faq:
                 return text
             url = canonical_url(faq)
             return '<a href="%s">%s</a>' % (url, text)
-        elif match.group('oops') is not None:
-            text = match.group('oops')
+        elif match.group("oops") is not None:
+            text = match.group("oops")
 
             if not getUtility(ILaunchBag).developer:
                 return text
 
             root_url = config.launchpad.oops_root_url
-            url = root_url + "OOPS-" + match.group('oopscode')
+            url = root_url + "OOPS-" + match.group("oopscode")
             return '<a href="%s">%s</a>' % (url, text)
-        elif match.group('lpbranchurl') is not None:
-            lp_url = match.group('lpbranchurl')
-            path = match.group('branch')
+        elif match.group("lpbranchurl") is not None:
+            lp_url = match.group("lpbranchurl")
+            path = match.group("branch")
             lp_url, trailers = FormattersAPI._split_url_and_trailers(
-                html_unescape(lp_url))
+                html_unescape(lp_url)
+            )
             path, trailers = FormattersAPI._split_url_and_trailers(
-                html_unescape(path))
+                html_unescape(path)
+            )
             if path.isdigit():
                 return FormattersAPI._linkify_bug_number(
-                    lp_url, path, trailers)
-            url = '/+code/%s' % path
+                    lp_url, path, trailers
+                )
+            url = "/+code/%s" % path
             # Mark the links with a 'branch-short-link' class so they can be
             # harvested and validated when the page is rendered.
             return structured(
                 '<a href="%s" class="branch-short-link">%s</a>%s',
-                url, lp_url, trailers).escapedtext
+                url,
+                lp_url,
+                trailers,
+            ).escapedtext
         elif match.group("clbug") is not None:
             # 'clbug' matches Ubuntu changelog format bugs. 'bugnumbers' is
             # all of the bug numbers, that look something like "#1234, #434".
             # 'leader' is the 'LP: ' bit at the beginning.
             bug_parts = []
             # Split the bug numbers into multiple bugs.
-            splitted = re.split(r"(,(?:\s|<br\s*/>)+)",
-                    match.group("bugnumbers")) + [""]
+            splitted = re.split(
+                r"(,(?:\s|<br\s*/>)+)", match.group("bugnumbers")
+            ) + [""]
             for bug_id, spacer in zip(splitted[::2], splitted[1::2]):
-                bug_parts.append(FormattersAPI._linkify_bug_number(
-                    bug_id, bug_id.lstrip("#")))
+                bug_parts.append(
+                    FormattersAPI._linkify_bug_number(
+                        bug_id, bug_id.lstrip("#")
+                    )
+                )
                 bug_parts.append(spacer)
             return match.group("leader") + "".join(bug_parts)
         else:
@@ -486,8 +494,8 @@ class FormattersAPI:
         """
         linkified_text = FormattersAPI._linkify_substitution(match)
         element_tree = html.fromstring(linkified_text)
-        for link in element_tree.xpath('//a'):
-            link.set('target', '_new')
+        for link in element_tree.xpath("//a"):
+            link.set("target", "_new")
         # html.tostring returns bytes; we want text.  (Passing
         # encoding='unicode' would cause it to return text, but that would
         # also disable "&#...;" character encoding of non-ASCII characters,
@@ -495,7 +503,7 @@ class FormattersAPI:
         return six.ensure_text(html.tostring(element_tree))
 
     # match whitespace at the beginning of a line
-    _re_leadingspace = re.compile(r'^(\s+)')
+    _re_leadingspace = re.compile(r"^(\s+)")
 
     # From RFC 3986 ABNF for URIs:
     #
@@ -558,7 +566,8 @@ class FormattersAPI:
     # it's probably best to accomodate them.
 
     # Match urls or bugs or oopses.
-    _re_linkify = re.compile(r'''
+    _re_linkify = re.compile(
+        r"""
       (?P<url>
         \b
         (?:about|gopher|http|https|sftp|news|ftp|mailto|irc|jabber|apt)
@@ -624,17 +633,23 @@ class FormattersAPI:
         \blp:(?:///|/)?
         (?P<branch>%(unreserved)s(?:%(unreserved)s|/)*)
       )
-    ''' % {'unreserved': r"(?:[-a-zA-Z0-9._~%!$'()*+,;=]|&amp;|&\#x27;)"},
-                             re.IGNORECASE | re.VERBOSE)
+    """
+        % {"unreserved": r"(?:[-a-zA-Z0-9._~%!$'()*+,;=]|&amp;|&\#x27;)"},
+        re.IGNORECASE | re.VERBOSE,
+    )
 
     # There is various punctuation that can occur at the end of a link that
     # shouldn't be included. The regex below matches on the set of characters
     # we don't generally want. See also _handle_parens_in_trailers, which
     # re-attaches parens if we do want them to be part of the url.
-    _re_url_trailers = re.compile(r'([,.?:);>]+)$')
-
-    def text_to_html(self, linkify_text=True, linkify_substitution=None,
-                     last_paragraph_class=None):
+    _re_url_trailers = re.compile(r"([,.?:);>]+)$")
+
+    def text_to_html(
+        self,
+        linkify_text=True,
+        linkify_substitution=None,
+        last_paragraph_class=None,
+    ):
         """Quote text according to DisplayingParagraphsOfText."""
         # This is based on the algorithm in the
         # DisplayingParagraphsOfText spec, but is a little more
@@ -650,33 +665,35 @@ class FormattersAPI:
         last_paragraph_index = len(paras) - 1
         for index, para in enumerate(paras):
             if index > 0:
-                output.append('\n')
-            css_class = ''
+                output.append("\n")
+            css_class = ""
             if last_paragraph_class and index == last_paragraph_index:
                 css_class = ' class="%s"' % last_paragraph_class
-            output.append('<p%s>' % css_class)
+            output.append("<p%s>" % css_class)
 
             first_line = True
             for line in para:
                 if not first_line:
-                    output.append('<br />\n')
+                    output.append("<br />\n")
                 first_line = False
                 # escape ampersands, etc in text
                 line = html_escape(line)
                 # convert leading space in logical line to non-breaking space
                 line = self._re_leadingspace.sub(
-                    self._substitute_matchgroup_for_spaces, line)
+                    self._substitute_matchgroup_for_spaces, line
+                )
                 output.append(line)
-            output.append('</p>')
+            output.append("</p>")
 
-        text = ''.join(output)
+        text = "".join(output)
 
         # Linkify the text, if allowed.
         if linkify_text is True:
             if linkify_substitution is None:
                 linkify_substitution = self._linkify_substitution
-            text = re_substitute(self._re_linkify, linkify_substitution,
-                break_long_words, text)
+            text = re_substitute(
+                self._re_linkify, linkify_substitution, break_long_words, text
+            )
 
         return text
 
@@ -687,7 +704,8 @@ class FormattersAPI:
         """
         return self.text_to_html(
             linkify_text=True,
-            linkify_substitution=self._linkify_substitution_with_target)
+            linkify_substitution=self._linkify_substitution_with_target,
+        )
 
     def nice_pre(self):
         """<pre>, except the browser knows it is allowed to break long lines
@@ -702,9 +720,12 @@ class FormattersAPI:
         if not self._stringtoformat:
             return self._stringtoformat
         else:
-            linkified_text = re_substitute(self._re_linkify,
-                self._linkify_substitution, break_long_words,
-                html_escape(self._stringtoformat))
+            linkified_text = re_substitute(
+                self._re_linkify,
+                self._linkify_substitution,
+                break_long_words,
+                html_escape(self._stringtoformat),
+            )
             return '<pre class="wrap">%s</pre>' % linkified_text
 
     # Match lines that start with one or more quote symbols followed
@@ -713,11 +734,11 @@ class FormattersAPI:
     # '> > ' are valid quoting sequences.
     # The dpkg version is used for exceptional cases where it
     # is better to not assume '|' is a start of a quoted passage.
-    _re_quoted = re.compile('^(([|] ?)+|(&gt; ?)+)')
-    _re_dpkg_quoted = re.compile('^(&gt; ?)+ ')
+    _re_quoted = re.compile("^(([|] ?)+|(&gt; ?)+)")
+    _re_dpkg_quoted = re.compile("^(&gt; ?)+ ")
 
     # Match blocks that start as signatures or PGP inclusions.
-    _re_include = re.compile('^<p>(--<br />|-----BEGIN PGP)')
+    _re_include = re.compile("^<p>(--<br />|-----BEGIN PGP)")
 
     def email_to_html(self):
         """text_to_html and hide signatures and full-quoted emails.
@@ -731,7 +752,7 @@ class FormattersAPI:
         """
         start_fold_markup = '<span class="foldable">'
         start_fold_quoted_markup = '<span class="foldable-quoted">'
-        end_fold_markup = '%s\n</span></p>'
+        end_fold_markup = "%s\n</span></p>"
         re_quoted = self._re_quoted
         re_include = self._re_include
         output = []
@@ -747,17 +768,20 @@ class FormattersAPI:
             that next and previous lines of text consistently uses '>>> '
             as Python would.
             """
-            python_block = '&gt;&gt;&gt; '
-            return (not line.startswith(python_block)
-                and re_quoted.match(line) is not None)
+            python_block = "&gt;&gt;&gt; "
+            return (
+                not line.startswith(python_block)
+                and re_quoted.match(line) is not None
+            )
 
         def strip_leading_p_tag(line):
             """Return the characters after the paragraph mark (<p>).
 
             The caller must be certain the line starts with a paragraph mark.
             """
-            assert line.startswith('<p>'), (
-                "The line must start with a paragraph mark (<p>).")
+            assert line.startswith(
+                "<p>"
+            ), "The line must start with a paragraph mark (<p>)."
             return line[3:]
 
         def strip_trailing_p_tag(line):
@@ -765,12 +789,13 @@ class FormattersAPI:
 
             The caller must be certain the line ends with a paragraph mark.
             """
-            assert line.endswith('</p>'), (
-                "The line must end with a paragraph mark (</p>).")
+            assert line.endswith(
+                "</p>"
+            ), "The line must end with a paragraph mark (</p>)."
             return line[:-4]
 
-        for line in self.text_to_html().split('\n'):
-            if 'Desired=<wbr />Unknown/' in line and not in_fold:
+        for line in self.text_to_html().split("\n"):
+            if "Desired=<wbr />Unknown/" in line and not in_fold:
                 # When we see a evidence of dpkg output, we switch the
                 # quote matching rules. We do not assume lines that start
                 # with a pipe are quoted passages. dpkg output is often
@@ -782,15 +807,22 @@ class FormattersAPI:
                 # This line is a paragraph with a signature or PGP inclusion.
                 # Start a foldable paragraph.
                 in_fold = True
-                line = '<p>%s%s' % (start_fold_markup,
-                                    strip_leading_p_tag(line))
-            elif (not in_fold and line.startswith('<p>')
-                and is_quoted(strip_leading_p_tag(line))):
+                line = "<p>%s%s" % (
+                    start_fold_markup,
+                    strip_leading_p_tag(line),
+                )
+            elif (
+                not in_fold
+                and line.startswith("<p>")
+                and is_quoted(strip_leading_p_tag(line))
+            ):
                 # The paragraph starts with quoted marks.
                 # Start a foldable quoted paragraph.
                 in_fold = True
-                line = '<p>%s%s' % (
-                    start_fold_quoted_markup, strip_leading_p_tag(line))
+                line = "<p>%s%s" % (
+                    start_fold_quoted_markup,
+                    strip_leading_p_tag(line),
+                )
             elif not in_fold and is_quoted(line):
                 # This line in the paragraph is quoted.
                 # Start foldable quoted lines in a paragraph.
@@ -804,23 +836,23 @@ class FormattersAPI:
 
             # We must test line starts and ends in separate blocks to
             # close the rare single line that is foldable.
-            if in_fold and line.endswith('</p>') and in_false_paragraph:
+            if in_fold and line.endswith("</p>") and in_false_paragraph:
                 # The line ends with a false paragraph in a PGP signature.
                 # Restore the line break to join with the next paragraph.
-                line = '%s<br />\n<br />' % strip_trailing_p_tag(line)
-            elif (in_quoted and self._re_quoted.match(line) is None):
+                line = "%s<br />\n<br />" % strip_trailing_p_tag(line)
+            elif in_quoted and self._re_quoted.match(line) is None:
                 # The line is not quoted like the previous line.
                 # End fold before we append this line.
                 in_fold = False
                 in_quoted = False
                 output.append("</span>\n")
-            elif in_fold and line.endswith('</p>'):
+            elif in_fold and line.endswith("</p>"):
                 # The line is quoted or an inclusion, and ends the paragraph.
                 # End the fold before the close paragraph mark.
                 in_fold = False
                 in_quoted = False
                 line = end_fold_markup % strip_trailing_p_tag(line)
-            elif in_false_paragraph and line.startswith('<p>'):
+            elif in_false_paragraph and line.startswith("<p>"):
                 # This line continues a PGP signature, but starts a paragraph.
                 # Remove the paragraph to join with the previous paragraph.
                 in_false_paragraph = False
@@ -830,7 +862,7 @@ class FormattersAPI:
                 # This line is not a transition.
                 pass
 
-            if in_fold and 'PGP SIGNATURE' in line:
+            if in_fold and "PGP SIGNATURE" in line:
                 # PGP signature blocks are split into two paragraphs
                 # by the text_to_html. The foldable feature works with
                 # a single paragraph, so we merge this paragraph with
@@ -838,7 +870,7 @@ class FormattersAPI:
                 in_false_paragraph = True
 
             output.append(line)
-        return '\n'.join(output)
+        return "\n".join(output)
 
     def obfuscate_email(self):
         """Obfuscate an email address if there's no authenticated user.
@@ -903,11 +935,14 @@ class FormattersAPI:
                 # Circular dependancies now. Should be resolved by moving the
                 # object image display api.
                 from lp.app.browser.tales import ObjectImageDisplayAPI
+
                 css_sprite = ObjectImageDisplayAPI(person).sprite_css()
                 seen_addresses.append(address)
                 text = text.replace(
-                    address, '<a href="%s" class="%s">%s</a>' % (
-                        canonical_url(person), css_sprite, address))
+                    address,
+                    '<a href="%s" class="%s">%s</a>'
+                    % (canonical_url(person), css_sprite, address),
+                )
 
         return text
 
@@ -918,7 +953,7 @@ class FormattersAPI:
     def shorten(self, maxlength):
         """Use like tal:content="context/foo/fmt:shorten/60"."""
         if len(self._stringtoformat) > maxlength:
-            return '%s...' % self._stringtoformat[:maxlength - 3]
+            return "%s..." % self._stringtoformat[: maxlength - 3]
         else:
             return self._stringtoformat
 
@@ -927,15 +962,17 @@ class FormattersAPI:
         if len(self._stringtoformat) > maxlength:
             length = (maxlength - 3) // 2
             return (
-                self._stringtoformat[:maxlength - length - 3] + '...' +
-                self._stringtoformat[-length:])
+                self._stringtoformat[: maxlength - length - 3]
+                + "..."
+                + self._stringtoformat[-length:]
+            )
         else:
             return self._stringtoformat
 
     def format_diff(self):
         """Format the string as a diff in a table with line numbers."""
         # Trim off trailing carriage returns.
-        text = self._stringtoformat.rstrip('\n')
+        text = self._stringtoformat.rstrip("\n")
         if len(text) == 0:
             return text
         result = ['<table class="diff unidiff">']
@@ -945,11 +982,13 @@ class FormattersAPI:
             result.append('<td class="line-no unselectable">%s</td>' % row)
             result.append(
                 structured(
-                    '<td class="%s">%s</td>', css_class, line).escapedtext)
-            result.append('</tr>')
+                    '<td class="%s">%s</td>', css_class, line
+                ).escapedtext
+            )
+            result.append("</tr>")
 
-        result.append('</table>')
-        return ''.join(result)
+        result.append("</table>")
+        return "".join(result)
 
     def _ssdiff_emit_line(self, result, row, cells):
         result.append('<tr id="diff-line-%s">' % row)
@@ -958,40 +997,42 @@ class FormattersAPI:
         # per-file line numbers.
         result.append(
             '<td class="line-no unselectable" '
-            'style="display: none">%s</td>' % row)
+            'style="display: none">%s</td>' % row
+        )
         result.extend(cells)
-        result.append('</tr>')
+        result.append("</tr>")
 
     def _ssdiff_emit_queued_lines(self, result, queue_removed, queue_added):
         for removed, added in zip_longest(queue_removed, queue_added):
             if removed:
                 removed_diff_row, removed_row, removed_line = removed
             else:
-                removed_diff_row, removed_row, removed_line = 0, '', ''
+                removed_diff_row, removed_row, removed_line = 0, "", ""
             if added:
                 added_diff_row, added_row, added_line = added
             else:
-                added_diff_row, added_row, added_line = 0, '', ''
+                added_diff_row, added_row, added_line = 0, "", ""
             cells = (
                 '<td class="ss-line-no unselectable">%s</td>' % removed_row,
                 structured(
-                    '<td class="diff-removed text">%s</td>',
-                    removed_line).escapedtext,
+                    '<td class="diff-removed text">%s</td>', removed_line
+                ).escapedtext,
                 '<td class="ss-line-no unselectable">%s</td>' % added_row,
                 structured(
-                    '<td class="diff-added text">%s</td>',
-                    added_line).escapedtext,
-                )
+                    '<td class="diff-added text">%s</td>', added_line
+                ).escapedtext,
+            )
             # Pick a reasonable row of the unified diff to attribute inline
             # comments to.  Whichever of the removed and added rows is later
             # will do.
             self._ssdiff_emit_line(
-                result, max(removed_diff_row, added_diff_row), cells)
+                result, max(removed_diff_row, added_diff_row), cells
+            )
 
     def format_ssdiff(self):
         """Format the string as a side-by-side diff."""
         # Trim off trailing carriage returns.
-        text = self._stringtoformat.rstrip('\n')
+        text = self._stringtoformat.rstrip("\n")
         if not text:
             return text
         result = ['<table class="diff ssdiff">']
@@ -1012,7 +1053,8 @@ class FormattersAPI:
                 # this line.
                 if queue_orig or queue_mod:
                     self._ssdiff_emit_queued_lines(
-                        result, queue_orig, queue_mod)
+                        result, queue_orig, queue_mod
+                    )
                     queue_orig = []
                     queue_mod = []
                 if orig_row is None and mod_row is None:
@@ -1020,31 +1062,35 @@ class FormattersAPI:
                     cells = [
                         structured(
                             '<td class="%s" colspan="4">%s</td>',
-                            css_class, line).escapedtext,
-                        ]
+                            css_class,
+                            line,
+                        ).escapedtext,
+                    ]
                 else:
                     # Line common to both versions.
-                    if line.startswith(' '):
+                    if line.startswith(" "):
                         line = line[1:]
                     cells = [
-                        '<td class="ss-line-no unselectable">%s</td>' %
-                            orig_row,
+                        '<td class="ss-line-no unselectable">%s</td>'
+                        % orig_row,
                         structured(
-                            '<td class="text">%s</td>', line).escapedtext,
-                        '<td class="ss-line-no unselectable">%s</td>' %
-                            mod_row,
+                            '<td class="text">%s</td>', line
+                        ).escapedtext,
+                        '<td class="ss-line-no unselectable">%s</td>'
+                        % mod_row,
                         structured(
-                            '<td class="text">%s</td>', line).escapedtext,
-                        ]
+                            '<td class="text">%s</td>', line
+                        ).escapedtext,
+                    ]
                 self._ssdiff_emit_line(result, row, cells)
         if queue_orig or queue_mod:
             self._ssdiff_emit_queued_lines(result, queue_orig, queue_mod)
 
-        result.append('</table>')
-        return ''.join(result)
+        result.append("</table>")
+        return "".join(result)
 
-    _css_id_strip_pattern = re.compile(r'[^a-zA-Z0-9-_]+')
-    _zope_css_id_strip_pattern = re.compile(r'[^a-zA-Z0-9-_\.]+')
+    _css_id_strip_pattern = re.compile(r"[^a-zA-Z0-9-_]+")
+    _zope_css_id_strip_pattern = re.compile(r"[^a-zA-Z0-9-_\.]+")
 
     def css_id(self, prefix=None):
         """Return a CSS compliant id."""
@@ -1074,7 +1120,7 @@ class FormattersAPI:
             raw_text = prefix + self._stringtoformat
         else:
             raw_text = self._stringtoformat
-        id_ = strip_pattern.sub('-', raw_text)
+        id_ = strip_pattern.sub("-", raw_text)
 
         # If any characters are converted to a hyphen, we cannot be 100%
         # assured of a unique id unless we take further action. Note that we
@@ -1086,17 +1132,18 @@ class FormattersAPI:
             # we also have to strip off any padding characters ("=") because
             # Python's URL-safe base 64 encoding includes those and they
             # aren't allowed in IDs either.
-            unique_suffix = (
-                urlsafe_b64encode(raw_text.encode('ASCII')).decode('ASCII'))
+            unique_suffix = urlsafe_b64encode(raw_text.encode("ASCII")).decode(
+                "ASCII"
+            )
             # Ensure we put a '-' between the id and base 64 encoding.
-            if id_[-1] != '-':
-                id_ += '-'
-            id_ += unique_suffix.replace('=', '')
+            if id_[-1] != "-":
+                id_ += "-"
+            id_ += unique_suffix.replace("=", "")
 
-        if id_[0] in '-_0123456789':
+        if id_[0] in "-_0123456789":
             # 'j' is least common starting character in technical usage;
             # engineers love 'z', 'q', and 'y'.
-            return 'j' + id_
+            return "j" + id_
         else:
             return id_
 
@@ -1111,63 +1158,65 @@ class FormattersAPI:
         return '<a href="%s">%s</a>' % (url, self._stringtoformat)
 
     def markdown(self):
-        if getFeatureFlag('markdown.enabled'):
+        if getFeatureFlag("markdown.enabled"):
             return format_markdown(self._stringtoformat)
         else:
             return self.text_to_html()
 
     def traverse(self, name, furtherPath):
-        if name == 'nl_to_br':
+        if name == "nl_to_br":
             return self.nl_to_br()
-        elif name == 'escape':
+        elif name == "escape":
             return self.escape()
-        elif name == 'lower':
+        elif name == "lower":
             return self.lower()
-        elif name == 'break-long-words':
+        elif name == "break-long-words":
             return self.break_long_words()
-        elif name == 'markdown':
+        elif name == "markdown":
             return self.markdown()
-        elif name == 'text-to-html':
+        elif name == "text-to-html":
             return self.text_to_html()
-        elif name == 'text-to-html-with-target':
+        elif name == "text-to-html-with-target":
             return self.text_to_html_with_target()
-        elif name == 'nice_pre':
+        elif name == "nice_pre":
             return self.nice_pre()
-        elif name == 'email-to-html':
+        elif name == "email-to-html":
             return self.email_to_html()
-        elif name == 'obfuscate-email':
+        elif name == "obfuscate-email":
             return self.obfuscate_email()
-        elif name == 'strip-email':
+        elif name == "strip-email":
             return self.strip_email()
-        elif name == 'linkify-email':
+        elif name == "linkify-email":
             return self.linkify_email()
-        elif name == 'shorten':
+        elif name == "shorten":
             if len(furtherPath) == 0:
                 raise TraversalError(
-                    "you need to traverse a number after fmt:shorten")
+                    "you need to traverse a number after fmt:shorten"
+                )
             maxlength = int(furtherPath.pop())
             return self.shorten(maxlength)
-        elif name == 'ellipsize':
+        elif name == "ellipsize":
             if len(furtherPath) == 0:
                 raise TraversalError(
-                    "you need to traverse a number after fmt:ellipsize")
+                    "you need to traverse a number after fmt:ellipsize"
+                )
             maxlength = int(furtherPath.pop())
             return self.ellipsize(maxlength)
-        elif name == 'diff':
+        elif name == "diff":
             return self.format_diff()
-        elif name == 'ssdiff':
+        elif name == "ssdiff":
             return self.format_ssdiff()
-        elif name == 'css-id':
+        elif name == "css-id":
             if len(furtherPath) > 0:
                 return self.css_id(furtherPath.pop())
             else:
                 return self.css_id()
-        elif name == 'zope-css-id':
+        elif name == "zope-css-id":
             if len(furtherPath) > 0:
                 return self.zope_css_id(furtherPath.pop())
             else:
                 return self.zope_css_id()
-        elif name == 'oops-id':
+        elif name == "oops-id":
             return self.oops_id()
         else:
             raise TraversalError(name)
@@ -1177,9 +1226,10 @@ def format_markdown(text):
     """Return html form of marked-up text."""
     # This returns whole paragraphs (in p tags), similarly to text_to_html.
     md = markdown.Markdown(
-        safe_mode='escape',
+        safe_mode="escape",
         extensions=[
-            'tables',
-            'nl2br',
-            ])
+            "tables",
+            "nl2br",
+        ],
+    )
     return md.convert(text)  # How easy was that?
diff --git a/lib/lp/app/browser/tales.py b/lib/lp/app/browser/tales.py
index cfe0af0..fe62ec3 100644
--- a/lib/lp/app/browser/tales.py
+++ b/lib/lp/app/browser/tales.py
@@ -3,38 +3,23 @@
 
 """Implementation of the lp: htmlform: fmt: namespaces in TALES."""
 
-from bisect import bisect
-from datetime import (
-    datetime,
-    timedelta,
-    )
-from email.utils import (
-    formatdate,
-    mktime_tz,
-    )
 import math
 import os.path
 import sys
+from bisect import bisect
+from datetime import datetime, timedelta
+from email.utils import formatdate, mktime_tz
 from textwrap import dedent
 from urllib.parse import quote
 
+import pytz
 from lazr.enum import enumerated_type_registry
 from lazr.restful.utils import get_current_browser_request
 from lazr.uri import URI
-import pytz
 from zope.browserpage import ViewPageTemplateFile
-from zope.component import (
-    adapter,
-    getMultiAdapter,
-    getUtility,
-    queryAdapter,
-    )
+from zope.component import adapter, getMultiAdapter, getUtility, queryAdapter
 from zope.error.interfaces import IErrorReportingUtility
-from zope.interface import (
-    Attribute,
-    implementer,
-    Interface,
-    )
+from zope.interface import Attribute, Interface, implementer
 from zope.publisher.browser import BrowserView
 from zope.publisher.defaultview import getDefaultViewName
 from zope.schema import TextLine
@@ -44,7 +29,7 @@ from zope.traversing.interfaces import (
     IPathAdapter,
     ITraversable,
     TraversalError,
-    )
+)
 
 from lp import _
 from lp.app.browser.badge import IHasBadges
@@ -55,7 +40,7 @@ from lp.app.interfaces.launchpad import (
     IHasLogo,
     IHasMugshot,
     IPrivacy,
-    )
+)
 from lp.blueprints.interfaces.specification import ISpecification
 from lp.blueprints.interfaces.sprint import ISprint
 from lp.bugs.interfaces.bug import IBug
@@ -66,7 +51,7 @@ from lp.layers import LaunchpadLayer
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
-    )
+)
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.projectgroup import IProjectGroup
@@ -74,10 +59,7 @@ from lp.services.utils import round_half_up
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.canonicalurl import nearest_adapter
 from lp.services.webapp.error import SystemErrorView
-from lp.services.webapp.escaping import (
-    html_escape,
-    structured,
-    )
+from lp.services.webapp.escaping import html_escape, structured
 from lp.services.webapp.interfaces import (
     IApplicationMenu,
     IContextMenu,
@@ -85,36 +67,25 @@ from lp.services.webapp.interfaces import (
     ILaunchBag,
     INavigationMenu,
     NoCanonicalUrl,
-    )
-from lp.services.webapp.menu import (
-    get_current_view,
-    get_facet,
-    )
-from lp.services.webapp.publisher import (
-    canonical_url,
-    LaunchpadView,
-    nearest,
-    )
+)
+from lp.services.webapp.menu import get_current_view, get_facet
+from lp.services.webapp.publisher import LaunchpadView, canonical_url, nearest
 from lp.services.webapp.session import get_cookie_domain
 from lp.services.webapp.url import urlappend
 from lp.snappy.interfaces.snap import SnapBuildRequestStatus
 from lp.soyuz.enums import ArchivePurpose
-from lp.soyuz.interfaces.archive import (
-    IArchive,
-    IPPA,
-    )
+from lp.soyuz.interfaces.archive import IPPA, IArchive
 from lp.soyuz.interfaces.binarypackagename import IBinaryAndSourcePackageName
 
-
-SEPARATOR = ' : '
+SEPARATOR = " : "
 
 
-def format_link(obj, view_name=None, empty_value='None'):
+def format_link(obj, view_name=None, empty_value="None"):
     """Return the equivalent of obj/fmt:link as a string."""
     if obj is None:
         return empty_value
-    fmt_adapter = queryAdapter(obj, IPathAdapter, 'fmt')
-    link = getattr(fmt_adapter, 'link', None)
+    fmt_adapter = queryAdapter(obj, IPathAdapter, "fmt")
+    link = getattr(fmt_adapter, "link", None)
     if link is None:
         raise NotImplementedError("Missing link function on adapter.")
     return link(view_name)
@@ -136,7 +107,7 @@ class MenuLinksDict(dict):
 
         # The object has the facet, but does not have a menu, this
         # is probably the overview menu with is the default facet.
-        if menu is None or getattr(menu, 'disabled', False):
+        if menu is None or getattr(menu, "disabled", False):
             return
         menu.request = request
 
@@ -180,6 +151,7 @@ class MenuLinksDict(dict):
 
     def iterkeys(self):
         return iter(self._all_link_names)
+
     __iter__ = iterkeys
 
 
@@ -203,7 +175,8 @@ class MenuAPI:
             self._context = self.view.context
             self._request = self.view.request
             self._selectedfacetname = getattr(
-                self.view, '__launchpad_facetname__', None)
+                self.view, "__launchpad_facetname__", None
+            )
         else:
             self._context = context
             self._request = get_current_browser_request()
@@ -226,11 +199,12 @@ class MenuAPI:
         # be placed first, since all the properties it uses would need to
         # be retrieved with object.__getattribute().
         missing = object()
-        if (getattr(MenuAPI, facet, missing) is not missing
-            or facet in object.__getattribute__(self, '__dict__')):
+        if getattr(
+            MenuAPI, facet, missing
+        ) is not missing or facet in object.__getattribute__(self, "__dict__"):
             return object.__getattribute__(self, facet)
 
-        has_facet = object.__getattribute__(self, '_has_facet')
+        has_facet = object.__getattribute__(self, "_has_facet")
         if not has_facet(facet):
             raise AttributeError(facet)
         menu = queryAdapter(self._context, IApplicationMenu, facet)
@@ -259,9 +233,9 @@ class MenuAPI:
         # If the default view name is being used, we will want the url
         # without the default view name.
         defaultviewname = getDefaultViewName(self._context, request)
-        if request_urlobj.path.rstrip('/').endswith(defaultviewname):
+        if request_urlobj.path.rstrip("/").endswith(defaultviewname):
             request_urlobj = URI(request.getURL(1))
-        query = request.get('QUERY_STRING')
+        query = request.get("QUERY_STRING")
         if query:
             request_urlobj = request_urlobj.replace(query=query)
         return request_urlobj
@@ -272,9 +246,12 @@ class MenuAPI:
         if menu is None:
             return []
         menu.request = self._request
-        return list(menu.iterlinks(
-            request_url=self._request_url(),
-            selectedfacetname=self._selectedfacetname))
+        return list(
+            menu.iterlinks(
+                request_url=self._request_url(),
+                selectedfacetname=self._selectedfacetname,
+            )
+        )
 
     def _facet_menu(self):
         """Return the IFacetMenu related to the context."""
@@ -283,6 +260,7 @@ class MenuAPI:
         # ensuring that the facet tabs match what's above them. This
         # whole class needs refactoring once the UI is stable.
         from lp.app.browser.launchpad import Hierarchy
+
         try:
             context = self._context
             crumbs = Hierarchy(context, self._request).heading_breadcrumbs
@@ -296,7 +274,7 @@ class MenuAPI:
 
     def selectedfacetname(self):
         if self._selectedfacetname is None:
-            return 'unknown'
+            return "unknown"
         else:
             return self._selectedfacetname
 
@@ -322,7 +300,8 @@ class MenuAPI:
                 selectedfacetname = get_facet(view)
             try:
                 menu = nearest_adapter(
-                    context, INavigationMenu, name=selectedfacetname)
+                    context, INavigationMenu, name=selectedfacetname
+                )
             except NoCanonicalUrl:
                 menu = None
             return self._getMenuLinksAndAttributes(menu)
@@ -331,7 +310,8 @@ class MenuAPI:
             # AssertionError. Otherwise, zope will hide the root cause
             # of the error and just say that "navigation" can't be traversed.
             new_exception = AssertionError(
-                'AttributError in MenuAPI.navigation: %s' % e)
+                "AttributError in MenuAPI.navigation: %s" % e
+            )
             # We cannot use parens around the arguments to `raise`,
             # since that will cause it to ignore the third argument,
             # which is the original traceback.
@@ -379,8 +359,9 @@ class EnumValueAPI:
                 enum.getTermByToken(name)
             except LookupError:
                 raise TraversalError(
-                    'The enumerated type %s does not have a value %s.' %
-                    (enum.name, name))
+                    "The enumerated type %s does not have a value %s."
+                    % (enum.name, name)
+                )
             return False
 
 
@@ -425,7 +406,6 @@ def htmlmatch(formvalue, value):
 
 @implementer(ITraversable)
 class HTMLFormOperation:
-
     def __init__(self, formvalue, operation):
         self.formvalue = formvalue
         self.operation = operation
@@ -457,13 +437,13 @@ class RequestAPI:
 
     @property
     def cookie_scope(self):
-        params = '; Path=/'
+        params = "; Path=/"
         uri = URI(self.request.getURL())
-        if uri.scheme == 'https':
-            params += '; Secure'
+        if uri.scheme == "https":
+            params += "; Secure"
         domain = get_cookie_domain(uri.host)
         if domain is not None:
-            params += '; Domain=%s' % domain
+            params += "; Domain=%s" % domain
         return params
 
 
@@ -500,46 +480,47 @@ class NoneFormatter:
     """
 
     allowed_names = {
-        'approximatedate',
-        'approximatedatetitle',
-        'approximateduration',
-        'break-long-words',
-        'date',
-        'datetime',
-        'displaydate',
-        'displaydatetitle',
-        'isodate',
-        'email-to-html',
-        'exactduration',
-        'lower',
-        'nice_pre',
-        'nl_to_br',
-        'pagetitle',
-        'rfc822utcdatetime',
-        'text-to-html',
-        'time',
-        'url',
-        'link',
-        }
+        "approximatedate",
+        "approximatedatetitle",
+        "approximateduration",
+        "break-long-words",
+        "date",
+        "datetime",
+        "displaydate",
+        "displaydatetitle",
+        "isodate",
+        "email-to-html",
+        "exactduration",
+        "lower",
+        "nice_pre",
+        "nl_to_br",
+        "pagetitle",
+        "rfc822utcdatetime",
+        "text-to-html",
+        "time",
+        "url",
+        "link",
+    }
 
     def __init__(self, context):
         self.context = context
 
     def traverse(self, name, furtherPath):
-        if name == 'shorten':
+        if name == "shorten":
             if not len(furtherPath):
                 raise TraversalError(
-                    "you need to traverse a number after fmt:shorten")
+                    "you need to traverse a number after fmt:shorten"
+                )
             # Remove the maxlength from the path as it is a parameter
             # and not another traversal command.
             furtherPath.pop()
-            return ''
+            return ""
         # We need to check to see if the name has been augmented with optional
         # evaluation parameters, delimited by ":". These parameters are:
         #  param1 = rootsite (used with link and url)
         #  param2 = default value (in case of context being None)
         # We are interested in the default value (param2).
-        result = ''
+        result = ""
         for nm in self.allowed_names:
             if name.startswith(nm + ":"):
                 name_parts = name.split(":")
@@ -562,16 +543,16 @@ class ObjectFormatterAPI:
     # frozenset (http://code.activestate.com/recipes/414283/) here, though.
     # The names which can be traversed further (e.g context/fmt:url/+edit).
     traversable_names = {
-        'api_url': 'api_url',
-        'link': 'link',
-        'url': 'url',
-        }
+        "api_url": "api_url",
+        "link": "link",
+        "url": "url",
+    }
 
     # Names which are allowed but can't be traversed further.
     final_traversable_names = {
-        'pagetitle': 'pagetitle',
-        'global-css': 'global_css',
-        }
+        "pagetitle": "pagetitle",
+        "global-css": "global_css",
+    }
 
     def __init__(self, context):
         self._context = context
@@ -587,8 +568,11 @@ class ObjectFormatterAPI:
         """
         try:
             url = canonical_url(
-                self._context, path_only_if_possible=True,
-                rootsite=rootsite, view_name=view_name)
+                self._context,
+                path_only_if_possible=True,
+                rootsite=rootsite,
+                view_name=view_name,
+            )
         except Unauthorized:
             url = ""
         return url
@@ -617,18 +601,18 @@ class ObjectFormatterAPI:
             param2 = default (used when self.context is None). The context
                      is not None here so this parameter is ignored.
         """
-        if name.startswith('link:') or name.startswith('url:'):
-            name_parts = name.split(':')
+        if name.startswith("link:") or name.startswith("url:"):
+            name_parts = name.split(":")
             name = name_parts[0]
             rootsite = name_parts[1]
-            if rootsite != '':
+            if rootsite != "":
                 extra_path = None
                 if len(furtherPath) > 0:
-                    extra_path = '/'.join(reversed(furtherPath))
+                    extra_path = "/".join(reversed(furtherPath))
                 # Remove remaining entries in furtherPath so that traversal
                 # stops here.
                 del furtherPath[:]
-                if name == 'link':
+                if name == "link":
                     if rootsite is None:
                         return self.link(extra_path)
                     else:
@@ -638,11 +622,11 @@ class ObjectFormatterAPI:
                         self.url(extra_path)
                     else:
                         return self.url(extra_path, rootsite=rootsite)
-        if '::' in name:
-            name = name.split(':')[0]
+        if "::" in name:
+            name = name.split(":")[0]
         if name in self.traversable_names:
             if len(furtherPath) >= 1:
-                extra_path = '/'.join(reversed(furtherPath))
+                extra_path = "/".join(reversed(furtherPath))
                 del furtherPath[:]
             else:
                 extra_path = None
@@ -667,30 +651,31 @@ class ObjectFormatterAPI:
         """
         raise NotImplementedError(
             "No link implementation for %r, IPathAdapter implementation "
-            "for %r." % (self, self._context))
+            "for %r." % (self, self._context)
+        )
 
     def global_css(self):
         css_classes = set()
         view = self._context
 
         # XXX: Bug #1076074
-        private = getattr(view, 'private', False)
+        private = getattr(view, "private", False)
         if private:
-            css_classes.add('private')
+            css_classes.add("private")
         else:
-            css_classes.add('public')
-        beta = getattr(view, 'beta_features', [])
+            css_classes.add("public")
+        beta = getattr(view, "beta_features", [])
         if beta:
-            css_classes.add('beta')
-        return ' '.join(list(css_classes))
+            css_classes.add("beta")
+        return " ".join(list(css_classes))
 
     def _getSaneBreadcrumbDetail(self, breadcrumb):
         text = breadcrumb.detail
         if len(text) > 64:
-            truncated = '%s...' % text[0:64]
-            if truncated.count('\u201c') > truncated.count('\u201cd'):
+            truncated = "%s..." % text[0:64]
+            if truncated.count("\u201c") > truncated.count("\u201cd"):
                 # Close the open smartquote if it was dropped.
-                truncated += '\u201d'
+                truncated += "\u201d"
             return truncated
         return text
 
@@ -700,23 +685,25 @@ class ObjectFormatterAPI:
         By default, reverse breadcrumbs are always used if they are available.
         If not available, then the view's .page_title attribute is used.
         """
-        ROOT_TITLE = 'Launchpad'
+        ROOT_TITLE = "Launchpad"
         view = self._context
         request = get_current_browser_request()
-        hierarchy_view = getMultiAdapter((view, request), name='+hierarchy')
-        if (isinstance(view, SystemErrorView) or
-            hierarchy_view is None or
-            len(hierarchy_view.items) < 2):
+        hierarchy_view = getMultiAdapter((view, request), name="+hierarchy")
+        if (
+            isinstance(view, SystemErrorView)
+            or hierarchy_view is None
+            or len(hierarchy_view.items) < 2
+        ):
             # The breadcrumbs are either not available or are overridden.  If
             # the view has a .page_title attribute use that.
-            page_title = getattr(view, 'page_title', None)
+            page_title = getattr(view, "page_title", None)
             if page_title is not None:
                 return page_title
             # If there is no template for the view, just use the default
             # Launchpad title.
-            template = getattr(view, 'template', None)
+            template = getattr(view, "template", None)
             if template is None:
-                template = getattr(view, 'index', None)
+                template = getattr(view, "index", None)
                 if template is None:
                     return ROOT_TITLE
         # Use the reverse breadcrumbs.
@@ -724,7 +711,7 @@ class ObjectFormatterAPI:
         if len(breadcrumbs) == 0:
             # This implies there are no breadcrumbs, but this more often
             # is caused when an Unauthorized error is being raised.
-            return ''
+            return ""
         detail_breadcrumb = self._getSaneBreadcrumbDetail(breadcrumbs[0])
         title_breadcrumbs = [breadcrumb.text for breadcrumb in breadcrumbs[1:]]
         title_text = SEPARATOR.join([detail_breadcrumb] + title_breadcrumbs)
@@ -739,7 +726,7 @@ class ObjectImageDisplayAPI:
     def __init__(self, context):
         self._context = context
 
-    #def default_icon_resource(self, context):
+    # def default_icon_resource(self, context):
     def sprite_css(self):
         """Return the CSS class for the sprite"""
         # XXX: mars 2008-08-22 bug=260468
@@ -748,44 +735,44 @@ class ObjectImageDisplayAPI:
         context = self._context
         sprite_string = None
         if IProduct.providedBy(context):
-            sprite_string = 'product'
+            sprite_string = "product"
         elif IProjectGroup.providedBy(context):
-            sprite_string = 'project'
+            sprite_string = "project"
         elif IPerson.providedBy(context):
             if context.is_team:
-                sprite_string = 'team'
+                sprite_string = "team"
             else:
                 if context.is_valid_person:
-                    sprite_string = 'person'
+                    sprite_string = "person"
                 else:
-                    sprite_string = 'person-inactive'
+                    sprite_string = "person-inactive"
         elif IDistribution.providedBy(context):
-            sprite_string = 'distribution'
+            sprite_string = "distribution"
         elif IDistributionSourcePackage.providedBy(context):
-            sprite_string = 'package-source'
+            sprite_string = "package-source"
         elif ISprint.providedBy(context):
-            sprite_string = 'meeting'
+            sprite_string = "meeting"
         elif IBug.providedBy(context):
-            sprite_string = 'bug'
+            sprite_string = "bug"
         elif IPPA.providedBy(context):
             if context.enabled:
-                sprite_string = 'ppa-icon'
+                sprite_string = "ppa-icon"
             else:
-                sprite_string = 'ppa-icon-inactive'
+                sprite_string = "ppa-icon-inactive"
         elif IArchive.providedBy(context):
-            sprite_string = 'distribution'
+            sprite_string = "distribution"
         elif IBranch.providedBy(context):
-            sprite_string = 'branch'
+            sprite_string = "branch"
         elif ISpecification.providedBy(context):
-            sprite_string = 'blueprint'
+            sprite_string = "blueprint"
         elif IBinaryAndSourcePackageName.providedBy(context):
-            sprite_string = 'package-source'
+            sprite_string = "package-source"
 
         if sprite_string is None:
             return None
         else:
-            if hasattr(context, 'private') and context.private:
-                sprite_string = sprite_string + ' private'
+            if hasattr(context, "private") and context.private:
+                sprite_string = sprite_string + " private"
 
             return "sprite %s" % sprite_string
 
@@ -794,21 +781,21 @@ class ObjectImageDisplayAPI:
         # This should be refactored.  We shouldn't have to do type-checking
         # using interfaces.
         if IProjectGroup.providedBy(context):
-            return '/@@/project-logo'
+            return "/@@/project-logo"
         elif IPerson.providedBy(context):
             if context.is_team:
-                return '/@@/team-logo'
+                return "/@@/team-logo"
             else:
                 if context.is_valid_person:
-                    return '/@@/person-logo'
+                    return "/@@/person-logo"
                 else:
-                    return '/@@/person-inactive-logo'
+                    return "/@@/person-inactive-logo"
         elif IProduct.providedBy(context):
-            return '/@@/product-logo'
+            return "/@@/product-logo"
         elif IDistribution.providedBy(context):
-            return '/@@/distribution-logo'
+            return "/@@/distribution-logo"
         elif ISprint.providedBy(context):
-            return '/@@/meeting-logo'
+            return "/@@/meeting-logo"
         return None
 
     def default_mugshot_resource(self, context):
@@ -816,21 +803,21 @@ class ObjectImageDisplayAPI:
         # This should be refactored.  We shouldn't have to do type-checking
         # using interfaces.
         if IProjectGroup.providedBy(context):
-            return '/@@/project-mugshot'
+            return "/@@/project-mugshot"
         elif IPerson.providedBy(context):
             if context.is_team:
-                return '/@@/team-mugshot'
+                return "/@@/team-mugshot"
             else:
                 if context.is_valid_person:
-                    return '/@@/person-mugshot'
+                    return "/@@/person-mugshot"
                 else:
-                    return '/@@/person-inactive-mugshot'
+                    return "/@@/person-inactive-mugshot"
         elif IProduct.providedBy(context):
-            return '/@@/product-mugshot'
+            return "/@@/product-mugshot"
         elif IDistribution.providedBy(context):
-            return '/@@/distribution-mugshot'
+            return "/@@/distribution-mugshot"
         elif ISprint.providedBy(context):
-            return '/@@/meeting-mugshot'
+            return "/@@/meeting-mugshot"
         return None
 
     def custom_icon_url(self):
@@ -840,12 +827,12 @@ class ObjectImageDisplayAPI:
             icon_url = context.icon.getURL()
             return icon_url
         elif context is None:
-            return ''
+            return ""
         else:
             return None
 
     def icon(self):
-        #XXX: this should go away as soon as all image:icon where replaced
+        # XXX: this should go away as soon as all image:icon where replaced
         return None
 
     def logo(self):
@@ -860,7 +847,7 @@ class ObjectImageDisplayAPI:
         if context is None:
             # we use the Launchpad logo for anything which is in no way
             # related to a Pillar (for example, a buildfarm)
-            url = '/@@/launchpad-logo'
+            url = "/@@/launchpad-logo"
         elif context.logo is not None:
             url = context.logo.getURL()
         else:
@@ -879,7 +866,7 @@ class ObjectImageDisplayAPI:
             a mugshot.
         """
         context = self._context
-        assert IHasMugshot.providedBy(context), 'No Mugshot for this item'
+        assert IHasMugshot.providedBy(context), "No Mugshot for this item"
         if context.mugshot is not None:
             url = context.mugshot.getURL()
         else:
@@ -894,14 +881,15 @@ class ObjectImageDisplayAPI:
 
     def badges(self):
         raise NotImplementedError(
-            "Badge display not implemented for this item")
+            "Badge display not implemented for this item"
+        )
 
     def boolean(self):
         """Return an icon representing the context as a boolean value."""
         if bool(self._context):
-            icon = 'yes'
+            icon = "yes"
         else:
-            icon = 'no'
+            icon = "no"
         markup = '<span class="sprite %(icon)s action-icon">%(icon)s</span>'
         return markup % dict(icon=icon)
 
@@ -916,18 +904,16 @@ class BugTaskImageDisplayAPI(ObjectImageDisplayAPI):
     """
 
     allowed_names = {
-        'icon',
-        'logo',
-        'mugshot',
-        'badges',
-        'sprite_css',
-        }
+        "icon",
+        "logo",
+        "mugshot",
+        "badges",
+        "sprite_css",
+    }
 
-    icon_template = (
-        '<span alt="%s" title="%s" class="%s"></span>')
+    icon_template = '<span alt="%s" title="%s" class="%s"></span>'
 
-    linked_icon_template = (
-        '<a href="%s" alt="%s" title="%s" class="%s"></a>')
+    linked_icon_template = '<a href="%s" alt="%s" title="%s" class="%s"></a>'
 
     def traverse(self, name, furtherPath):
         """Special-case traversal for icons with an optional rootsite."""
@@ -978,28 +964,44 @@ class BugTaskImageDisplayAPI(ObjectImageDisplayAPI):
         badges = []
         information_type = self._context.bug.information_type
         if information_type in PRIVATE_INFORMATION_TYPES:
-            badges.append(self.icon_template % (
-                information_type.title, information_type.description,
-                "sprite private"))
+            badges.append(
+                self.icon_template
+                % (
+                    information_type.title,
+                    information_type.description,
+                    "sprite private",
+                )
+            )
 
         if self._hasBugBranch():
-            badges.append(self.icon_template % (
-                "branch", "Branch exists", "sprite branch"))
+            badges.append(
+                self.icon_template
+                % ("branch", "Branch exists", "sprite branch")
+            )
 
         if self._hasSpecification():
-            badges.append(self.icon_template % (
-                "blueprint", "Related to a blueprint", "sprite blueprint"))
+            badges.append(
+                self.icon_template
+                % ("blueprint", "Related to a blueprint", "sprite blueprint")
+            )
 
         if self._context.milestone:
             milestone_text = "milestone %s" % self._context.milestone.name
-            badges.append(self.linked_icon_template % (
-                canonical_url(self._context.milestone),
-                milestone_text, "Linked to %s" % milestone_text,
-                "sprite milestone"))
+            badges.append(
+                self.linked_icon_template
+                % (
+                    canonical_url(self._context.milestone),
+                    milestone_text,
+                    "Linked to %s" % milestone_text,
+                    "sprite milestone",
+                )
+            )
 
         if self._hasPatch():
-            badges.append(self.icon_template % (
-                "haspatch", "Has a patch", "sprite haspatch-icon"))
+            badges.append(
+                self.icon_template
+                % ("haspatch", "Has a patch", "sprite haspatch-icon")
+            )
 
         # Join with spaces to avoid the icons smashing into each other
         # when multiple ones are presented.
@@ -1042,8 +1044,7 @@ class SpecificationImageDisplayAPI(ObjectImageDisplayAPI):
     Used for image:icon.
     """
 
-    icon_template = (
-        '<span alt="%s" title="%s" class="%s" />')
+    icon_template = '<span alt="%s" title="%s" class="%s" />'
 
     def sprite_css(self):
         """Return the CSS class for the sprite"""
@@ -1054,7 +1055,7 @@ class SpecificationImageDisplayAPI(ObjectImageDisplayAPI):
             sprite_str = sprite_str + "-%s" % priority
 
         if self._context.private:
-            sprite_str = sprite_str + ' private'
+            sprite_str = sprite_str + " private"
 
         return sprite_str
 
@@ -1063,16 +1064,21 @@ class SpecificationImageDisplayAPI(ObjectImageDisplayAPI):
 
     def badges(self):
 
-        badges = ''
+        badges = ""
 
         if len(self._context.linked_branches) > 0:
             badges += self.icon_template % (
-                "branch", "Branch is available", "sprite branch")
+                "branch",
+                "Branch is available",
+                "sprite branch",
+            )
 
         if self._context.informational:
             badges += self.icon_template % (
-                "informational", "Blueprint is purely informational",
-                "sprite info")
+                "informational",
+                "Blueprint is purely informational",
+                "sprite info",
+            )
 
         return badges
 
@@ -1084,17 +1090,20 @@ class KarmaCategoryImageDisplayAPI(ObjectImageDisplayAPI):
     """
 
     icons_for_karma_categories = {
-        'bugs': '/@@/bug',
-        'code': '/@@/branch',
-        'translations': '/@@/translation',
-        'specs': '/@@/blueprint',
-        'soyuz': '/@@/package-source',
-        'answers': '/@@/question'}
+        "bugs": "/@@/bug",
+        "code": "/@@/branch",
+        "translations": "/@@/translation",
+        "specs": "/@@/blueprint",
+        "soyuz": "/@@/package-source",
+        "answers": "/@@/question",
+    }
 
     def icon(self):
         icon = self.icons_for_karma_categories[self._context.name]
-        return ('<img height="14" width="14" alt="" title="%s" src="%s" />'
-                % (self._context.title, icon))
+        return '<img height="14" width="14" alt="" title="%s" src="%s" />' % (
+            self._context.title,
+            icon,
+        )
 
 
 class MilestoneImageDisplayAPI(ObjectImageDisplayAPI):
@@ -1114,40 +1123,42 @@ class BuildImageDisplayAPI(ObjectImageDisplayAPI):
 
     Used for image:icon.
     """
+
     icon_template = (
         '<img width="%(width)s" height="14" alt="%(alt)s" '
-        'title="%(title)s" src="%(src)s" />')
+        'title="%(title)s" src="%(src)s" />'
+    )
 
     def icon(self):
         """Return the appropriate <img> tag for the build icon."""
         icon_map = {
-            BuildStatus.NEEDSBUILD: {'src': "/@@/build-needed"},
-            BuildStatus.FULLYBUILT: {'src': "/@@/build-success"},
+            BuildStatus.NEEDSBUILD: {"src": "/@@/build-needed"},
+            BuildStatus.FULLYBUILT: {"src": "/@@/build-success"},
             BuildStatus.FAILEDTOBUILD: {
-                'src': "/@@/build-failed",
-                'width': '16',
-                },
-            BuildStatus.MANUALDEPWAIT: {'src': "/@@/build-depwait"},
-            BuildStatus.CHROOTWAIT: {'src': "/@@/build-chrootwait"},
-            BuildStatus.SUPERSEDED: {'src': "/@@/build-superseded"},
-            BuildStatus.BUILDING: {'src': "/@@/processing"},
-            BuildStatus.FAILEDTOUPLOAD: {'src': "/@@/build-failedtoupload"},
-            BuildStatus.UPLOADING: {'src': "/@@/processing"},
-            BuildStatus.CANCELLING: {'src': "/@@/processing"},
-            BuildStatus.CANCELLED: {'src': "/@@/build-failed"},
-            }
-
-        alt = '[%s]' % self._context.status.name
+                "src": "/@@/build-failed",
+                "width": "16",
+            },
+            BuildStatus.MANUALDEPWAIT: {"src": "/@@/build-depwait"},
+            BuildStatus.CHROOTWAIT: {"src": "/@@/build-chrootwait"},
+            BuildStatus.SUPERSEDED: {"src": "/@@/build-superseded"},
+            BuildStatus.BUILDING: {"src": "/@@/processing"},
+            BuildStatus.FAILEDTOUPLOAD: {"src": "/@@/build-failedtoupload"},
+            BuildStatus.UPLOADING: {"src": "/@@/processing"},
+            BuildStatus.CANCELLING: {"src": "/@@/processing"},
+            BuildStatus.CANCELLED: {"src": "/@@/build-failed"},
+        }
+
+        alt = "[%s]" % self._context.status.name
         title = self._context.status.title
-        source = icon_map[self._context.status].get('src')
-        width = icon_map[self._context.status].get('width', '14')
+        source = icon_map[self._context.status].get("src")
+        width = icon_map[self._context.status].get("width", "14")
 
         return self.icon_template % {
-            'alt': alt,
-            'title': title,
-            'src': source,
-            'width': width,
-            }
+            "alt": alt,
+            "title": title,
+            "src": source,
+            "width": width,
+        }
 
 
 class ArchiveImageDisplayAPI(ObjectImageDisplayAPI):
@@ -1155,6 +1166,7 @@ class ArchiveImageDisplayAPI(ObjectImageDisplayAPI):
 
     Used for image:icon.
     """
+
     icon_template = """
         <img width="14" height="14" alt="%s" title="%s" src="%s" />
         """
@@ -1162,13 +1174,13 @@ class ArchiveImageDisplayAPI(ObjectImageDisplayAPI):
     def icon(self):
         """Return the appropriate <img> tag for an archive."""
         icon_map = {
-            ArchivePurpose.PRIMARY: '/@@/distribution',
-            ArchivePurpose.PARTNER: '/@@/distribution',
-            ArchivePurpose.PPA: '/@@/ppa-icon',
-            ArchivePurpose.COPY: '/@@/distribution',
-            }
+            ArchivePurpose.PRIMARY: "/@@/distribution",
+            ArchivePurpose.PARTNER: "/@@/distribution",
+            ArchivePurpose.PPA: "/@@/ppa-icon",
+            ArchivePurpose.COPY: "/@@/distribution",
+        }
 
-        alt = '[%s]' % self._context.purpose.title
+        alt = "[%s]" % self._context.purpose.title
         title = self._context.purpose.title
         source = icon_map[self._context.purpose]
 
@@ -1180,32 +1192,34 @@ class SnapBuildRequestImageDisplayAPI(ObjectImageDisplayAPI):
 
     Used for image:icon.
     """
+
     icon_template = (
         '<img width="%(width)s" height="14" alt="%(alt)s" '
-        'title="%(title)s" src="%(src)s" />')
+        'title="%(title)s" src="%(src)s" />'
+    )
 
     def icon(self):
         """Return the appropriate <img> tag for the build request icon."""
         icon_map = {
-            SnapBuildRequestStatus.PENDING: {'src': "/@@/processing"},
+            SnapBuildRequestStatus.PENDING: {"src": "/@@/processing"},
             SnapBuildRequestStatus.FAILED: {
-                'src': "/@@/build-failed",
-                'width': "16",
-                },
-            SnapBuildRequestStatus.COMPLETED: {'src': "/@@/build-success"},
-            }
+                "src": "/@@/build-failed",
+                "width": "16",
+            },
+            SnapBuildRequestStatus.COMPLETED: {"src": "/@@/build-success"},
+        }
 
-        alt = '[%s]' % self._context.status.name
+        alt = "[%s]" % self._context.status.name
         title = self._context.status.title
-        source = icon_map[self._context.status].get('src')
-        width = icon_map[self._context.status].get('width', '14')
+        source = icon_map[self._context.status].get("src")
+        width = icon_map[self._context.status].get("width", "14")
 
         return self.icon_template % {
-            'alt': alt,
-            'title': title,
-            'src': source,
-            'width': width,
-            }
+            "alt": alt,
+            "title": title,
+            "src": source,
+            "width": width,
+        }
 
 
 class RevisionStatusReportImageDisplayAPI(ObjectImageDisplayAPI):
@@ -1213,9 +1227,11 @@ class RevisionStatusReportImageDisplayAPI(ObjectImageDisplayAPI):
 
     Used for image:icon.
     """
+
     icon_template = (
         '<img width="%(width)s" height="14" alt="%(alt)s" '
-        'title="%(title)s" src="%(src)s" />')
+        'title="%(title)s" src="%(src)s" />'
+    )
 
     def icon(self):
         """Return the appropriate <img> tag for the result icon."""
@@ -1228,8 +1244,8 @@ class RevisionStatusReportImageDisplayAPI(ObjectImageDisplayAPI):
             RevisionStatusResult.CANCELLED: {
                 "src": "/@@/build-failed",
                 "width": "16",
-                },
-            }
+            },
+        }
 
         result = self._context.result
         if result is None:
@@ -1244,7 +1260,7 @@ class RevisionStatusReportImageDisplayAPI(ObjectImageDisplayAPI):
             "title": title,
             "src": source,
             "width": width,
-            }
+        }
 
 
 class BadgeDisplayAPI:
@@ -1260,33 +1276,36 @@ class BadgeDisplayAPI:
     def small(self):
         """Render the visible badge's icon images."""
         badges = self.context.getVisibleBadges()
-        return ''.join([badge.renderIconImage() for badge in badges])
+        return "".join([badge.renderIconImage() for badge in badges])
 
     def large(self):
         """Render the visible badge's heading images."""
         badges = self.context.getVisibleBadges()
-        return ''.join([badge.renderHeadingImage() for badge in badges])
+        return "".join([badge.renderHeadingImage() for badge in badges])
 
 
 class PersonFormatterAPI(ObjectFormatterAPI):
     """Adapter for `IPerson` objects to a formatted string."""
 
-    traversable_names = {'link': 'link', 'url': 'url', 'api_url': 'api_url',
-                         'icon': 'icon',
-                         'displayname': 'displayname',
-                         'unique_displayname': 'unique_displayname',
-                         'link-display-name-id': 'link_display_name_id',
-                         }
+    traversable_names = {
+        "link": "link",
+        "url": "url",
+        "api_url": "api_url",
+        "icon": "icon",
+        "displayname": "displayname",
+        "unique_displayname": "unique_displayname",
+        "link-display-name-id": "link_display_name_id",
+    }
 
-    final_traversable_names = {'local-time': 'local_time'}
+    final_traversable_names = {"local-time": "local_time"}
     final_traversable_names.update(ObjectFormatterAPI.final_traversable_names)
 
     def local_time(self):
         """Return the local time for this person."""
         time_zone = self._context.time_zone
-        return datetime.now(pytz.timezone(time_zone)).strftime('%T %Z')
+        return datetime.now(pytz.timezone(time_zone)).strftime("%T %Z")
 
-    def url(self, view_name=None, rootsite='mainsite'):
+    def url(self, view_name=None, rootsite="mainsite"):
         """See `ObjectFormatterAPI`.
 
         The default URL for a person is to the mainsite.
@@ -1300,15 +1319,18 @@ class PersonFormatterAPI(ObjectFormatterAPI):
         if custom_icon is None:
             css_class = ObjectImageDisplayAPI(person).sprite_css()
             return structured(
-                '<a href="%s" class="%s">%s</a>',
-                url, css_class, text).escapedtext
+                '<a href="%s" class="%s">%s</a>', url, css_class, text
+            ).escapedtext
         else:
             return structured(
                 '<a href="%s" class="bg-image" '
                 'style="background-image: url(%s)">%s</a>',
-                url, custom_icon, text).escapedtext
+                url,
+                custom_icon,
+                text,
+            ).escapedtext
 
-    def link(self, view_name, rootsite='mainsite'):
+    def link(self, view_name, rootsite="mainsite"):
         """See `ObjectFormatterAPI`.
 
         Return an HTML link to the person's page containing an icon
@@ -1329,8 +1351,7 @@ class PersonFormatterAPI(ObjectFormatterAPI):
 
     def icon(self, view_name):
         """Return the URL for the person's icon."""
-        custom_icon = ObjectImageDisplayAPI(
-            self._context).custom_icon_url()
+        custom_icon = ObjectImageDisplayAPI(self._context).custom_icon_url()
         if custom_icon is None:
             css_class = ObjectImageDisplayAPI(self._context).sprite_css()
             return '<span class="' + css_class + '"></span>'
@@ -1344,7 +1365,7 @@ class PersonFormatterAPI(ObjectFormatterAPI):
         indicate which user profile is linked.
         """
         text = self.unique_displayname(None)
-        return self._makeLink(view_name, 'mainsite', text)
+        return self._makeLink(view_name, "mainsite", text)
 
 
 class MixedVisibilityError(Exception):
@@ -1354,15 +1375,15 @@ class MixedVisibilityError(Exception):
 class TeamFormatterAPI(PersonFormatterAPI):
     """Adapter for `ITeam` objects to a formatted string."""
 
-    hidden = '<hidden>'
+    hidden = "<hidden>"
 
-    def url(self, view_name=None, rootsite='mainsite'):
+    def url(self, view_name=None, rootsite="mainsite"):
         """See `ObjectFormatterAPI`.
 
         The default URL for a team is to the mainsite. None is returned
         when the user does not have permission to review the team.
         """
-        if not check_permission('launchpad.LimitedView', self._context):
+        if not check_permission("launchpad.LimitedView", self._context):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return None
@@ -1370,29 +1391,30 @@ class TeamFormatterAPI(PersonFormatterAPI):
 
     def api_url(self, context):
         """See `ObjectFormatterAPI`."""
-        if not check_permission('launchpad.LimitedView', self._context):
+        if not check_permission("launchpad.LimitedView", self._context):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return None
         return super().api_url(context)
 
-    def link(self, view_name, rootsite='mainsite'):
+    def link(self, view_name, rootsite="mainsite"):
         """See `ObjectFormatterAPI`.
 
         The default URL for a team is to the mainsite. None is returned
         when the user does not have permission to review the team.
         """
         person = self._context
-        if not check_permission('launchpad.LimitedView', person):
+        if not check_permission("launchpad.LimitedView", person):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return structured(
-                '<span class="sprite team">%s</span>', self.hidden).escapedtext
+                '<span class="sprite team">%s</span>', self.hidden
+            ).escapedtext
         return super().link(view_name, rootsite)
 
     def icon(self, view_name):
         team = self._context
-        if not check_permission('launchpad.LimitedView', team):
+        if not check_permission("launchpad.LimitedView", team):
             css_class = ObjectImageDisplayAPI(team).sprite_css()
             return '<span class="' + css_class + '"></span>'
         else:
@@ -1401,7 +1423,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
     def displayname(self, view_name, rootsite=None):
         """See `PersonFormatterAPI`."""
         person = self._context
-        if not check_permission('launchpad.LimitedView', person):
+        if not check_permission("launchpad.LimitedView", person):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return self.hidden
@@ -1410,7 +1432,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
     def unique_displayname(self, view_name):
         """See `PersonFormatterAPI`."""
         person = self._context
-        if not check_permission('launchpad.LimitedView', person):
+        if not check_permission("launchpad.LimitedView", person):
             # This person has no permission to view the team details.
             self._report_visibility_leak()
             return self.hidden
@@ -1421,8 +1443,7 @@ class TeamFormatterAPI(PersonFormatterAPI):
         try:
             raise MixedVisibilityError()
         except MixedVisibilityError:
-            getUtility(IErrorReportingUtility).raising(
-                sys.exc_info(), request)
+            getUtility(IErrorReportingUtility).raising(sys.exc_info(), request)
 
 
 class CustomizableFormatter(ObjectFormatterAPI):
@@ -1447,7 +1468,7 @@ class CustomizableFormatter(ObjectFormatterAPI):
     If a different permission is required, override _link_permission.
     """
 
-    _link_permission = 'launchpad.View'
+    _link_permission = "launchpad.View"
 
     def _link_summary_values(self):
         """Return a dict of values to use for template substitution.
@@ -1464,8 +1485,9 @@ class CustomizableFormatter(ObjectFormatterAPI):
         contexts like lists of items.
         """
         values = {
-            k: v if v is not None else ''
-            for k, v in self._link_summary_values().items()}
+            k: v if v is not None else ""
+            for k, v in self._link_summary_values().items()
+        }
         return structured(self._link_summary_template, **values).escapedtext
 
     def _title_values(self):
@@ -1482,12 +1504,13 @@ class CustomizableFormatter(ObjectFormatterAPI):
         This title is for use in fmt:link, which is meant to be used in
         contexts like lists of items.
         """
-        title_template = getattr(self, '_title_template', None)
+        title_template = getattr(self, "_title_template", None)
         if title_template is None:
             return None
         values = {
-            k: v if v is not None else ''
-            for k, v in self._title_values().items()}
+            k: v if v is not None else ""
+            for k, v in self._title_values().items()
+        }
         return structured(title_template, **values).escapedtext
 
     def sprite_css(self):
@@ -1495,7 +1518,7 @@ class CustomizableFormatter(ObjectFormatterAPI):
 
         :return: The icon css or None if no icon is available.
         """
-        return queryAdapter(self._context, IPathAdapter, 'image').sprite_css()
+        return queryAdapter(self._context, IPathAdapter, "image").sprite_css()
 
     def link(self, view_name, rootsite=None):
         """Return html including a link, description and icon.
@@ -1507,21 +1530,21 @@ class CustomizableFormatter(ObjectFormatterAPI):
         """
         sprite = self.sprite_css()
         if sprite is None:
-            css = ''
+            css = ""
         else:
             css = ' class="' + sprite + '"'
 
         summary = self._make_link_summary()
         title = self._make_title()
         if title is None:
-            title = ''
+            title = ""
         else:
             title = ' title="%s"' % title
 
         if check_permission(self._link_permission, self._context):
             url = self.url(view_name, rootsite)
         else:
-            url = ''
+            url = ""
         if url:
             return '<a href="%s"%s%s>%s</a>' % (url, css, title, summary)
         else:
@@ -1532,19 +1555,19 @@ class PillarFormatterAPI(CustomizableFormatter):
     """Adapter for IProduct, IDistribution and IProjectGroup objects to a
     formatted string."""
 
-    _link_summary_template = '%(displayname)s'
-    _link_permission = 'zope.Public'
+    _link_summary_template = "%(displayname)s"
+    _link_permission = "zope.Public"
 
     traversable_names = {
-        'api_url': 'api_url',
-        'link': 'link',
-        'url': 'url',
-        'link_with_displayname': 'link_with_displayname'
-        }
+        "api_url": "api_url",
+        "link": "link",
+        "url": "url",
+        "link_with_displayname": "link_with_displayname",
+    }
 
     def _link_summary_values(self):
         displayname = self._context.displayname
-        return {'displayname': displayname}
+        return {"displayname": displayname}
 
     def url(self, view_name=None, rootsite=None):
         """See `ObjectFormatterAPI`.
@@ -1553,8 +1576,9 @@ class PillarFormatterAPI(CustomizableFormatter):
         """
         return super().url(view_name, rootsite)
 
-    def _getLinkHTML(self, view_name, rootsite,
-        template, custom_icon_template):
+    def _getLinkHTML(
+        self, view_name, rootsite, template, custom_icon_template
+    ):
         """Generates html, mapping a link context to given templates.
 
         The html is generated using given `template` or `custom_icon_template`
@@ -1571,19 +1595,19 @@ class PillarFormatterAPI(CustomizableFormatter):
         context = self._context
         # XXX wgrant: the structured() in this dict is evil; refactor.
         mapping = {
-            'url': self.url(view_name, rootsite),
-            'name': context.name,
-            'displayname': context.displayname,
-            'summary': structured(self._make_link_summary()),
-            }
+            "url": self.url(view_name, rootsite),
+            "name": context.name,
+            "displayname": context.displayname,
+            "summary": structured(self._make_link_summary()),
+        }
         custom_icon = ObjectImageDisplayAPI(context).custom_icon_url()
         if custom_icon is None:
-            mapping['css_class'] = ObjectImageDisplayAPI(context).sprite_css()
+            mapping["css_class"] = ObjectImageDisplayAPI(context).sprite_css()
             return structured(template, **mapping).escapedtext
-        mapping['custom_icon'] = custom_icon
+        mapping["custom_icon"] = custom_icon
         return structured(custom_icon_template, **mapping).escapedtext
 
-    def link(self, view_name, rootsite='mainsite'):
+    def link(self, view_name, rootsite="mainsite"):
         """The html to show a link to a Product, ProjectGroup or distribution.
 
         In the case of Products or ProjectGroups we display the custom
@@ -1594,11 +1618,12 @@ class PillarFormatterAPI(CustomizableFormatter):
         custom_icon_template = (
             '<a href="%(url)s" class="bg-image" '
             'style="background-image: url(%(custom_icon)s)">%(summary)s</a>'
-            )
+        )
         return self._getLinkHTML(
-            view_name, rootsite, template, custom_icon_template)
+            view_name, rootsite, template, custom_icon_template
+        )
 
-    def link_with_displayname(self, view_name, rootsite='mainsite'):
+    def link_with_displayname(self, view_name, rootsite="mainsite"):
         """The html to show a link to a Product, ProjectGroup or
         distribution, including displayname and name.
 
@@ -1609,43 +1634,45 @@ class PillarFormatterAPI(CustomizableFormatter):
         template = (
             '<a href="%(url)s" class="%(css_class)s">%(displayname)s</a>'
             '&nbsp;(<a href="%(url)s">%(name)s</a>)'
-            )
+        )
         custom_icon_template = (
             '<a href="%(url)s" class="bg-image" '
             'style="background-image: url(%(custom_icon)s)">'
             '%(displayname)s</a>&nbsp;(<a href="%(url)s">%(name)s</a>)'
-            )
+        )
         return self._getLinkHTML(
-            view_name, rootsite, template, custom_icon_template)
+            view_name, rootsite, template, custom_icon_template
+        )
 
 
 class DistroSeriesFormatterAPI(CustomizableFormatter):
     """Adapter for IDistroSeries objects to a formatted string."""
 
-    _link_summary_template = '%(displayname)s'
-    _link_permission = 'zope.Public'
+    _link_summary_template = "%(displayname)s"
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         displayname = self._context.displayname
-        return {'displayname': displayname}
+        return {"displayname": displayname}
 
 
 class SourcePackageReleaseFormatterAPI(CustomizableFormatter):
 
     """Adapter for ISourcePackageRelease objects to a formatted string."""
 
-    _link_summary_template = '%(sourcepackage)s %(version)s'
+    _link_summary_template = "%(sourcepackage)s %(version)s"
 
     def _link_summary_values(self):
-        return {'sourcepackage':
-                self._context.distrosourcepackage.displayname,
-                'version': self._context.version}
+        return {
+            "sourcepackage": self._context.distrosourcepackage.displayname,
+            "version": self._context.version,
+        }
 
 
 class ProductReleaseFileFormatterAPI(ObjectFormatterAPI):
     """Adapter for `IProductReleaseFile` objects to a formatted string."""
 
-    traversable_names = {'link': 'link', 'url': 'url'}
+    traversable_names = {"link": "link", "url": "url"}
 
     def link(self, view_name):
         """A hyperlinked ProductReleaseFile.
@@ -1656,29 +1683,35 @@ class ProductReleaseFileFormatterAPI(ObjectFormatterAPI):
         """
         file_ = self._context
         file_size = NumberFormatterAPI(
-            file_.libraryfile.content.filesize).bytes()
+            file_.libraryfile.content.filesize
+        ).bytes()
         if file_.description is not None:
             description = file_.description
         else:
             description = file_.libraryfile.filename
         link_title = "%s (%s)" % (description, file_size)
         download_url = self._getDownloadURL(file_.libraryfile)
-        md5_url = urlappend(download_url, '+md5')
+        md5_url = urlappend(download_url, "+md5")
         replacements = dict(
-            url=download_url, filename=file_.libraryfile.filename,
-            md5_url=md5_url, link_title=link_title)
+            url=download_url,
+            filename=file_.libraryfile.filename,
+            md5_url=md5_url,
+            link_title=link_title,
+        )
         html = (
             '<img alt="download icon" src="/@@/download" />'
-            '<strong>'
+            "<strong>"
             '  <a title="%(link_title)s" href="%(url)s">%(filename)s</a> '
-            '</strong>'
-            '(<a href="%(md5_url)s">md5</a>')
+            "</strong>"
+            '(<a href="%(md5_url)s">md5</a>'
+        )
         if file_.signature is not None:
             html += ', <a href="%(signature_url)s">sig</a>)'
-            replacements['signature_url'] = self._getDownloadURL(
-                file_.signature)
+            replacements["signature_url"] = self._getDownloadURL(
+                file_.signature
+            )
         else:
-            html += ')'
+            html += ")"
         return structured(html, **replacements).escapedtext
 
     def url(self, view_name=None, rootsite=None):
@@ -1691,36 +1724,40 @@ class ProductReleaseFileFormatterAPI(ObjectFormatterAPI):
 
     def _getDownloadURL(self, lfa):
         """Return the download URL for the given `LibraryFileAlias`."""
-        url = urlappend(canonical_url(self._release), '+download')
+        url = urlappend(canonical_url(self._release), "+download")
         # Quote the filename to eliminate non-ascii characters which
         # are invalid in the url.
-        return urlappend(url, quote(lfa.filename.encode('utf-8')))
+        return urlappend(url, quote(lfa.filename.encode("utf-8")))
 
 
 class BranchFormatterAPI(ObjectFormatterAPI):
     """Adapter for IBranch objects to a formatted string."""
 
     traversable_names = {
-        'link': 'link', 'url': 'url',
-        'title-link': 'titleLink', 'bzr-link': 'bzrLink',
-        'api_url': 'api_url'}
+        "link": "link",
+        "url": "url",
+        "title-link": "titleLink",
+        "bzr-link": "bzrLink",
+        "api_url": "api_url",
+    }
 
     def _args(self, view_name):
         """Generate a dict of attributes for string template expansion."""
         branch = self._context
         return {
-            'bzr_identity': branch.bzr_identity,
-            'display_name': branch.displayname,
-            'name': branch.name,
-            'unique_name': branch.unique_name,
-            'url': self.url(view_name),
-            }
+            "bzr_identity": branch.bzr_identity,
+            "display_name": branch.displayname,
+            "name": branch.name,
+            "unique_name": branch.unique_name,
+            "url": self.url(view_name),
+        }
 
     def link(self, view_name):
         """A hyperlinked branch icon with the displayname."""
         return structured(
-            '<a href="%(url)s" class="sprite branch">'
-            '%(display_name)s</a>', **self._args(view_name)).escapedtext
+            '<a href="%(url)s" class="sprite branch">' "%(display_name)s</a>",
+            **self._args(view_name),
+        ).escapedtext
 
     def bzrLink(self, view_name):
         """A hyperlinked branch icon with the bazaar identity."""
@@ -1731,45 +1768,47 @@ class BranchFormatterAPI(ObjectFormatterAPI):
         """A hyperlinked branch name with following title."""
         return structured(
             '<a href="%(url)s" title="%(display_name)s">'
-            '%(name)s</a>: %(title)s', **self._args(view_name)).escapedtext
+            "%(name)s</a>: %(title)s",
+            **self._args(view_name),
+        ).escapedtext
 
 
 class BranchSubscriptionFormatterAPI(CustomizableFormatter):
     """Adapter for IBranchSubscription objects to a formatted string."""
 
-    _link_summary_template = _('Subscription of %(person)s to %(branch)s')
+    _link_summary_template = _("Subscription of %(person)s to %(branch)s")
 
     def _link_summary_values(self):
         """Provide values for template substitution"""
         return {
-            'person': self._context.person.displayname,
-            'branch': self._context.branch.displayname,
+            "person": self._context.person.displayname,
+            "branch": self._context.branch.displayname,
         }
 
 
 class BranchMergeProposalFormatterAPI(CustomizableFormatter):
 
-    _link_summary_template = _('%(title)s')
+    _link_summary_template = _("%(title)s")
 
     def _link_summary_values(self):
         return {
-            'title': self._context.title,
-            }
+            "title": self._context.title,
+        }
 
 
 class GitRepositoryFormatterAPI(CustomizableFormatter):
     """Adapter for IGitRepository objects to a formatted string."""
 
-    _link_summary_template = '%(display_name)s'
+    _link_summary_template = "%(display_name)s"
 
     def _link_summary_values(self):
-        return {'display_name': self._context.display_name}
+        return {"display_name": self._context.display_name}
 
 
 class GitRefFormatterAPI(CustomizableFormatter):
     """Adapter for IGitRef objects to a formatted string."""
 
-    _link_summary_template = '%(display_name)s'
+    _link_summary_template = "%(display_name)s"
 
     def url(self, view_name=None, rootsite=None):
         """See `ObjectFormatterAPI`.
@@ -1781,7 +1820,7 @@ class GitRefFormatterAPI(CustomizableFormatter):
         return super().url(view_name, rootsite)
 
     def _link_summary_values(self):
-        return {'display_name': self._context.display_name}
+        return {"display_name": self._context.display_name}
 
 
 class BugBranchFormatterAPI(CustomizableFormatter):
@@ -1805,21 +1844,23 @@ class BugBranchFormatterAPI(CustomizableFormatter):
 class BugFormatterAPI(CustomizableFormatter):
     """Adapter for IBug objects to a formatted string."""
 
-    _link_summary_template = 'Bug #%(id)s: %(title)s'
+    _link_summary_template = "Bug #%(id)s: %(title)s"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'id': str(self._context.id), 'title': self._context.title}
+        return {"id": str(self._context.id), "title": self._context.title}
 
 
 class BugTaskFormatterAPI(CustomizableFormatter):
     """Adapter for IBugTask objects to a formatted string."""
 
-    _title_template = '%(importance)s - %(status)s'
+    _title_template = "%(importance)s - %(status)s"
 
     def _title_values(self):
-        return {'importance': self._context.importance.title,
-                'status': self._context.status.title}
+        return {
+            "importance": self._context.importance.title,
+            "status": self._context.status.title,
+        }
 
     def _make_link_summary(self):
         return BugFormatterAPI(self._context.bug)._make_link_summary()
@@ -1828,14 +1869,15 @@ class BugTaskFormatterAPI(CustomizableFormatter):
 class CodeImportFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for CodeImport objects"""
 
-    _link_summary_template = _('Import of %(target)s: %(branch)s')
-    _link_permission = 'zope.Public'
+    _link_summary_template = _("Import of %(target)s: %(branch)s")
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'target': self._context.branch.target.displayname,
-                'branch': self._context.branch.bzr_identity,
-               }
+        return {
+            "target": self._context.branch.target.displayname,
+            "branch": self._context.branch.bzr_identity,
+        }
 
     def url(self, view_name=None, rootsite=None):
         """See `ObjectFormatterAPI`."""
@@ -1843,8 +1885,10 @@ class CodeImportFormatterAPI(CustomizableFormatter):
         # This is still here primarily for supporting branch deletion,
         # which does a fmt:link of the other entities that will be deleted.
         url = canonical_url(
-            self._context.branch, path_only_if_possible=True,
-            view_name=view_name)
+            self._context.branch,
+            path_only_if_possible=True,
+            view_name=view_name,
+        )
         return url
 
 
@@ -1859,182 +1903,201 @@ class PackageBuildFormatterAPI(ObjectFormatterAPI):
 
     def link(self, view_name, rootsite=None):
         build = self._context
-        if (not check_permission('launchpad.View', build) or
-            not check_permission('launchpad.View', build.archive.owner)):
-            return 'private job'
+        if not check_permission(
+            "launchpad.View", build
+        ) or not check_permission("launchpad.View", build.archive.owner):
+            return "private job"
 
         url = self.url(view_name=view_name, rootsite=rootsite)
         archive = self._composeArchiveReference(build.archive)
         return structured(
-            '<a href="%s">%s</a>%s', url, build.title, archive).escapedtext
+            '<a href="%s">%s</a>%s', url, build.title, archive
+        ).escapedtext
 
 
 class CodeImportMachineFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for CodeImport objects"""
 
-    _link_summary_template = _('%(hostname)s')
-    _link_permission = 'zope.Public'
+    _link_summary_template = _("%(hostname)s")
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'hostname': self._context.hostname}
+        return {"hostname": self._context.hostname}
 
 
 class MilestoneFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for Milestone objects."""
 
-    _link_summary_template = _('%(title)s')
-    _link_permission = 'zope.Public'
+    _link_summary_template = _("%(title)s")
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'title': self._context.title}
+        return {"title": self._context.title}
 
 
 class ProductReleaseFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for Milestone objects."""
 
-    _link_summary_template = _('%(displayname)s %(code_name)s')
-    _link_permission = 'zope.Public'
+    _link_summary_template = _("%(displayname)s %(code_name)s")
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
         code_name = self._context.milestone.code_name
-        if code_name is None or code_name.strip() == '':
-            code_name = ''
+        if code_name is None or code_name.strip() == "":
+            code_name = ""
         else:
-            code_name = '(%s)' % code_name.strip()
-        return dict(displayname=self._context.milestone.displayname,
-                    code_name=code_name)
+            code_name = "(%s)" % code_name.strip()
+        return dict(
+            displayname=self._context.milestone.displayname,
+            code_name=code_name,
+        )
 
 
 class ProductSeriesFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for ProductSeries objects"""
 
-    _link_summary_template = _('%(product)s %(series)s series')
+    _link_summary_template = _("%(product)s %(series)s series")
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'series': self._context.name,
-                'product': self._context.product.displayname}
+        return {
+            "series": self._context.name,
+            "product": self._context.product.displayname,
+        }
 
 
 class QuestionFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for question objects."""
 
-    _link_summary_template = _('%(id)s: %(title)s')
-    _link_permission = 'zope.Public'
+    _link_summary_template = _("%(id)s: %(title)s")
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'id': str(self._context.id), 'title': self._context.title}
+        return {"id": str(self._context.id), "title": self._context.title}
 
 
 class SourcePackageRecipeFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for ISourcePackageRecipe objects."""
 
-    _link_summary_template = 'Recipe %(name)s for %(owner)s'
+    _link_summary_template = "Recipe %(name)s for %(owner)s"
 
     def _link_summary_values(self):
-        return {'name': self._context.name,
-                'owner': self._context.owner.displayname}
+        return {
+            "name": self._context.name,
+            "owner": self._context.owner.displayname,
+        }
 
 
 class LiveFSFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for ILiveFS objects."""
 
     _link_summary_template = _(
-        'Live filesystem %(distroseries)s %(name)s for %(owner)s')
+        "Live filesystem %(distroseries)s %(name)s for %(owner)s"
+    )
 
     def _link_summary_values(self):
-        return {'distroseries': self._context.distro_series.name,
-                'name': self._context.name,
-                'owner': self._context.owner.displayname}
+        return {
+            "distroseries": self._context.distro_series.name,
+            "name": self._context.name,
+            "owner": self._context.owner.displayname,
+        }
 
 
 class OCIRecipeFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for IOCIRecipe objects."""
 
     _link_summary_template = _(
-        'OCI recipe %(pillar_name)s/%(oci_project_name)s/%(recipe_name)s for '
-        '%(owner)s')
+        "OCI recipe %(pillar_name)s/%(oci_project_name)s/%(recipe_name)s for "
+        "%(owner)s"
+    )
 
     def _link_summary_values(self):
-        return {'pillar_name': self._context.oci_project.pillar.name,
-                'oci_project_name': self._context.oci_project.name,
-                'recipe_name': self._context.name,
-                'owner': self._context.owner.displayname}
+        return {
+            "pillar_name": self._context.oci_project.pillar.name,
+            "oci_project_name": self._context.oci_project.name,
+            "recipe_name": self._context.name,
+            "owner": self._context.owner.displayname,
+        }
 
 
 class SnapFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for ISnap objects."""
 
-    _link_summary_template = _(
-        'Snap %(name)s for %(owner)s')
+    _link_summary_template = _("Snap %(name)s for %(owner)s")
 
     def _link_summary_values(self):
-        return {'name': self._context.name,
-                'owner': self._context.owner.displayname}
+        return {
+            "name": self._context.name,
+            "owner": self._context.owner.displayname,
+        }
 
 
 class SnappySeriesFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for ISnappySeries objects."""
 
-    _link_summary_template = _('%(title)s')
+    _link_summary_template = _("%(title)s")
 
     def _link_summary_values(self):
-        return {'title': self._context.title}
+        return {"title": self._context.title}
 
 
 class CharmRecipeFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for ICharmRecipe objects."""
 
     _link_summary_template = _(
-        'Charm recipe %(name)s for %(owner)s in %(project)s')
+        "Charm recipe %(name)s for %(owner)s in %(project)s"
+    )
 
     def _link_summary_values(self):
-        return {'name': self._context.name,
-                'owner': self._context.owner.displayname,
-                'project': self._context.project.displayname}
+        return {
+            "name": self._context.name,
+            "owner": self._context.owner.displayname,
+            "project": self._context.project.displayname,
+        }
 
 
 class SpecificationFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for Specification objects"""
 
-    _link_summary_template = _('%(title)s')
-    _link_permission = 'zope.Public'
+    _link_summary_template = _("%(title)s")
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'title': self._context.title}
+        return {"title": self._context.title}
 
 
 class CodeReviewCommentFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for CodeReviewComment objects"""
 
-    _link_summary_template = _('Comment by %(author)s')
-    _link_permission = 'zope.Public'
+    _link_summary_template = _("Comment by %(author)s")
+    _link_permission = "zope.Public"
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'author': self._context.message.owner.displayname}
+        return {"author": self._context.message.owner.displayname}
 
 
 class ArchiveFormatterAPI(CustomizableFormatter):
     """Adapter providing fmt support for `IArchive` objects."""
 
-    _link_summary_template = '%(display_name)s'
-    _link_permission = 'launchpad.View'
-    _reference_permission = 'launchpad.SubscriberView'
+    _link_summary_template = "%(display_name)s"
+    _link_permission = "launchpad.View"
+    _reference_permission = "launchpad.SubscriberView"
     _reference_template = "ppa:%(owner_name)s/%(ppa_name)s"
 
-    final_traversable_names = {'reference': 'reference'}
+    final_traversable_names = {"reference": "reference"}
     final_traversable_names.update(
-        CustomizableFormatter.final_traversable_names)
+        CustomizableFormatter.final_traversable_names
+    )
 
     def _link_summary_values(self):
         """See CustomizableFormatter._link_summary_values."""
-        return {'display_name': self._context.displayname}
+        return {"display_name": self._context.displayname}
 
     def link(self, view_name):
         """Return html including a link for the context archive.
@@ -2052,8 +2115,8 @@ class ArchiveFormatterAPI(CustomizableFormatter):
         if check_permission(self._link_permission, self._context):
             if self._context.is_main:
                 url = queryAdapter(
-                    self._context.distribution, IPathAdapter, 'fmt').url(
-                        view_name)
+                    self._context.distribution, IPathAdapter, "fmt"
+                ).url(view_name)
             else:
                 url = self.url(view_name)
             return '<a href="%s" class="%s">%s</a>' % (url, css, summary)
@@ -2061,19 +2124,21 @@ class ArchiveFormatterAPI(CustomizableFormatter):
             if not self._context.private:
                 return '<span class="%s">%s</span>' % (css, summary)
             else:
-                return ''
+                return ""
 
     def reference(self, view_name=None, rootsite=None):
         """Return the text PPA reference for a PPA."""
         if not IPPA.providedBy(self._context):
             raise NotImplementedError(
-                "No reference implementation for non-PPA archive %r." %
-                self._context)
+                "No reference implementation for non-PPA archive %r."
+                % self._context
+            )
         if not check_permission(self._reference_permission, self._context):
-            return ''
+            return ""
         return self._reference_template % {
-            'owner_name': self._context.owner.name,
-            'ppa_name': self._context.name}
+            "owner_name": self._context.owner.name,
+            "ppa_name": self._context.name,
+        }
 
 
 class SpecificationBranchFormatterAPI(CustomizableFormatter):
@@ -2091,16 +2156,18 @@ class SpecificationBranchFormatterAPI(CustomizableFormatter):
 
     def sprite_css(self):
         return queryAdapter(
-            self._context.specification, IPathAdapter, 'image').sprite_css()
+            self._context.specification, IPathAdapter, "image"
+        ).sprite_css()
 
 
 class BugTrackerFormatterAPI(ObjectFormatterAPI):
     """Adapter for `IBugTracker` objects to a formatted string."""
 
     final_traversable_names = {
-        'aliases': 'aliases',
-        'external-link': 'external_link',
-        'external-title-link': 'external_title_link'}
+        "aliases": "aliases",
+        "external-link": "external_link",
+        "external-title-link": "external_title_link",
+    }
     final_traversable_names.update(ObjectFormatterAPI.final_traversable_names)
 
     def link(self, view_name):
@@ -2123,12 +2190,12 @@ class BugTrackerFormatterAPI(ObjectFormatterAPI):
         text (i.e. no <a/> link).
         """
         url = self._context.baseurl
-        if url.startswith('mailto:') and getUtility(ILaunchBag).user is None:
-            return html_escape('mailto:<email address hidden>')
+        if url.startswith("mailto:";) and getUtility(ILaunchBag).user is None:
+            return html_escape("mailto:<email address hidden>")
         else:
             return structured(
-                '<a class="link-external" href="%(url)s">%(url)s</a>',
-                url=url).escapedtext
+                '<a class="link-external" href="%(url)s">%(url)s</a>', url=url
+            ).escapedtext
 
     def external_title_link(self):
         """Return an HTML link to the external bugtracker.
@@ -2142,11 +2209,11 @@ class BugTrackerFormatterAPI(ObjectFormatterAPI):
         title = self._context.title
         if getUtility(ILaunchBag).user is None:
             title = FormattersAPI(title).obfuscate_email()
-            if url.startswith('mailto:'):
+            if url.startswith("mailto:";):
                 return html_escape(title)
         return structured(
-            '<a class="link-external" href="%s">%s</a>',
-            url, title).escapedtext
+            '<a class="link-external" href="%s">%s</a>', url, title
+        ).escapedtext
 
     def aliases(self):
         """Generate alias URLs, obfuscating where necessary.
@@ -2156,8 +2223,8 @@ class BugTrackerFormatterAPI(ObjectFormatterAPI):
         """
         anonymous = getUtility(ILaunchBag).user is None
         for alias in self._context.aliases:
-            if anonymous and alias.startswith('mailto:'):
-                yield 'mailto:<email address hidden>'
+            if anonymous and alias.startswith("mailto:";):
+                yield "mailto:<email address hidden>"
             else:
                 yield alias
 
@@ -2166,8 +2233,9 @@ class BugWatchFormatterAPI(ObjectFormatterAPI):
     """Adapter for `IBugWatch` objects to a formatted string."""
 
     final_traversable_names = {
-        'external-link': 'external_link',
-        'external-link-short': 'external_link_short'}
+        "external-link": "external_link",
+        "external-link-short": "external_link_short",
+    }
     final_traversable_names.update(ObjectFormatterAPI.final_traversable_names)
 
     def _make_external_link(self, summary=None):
@@ -2180,14 +2248,14 @@ class BugWatchFormatterAPI(ObjectFormatterAPI):
         an email address, only the summary is returned (i.e. no link).
         """
         if summary is None or len(summary) == 0:
-            summary = structured('&mdash;')
+            summary = structured("&mdash;")
         url = self._context.url
-        if url.startswith('mailto:') and getUtility(ILaunchBag).user is None:
+        if url.startswith("mailto:";) and getUtility(ILaunchBag).user is None:
             return html_escape(summary)
         else:
             return structured(
-                '<a class="link-external" href="%s">%s</a>',
-                url, summary).escapedtext
+                '<a class="link-external" href="%s">%s</a>', url, summary
+            ).escapedtext
 
     def external_link(self):
         """Return an HTML link with a detailed link text.
@@ -2198,7 +2266,7 @@ class BugWatchFormatterAPI(ObjectFormatterAPI):
         summary = self._context.bugtracker.name
         remotebug = self._context.remotebug
         if remotebug is not None and len(remotebug) > 0:
-            summary = '%s #%s' % (summary, remotebug)
+            summary = "%s #%s" % (summary, remotebug)
         return self._make_external_link(summary)
 
     def external_link_short(self):
@@ -2218,16 +2286,17 @@ class NumberFormatterAPI:
         self._number = number
 
     def traverse(self, name, furtherPath):
-        if name == 'float':
+        if name == "float":
             if len(furtherPath) != 1:
                 raise TraversalError(
-                    "fmt:float requires a single decimal argument")
+                    "fmt:float requires a single decimal argument"
+                )
             # coerce the argument to float to ensure it's safe
             format = furtherPath.pop()
             return self.float(float(format))
-        elif name == 'bytes':
+        elif name == "bytes":
             return self.bytes()
-        elif name == 'intcomma':
+        elif name == "intcomma":
             return self.intcomma()
         else:
             raise TraversalError(name)
@@ -2242,9 +2311,9 @@ class NumberFormatterAPI:
         L = []
         for index, char in enumerate(reversed(str(self._number))):
             if index != 0 and (index % 3) == 0:
-                L.insert(0, ',')
+                L.insert(0, ",")
             L.insert(0, char)
-        return ''.join(L)
+        return "".join(L)
 
     def bytes(self):
         """Render number as byte contractions according to IEC60027-2."""
@@ -2264,7 +2333,7 @@ class NumberFormatterAPI:
         if exponent < 1:
             # If this is less than 1 KiB, no need for rounding.
             return "%s bytes" % n
-        return "%.1f %s" % (n / 1024.0 ** exponent, suffixes[exponent - 1])
+        return "%.1f %s" % (n / 1024.0**exponent, suffixes[exponent - 1])
 
     def float(self, format):
         """Use like tal:content="context/foo/fmt:float/.2".
@@ -2286,29 +2355,30 @@ class DateTimeFormatterAPI:
             self._datetime = datetimeobject
         else:
             self._datetime = datetime(
-                datetimeobject.year, datetimeobject.month, datetimeobject.day,
-                tzinfo=pytz.timezone('UTC'))
+                datetimeobject.year,
+                datetimeobject.month,
+                datetimeobject.day,
+                tzinfo=pytz.timezone("UTC"),
+            )
 
     def time(self):
         if self._datetime.tzinfo:
-            value = self._datetime.astimezone(
-                getUtility(ILaunchBag).time_zone)
-            return value.strftime('%T %Z')
+            value = self._datetime.astimezone(getUtility(ILaunchBag).time_zone)
+            return value.strftime("%T %Z")
         else:
-            return self._datetime.strftime('%T')
+            return self._datetime.strftime("%T")
 
     def date(self):
         value = self._datetime
         if value.tzinfo:
-            value = value.astimezone(
-                getUtility(ILaunchBag).time_zone)
-        return value.strftime('%Y-%m-%d')
+            value = value.astimezone(getUtility(ILaunchBag).time_zone)
+        return value.strftime("%Y-%m-%d")
 
     def _now(self):
         # This method exists to be overridden in tests.
         if self._datetime.tzinfo:
             # datetime is offset-aware
-            return datetime.now(pytz.timezone('UTC'))
+            return datetime.now(pytz.timezone("UTC"))
         else:
             # datetime is offset-naive
             return datetime.utcnow()
@@ -2317,7 +2387,7 @@ class DateTimeFormatterAPI:
         delta = abs(self._now() - self._datetime)
         if delta > timedelta(1, 0, 0):
             # far in the past or future, display the date
-            return 'on ' + self.date()
+            return "on " + self.date()
         return self.approximatedate()
 
     def approximatedate(self):
@@ -2331,39 +2401,39 @@ class DateTimeFormatterAPI:
         hours = delta.seconds // 3600
         minutes = (delta.seconds - (3600 * hours)) // 60
         seconds = delta.seconds % 60
-        result = ''
+        result = ""
         if future:
-            result += 'in '
+            result += "in "
         if days != 0:
             amount = days
-            unit = 'day'
+            unit = "day"
         elif hours != 0:
             amount = hours
-            unit = 'hour'
+            unit = "hour"
         elif minutes != 0:
             amount = minutes
-            unit = 'minute'
+            unit = "minute"
         else:
             if seconds <= 10:
-                result += 'a moment'
+                result += "a moment"
                 if not future:
-                    result += ' ago'
+                    result += " ago"
                 return result
             else:
                 amount = seconds
-                unit = 'second'
+                unit = "second"
         if amount != 1:
-            unit += 's'
-        result += '%s %s' % (amount, unit)
+            unit += "s"
+        result += "%s %s" % (amount, unit)
         if not future:
-            result += ' ago'
+            result += " ago"
         return result
 
     def datetime(self):
         return "%s %s" % (self.date(), self.time())
 
     def rfc822utcdatetime(self):
-        return formatdate(mktime_tz(self._datetime.utctimetuple() + (0, )))
+        return formatdate(mktime_tz(self._datetime.utctimetuple() + (0,)))
 
     def isodate(self):
         return self._datetime.isoformat()
@@ -2375,7 +2445,10 @@ class DateTimeFormatterAPI:
         # is exact rather than being an estimate.
         return structured(
             '<time title="%s" datetime="%s">%s</time>',
-            self.datetime(), self.isodate(), self.displaydate()).escapedtext
+            self.datetime(),
+            self.isodate(),
+            self.displaydate(),
+        ).escapedtext
 
     def approximatedatetitle(self):
         # Like `approximatedate`, but wrapped in an HTML element with `title`
@@ -2384,8 +2457,10 @@ class DateTimeFormatterAPI:
         # timestamp is exact rather than being an estimate.
         return structured(
             '<time title="%s" datetime="%s">%s</time>',
-            self.datetime(), self.isodate(),
-            self.approximatedate()).escapedtext
+            self.datetime(),
+            self.isodate(),
+            self.approximatedate(),
+        ).escapedtext
 
     @staticmethod
     def _yearDelta(old, new):
@@ -2404,23 +2479,23 @@ class DateTimeFormatterAPI:
         """How long since the datetime, as a string."""
         now = self._now()
         number = self._yearDelta(self._datetime, now)
-        unit = 'year'
+        unit = "year"
         if number < 1:
             delta = now - self._datetime
             if delta.days > 0:
                 number = delta.days
-                unit = 'day'
+                unit = "day"
             else:
                 number = delta.seconds // 60
                 if number == 0:
-                    return 'less than a minute'
-                unit = 'minute'
+                    return "less than a minute"
+                unit = "minute"
                 if number >= 60:
                     number /= 60
-                    unit = 'hour'
+                    unit = "hour"
         if number != 1:
-            unit += 's'
-        return '%d %s' % (number, unit)
+            unit += "s"
+        return "%d %s" % (number, unit)
 
 
 class SeriesSourcePackageBranchFormatter(ObjectFormatterAPI):
@@ -2432,12 +2507,13 @@ class SeriesSourcePackageBranchFormatter(ObjectFormatterAPI):
 
     def url(self, view_name=None, rootsite=None):
         return queryAdapter(
-            self._context.sourcepackage, IPathAdapter, 'fmt').url(
-                view_name, rootsite)
+            self._context.sourcepackage, IPathAdapter, "fmt"
+        ).url(view_name, rootsite)
 
     def link(self, view_name):
         return queryAdapter(
-            self._context.sourcepackage, IPathAdapter, 'fmt').link(view_name)
+            self._context.sourcepackage, IPathAdapter, "fmt"
+        ).link(view_name)
 
 
 @implementer(ITraversable)
@@ -2448,11 +2524,11 @@ class DurationFormatterAPI:
         self._duration = duration
 
     def traverse(self, name, furtherPath):
-        if name == 'exactduration':
+        if name == "exactduration":
             return self.exactduration()
-        elif name == 'approximateduration':
+        elif name == "approximateduration":
             return self.approximateduration()
-        elif name == 'millisecondduration':
+        elif name == "millisecondduration":
             return self.millisecondduration()
         else:
             raise TraversalError(name)
@@ -2462,26 +2538,26 @@ class DurationFormatterAPI:
         parts = []
         minutes, seconds = divmod(self._duration.seconds, 60)
         hours, minutes = divmod(minutes, 60)
-        seconds = seconds + (float(self._duration.microseconds) / 10 ** 6)
+        seconds = seconds + (float(self._duration.microseconds) / 10**6)
         if self._duration.days > 0:
             if self._duration.days == 1:
-                parts.append('%d day' % self._duration.days)
+                parts.append("%d day" % self._duration.days)
             else:
-                parts.append('%d days' % self._duration.days)
+                parts.append("%d days" % self._duration.days)
         if parts or hours > 0:
             if hours == 1:
-                parts.append('%d hour' % hours)
+                parts.append("%d hour" % hours)
             else:
-                parts.append('%d hours' % hours)
+                parts.append("%d hours" % hours)
         if parts or minutes > 0:
             if minutes == 1:
-                parts.append('%d minute' % minutes)
+                parts.append("%d minute" % minutes)
             else:
-                parts.append('%d minutes' % minutes)
+                parts.append("%d minutes" % minutes)
         if parts or seconds > 0:
-            parts.append('%0.1f seconds' % seconds)
+            parts.append("%0.1f seconds" % seconds)
 
-        return ', '.join(parts)
+        return ", ".join(parts)
 
     def approximateduration(self):
         """Return a nicely-formatted approximate duration.
@@ -2505,19 +2581,19 @@ class DurationFormatterAPI:
         # the display value corresponding to the lowest boundary that
         # 'seconds' is less than, if one exists.
         representation_in_seconds = [
-            (1.5, '1 second'),
-            (2.5, '2 seconds'),
-            (3.5, '3 seconds'),
-            (4.5, '4 seconds'),
-            (7.5, '5 seconds'),
-            (12.5, '10 seconds'),
-            (17.5, '15 seconds'),
-            (22.5, '20 seconds'),
-            (27.5, '25 seconds'),
-            (35, '30 seconds'),
-            (45, '40 seconds'),
-            (55, '50 seconds'),
-            (90, '1 minute'),
+            (1.5, "1 second"),
+            (2.5, "2 seconds"),
+            (3.5, "3 seconds"),
+            (4.5, "4 seconds"),
+            (7.5, "5 seconds"),
+            (12.5, "10 seconds"),
+            (17.5, "15 seconds"),
+            (22.5, "20 seconds"),
+            (27.5, "25 seconds"),
+            (35, "30 seconds"),
+            (45, "40 seconds"),
+            (55, "50 seconds"),
+            (90, "1 minute"),
         ]
 
         # Break representation_in_seconds into two pieces, to simplify
@@ -2553,7 +2629,7 @@ class DurationFormatterAPI:
         hours, remaining_seconds = divmod(seconds, 3600)
         ten_minute_chunks = round_half_up(remaining_seconds / 600.0)
         minutes = ten_minute_chunks * 10
-        hours += (minutes // 60)
+        hours += minutes // 60
         minutes %= 60
         if hours < 10:
             if minutes:
@@ -2568,7 +2644,7 @@ class DurationFormatterAPI:
 
         # Is the duration less than ten and a half hours?
         if seconds < (10.5 * 3600):
-            return '10 hours'
+            return "10 hours"
 
         # Try to calculate the approximate number of hours, to a
         # maximum of 47.
@@ -2578,7 +2654,7 @@ class DurationFormatterAPI:
 
         # Is the duration fewer than two and a half days?
         if seconds < (2.5 * 24 * 3600):
-            return '2 days'
+            return "2 days"
 
         # Try to approximate to day granularity, up to a maximum of 13
         # days.
@@ -2588,7 +2664,7 @@ class DurationFormatterAPI:
 
         # Is the duration fewer than two and a half weeks?
         if seconds < (2.5 * 7 * 24 * 3600):
-            return '2 weeks'
+            return "2 weeks"
 
         # If we've made it this far, we'll calculate the duration to a
         # granularity of weeks, once and for all.
@@ -2596,23 +2672,23 @@ class DurationFormatterAPI:
         return "%d weeks" % weeks
 
     def millisecondduration(self):
-        return '%sms' % (self._duration.total_seconds() * 1000,)
+        return "%sms" % (self._duration.total_seconds() * 1000,)
 
 
 class LinkFormatterAPI(ObjectFormatterAPI):
     """Adapter from Link objects to a formatted anchor."""
+
     final_traversable_names = {
-        'icon': 'icon',
-        'icon-link': 'link',
-        'link-icon': 'link',
-        }
+        "icon": "icon",
+        "icon-link": "link",
+        "link-icon": "link",
+    }
     final_traversable_names.update(ObjectFormatterAPI.final_traversable_names)
 
     def icon(self):
         """Return the icon representation of the link."""
         request = get_current_browser_request()
-        return getMultiAdapter(
-            (self._context, request), name="+inline-icon")()
+        return getMultiAdapter((self._context, request), name="+inline-icon")()
 
     def link(self, view_name=None, rootsite=None):
         """Return the default representation of the link."""
@@ -2623,20 +2699,21 @@ class LinkFormatterAPI(ObjectFormatterAPI):
         if self._context.enabled:
             return self._context.url
         else:
-            return ''
+            return ""
 
 
 class RevisionAuthorFormatterAPI(ObjectFormatterAPI):
     """Adapter for `IRevisionAuthor` links."""
 
-    traversable_names = {'link': 'link'}
+    traversable_names = {"link": "link"}
 
-    def link(self, view_name=None, rootsite='mainsite'):
+    def link(self, view_name=None, rootsite="mainsite"):
         """See `ObjectFormatterAPI`."""
         context = self._context
         if context.person is not None:
             return PersonFormatterAPI(self._context.person).link(
-                view_name, rootsite)
+                view_name, rootsite
+            )
         elif context.name_without_email:
             return html_escape(context.name_without_email)
         elif context.email and getUtility(ILaunchBag).user is not None:
@@ -2645,7 +2722,7 @@ class RevisionAuthorFormatterAPI(ObjectFormatterAPI):
             return html_escape("<email address hidden>")
         else:
             # The RevisionAuthor name and email is None.
-            return ''
+            return ""
 
 
 @implementer(ITraversable)
@@ -2662,13 +2739,14 @@ class PermissionRequiredQuery:
     def traverse(self, name, furtherPath):
         if len(furtherPath) > 0:
             raise TraversalError(
-                    "There should be no further path segments after "
-                    "required:permission")
+                "There should be no further path segments after "
+                "required:permission"
+            )
         return check_permission(name, self.context)
 
 
 class IMainTemplateFile(Interface):
-    path = TextLine(title='The absolute path to this main template.')
+    path = TextLine(title="The absolute path to this main template.")
 
 
 @adapter(LaunchpadLayer)
@@ -2676,27 +2754,27 @@ class IMainTemplateFile(Interface):
 class LaunchpadLayerToMainTemplateAdapter:
     def __init__(self, context):
         here = os.path.dirname(os.path.realpath(__file__))
-        self.path = os.path.join(here, '../templates/base-layout.pt')
+        self.path = os.path.join(here, "../templates/base-layout.pt")
 
 
 @implementer(ITraversable)
 class PageMacroDispatcher:
     """Selects a macro, while storing information about page layout.
 
-        view/macro:page
-        view/macro:page/main_side
-        view/macro:page/main_only
-        view/macro:page/searchless
+    view/macro:page
+    view/macro:page/main_side
+    view/macro:page/main_only
+    view/macro:page/searchless
 
-        view/macro:pagehas/applicationtabs
-        view/macro:pagehas/globalsearch
-        view/macro:pagehas/portlets
-        view/macro:pagehas/main
+    view/macro:pagehas/applicationtabs
+    view/macro:pagehas/globalsearch
+    view/macro:pagehas/portlets
+    view/macro:pagehas/main
 
-        view/macro:pagetype
+    view/macro:pagetype
 
-        view/macro:is-page-contentless
-        view/macro:has-watermark
+    view/macro:is-page-contentless
+    view/macro:has-watermark
     """
 
     def __init__(self, context):
@@ -2706,44 +2784,46 @@ class PageMacroDispatcher:
     @property
     def base(self):
         return ViewPageTemplateFile(
-            IMainTemplateFile(self.context.request).path)
+            IMainTemplateFile(self.context.request).path
+        )
 
     def traverse(self, name, furtherPath):
-        if name == 'page':
+        if name == "page":
             if len(furtherPath) == 1:
                 pagetype = furtherPath.pop()
             elif not furtherPath:
-                pagetype = 'default'
+                pagetype = "default"
             else:
                 raise TraversalError("Max one path segment after macro:page")
 
             return self.page(pagetype)
-        elif name == 'pagehas':
+        elif name == "pagehas":
             if len(furtherPath) != 1:
                 raise TraversalError(
-                    "Exactly one path segment after macro:haspage")
+                    "Exactly one path segment after macro:haspage"
+                )
 
             layoutelement = furtherPath.pop()
             return self.haspage(layoutelement)
-        elif name == 'pagetype':
+        elif name == "pagetype":
             return self.pagetype()
-        elif name == 'is-page-contentless':
+        elif name == "is-page-contentless":
             return self.isPageContentless()
-        elif name == 'has-watermark':
+        elif name == "has-watermark":
             return self.hasWatermark()
         else:
             raise TraversalError(name)
 
     def page(self, pagetype):
         if pagetype not in self._pagetypes:
-            raise TraversalError('unknown pagetype: %s' % pagetype)
+            raise TraversalError("unknown pagetype: %s" % pagetype)
         self.context.__pagetype__ = pagetype
-        return self.base.macros['master']
+        return self.base.macros["master"]
 
     def haspage(self, layoutelement):
-        pagetype = getattr(self.context, '__pagetype__', None)
+        pagetype = getattr(self.context, "__pagetype__", None)
         if pagetype is None:
-            pagetype = 'unset'
+            pagetype = "unset"
         return self._pagetypes[pagetype][layoutelement]
 
     def hasWatermark(self):
@@ -2752,7 +2832,7 @@ class PageMacroDispatcher:
         The default value is True, but the view can provide has_watermark
         to force the page not render the standard location information.
         """
-        return getattr(self.context, 'has_watermark', True)
+        return getattr(self.context, "has_watermark", True)
 
     def isPageContentless(self):
         """Should the template avoid rendering detailed information.
@@ -2766,58 +2846,53 @@ class PageMacroDispatcher:
         if privacy is None or not privacy.private:
             return False
         return not (
-            check_permission('launchpad.SubscriberView', view_context) or
-            check_permission('launchpad.View', view_context))
+            check_permission("launchpad.SubscriberView", view_context)
+            or check_permission("launchpad.View", view_context)
+        )
 
     def pagetype(self):
-        return getattr(self.context, '__pagetype__', 'unset')
+        return getattr(self.context, "__pagetype__", "unset")
 
     class LayoutElements:
-
-        def __init__(self,
+        def __init__(
+            self,
             applicationtabs=False,
             globalsearch=False,
             portlets=False,
             pagetypewasset=True,
-            ):
+        ):
             self.elements = vars()
 
         def __getitem__(self, name):
             return self.elements[name]
 
     _pagetypes = {
-       'main_side':
-            LayoutElements(
-                applicationtabs=True,
-                globalsearch=True,
-                portlets=True),
-       'main_only':
-            LayoutElements(
-                applicationtabs=True,
-                globalsearch=True,
-                portlets=False),
-       'searchless':
-            LayoutElements(
-                applicationtabs=True,
-                globalsearch=False,
-                portlets=False),
-        }
+        "main_side": LayoutElements(
+            applicationtabs=True, globalsearch=True, portlets=True
+        ),
+        "main_only": LayoutElements(
+            applicationtabs=True, globalsearch=True, portlets=False
+        ),
+        "searchless": LayoutElements(
+            applicationtabs=True, globalsearch=False, portlets=False
+        ),
+    }
 
 
 class TranslationGroupFormatterAPI(ObjectFormatterAPI):
     """Adapter for `ITranslationGroup` objects to a formatted string."""
 
     traversable_names = {
-        'link': 'link',
-        'url': 'url',
-        'displayname': 'displayname',
+        "link": "link",
+        "url": "url",
+        "displayname": "displayname",
     }
 
-    def url(self, view_name=None, rootsite='translations'):
+    def url(self, view_name=None, rootsite="translations"):
         """See `ObjectFormatterAPI`."""
         return super().url(view_name, rootsite)
 
-    def link(self, view_name, rootsite='translations'):
+    def link(self, view_name, rootsite="translations"):
         """See `ObjectFormatterAPI`."""
         group = self._context
         url = self.url(view_name, rootsite)
@@ -2830,22 +2905,25 @@ class TranslationGroupFormatterAPI(ObjectFormatterAPI):
 
 class LanguageFormatterAPI(ObjectFormatterAPI):
     """Adapter for `ILanguage` objects to a formatted string."""
+
     traversable_names = {
-        'link': 'link',
-        'url': 'url',
-        'displayname': 'displayname',
+        "link": "link",
+        "url": "url",
+        "displayname": "displayname",
     }
 
-    def url(self, view_name=None, rootsite='translations'):
+    def url(self, view_name=None, rootsite="translations"):
         """See `ObjectFormatterAPI`."""
         return super().url(view_name, rootsite)
 
-    def link(self, view_name, rootsite='translations'):
+    def link(self, view_name, rootsite="translations"):
         """See `ObjectFormatterAPI`."""
         url = self.url(view_name, rootsite)
         return structured(
             '<a href="%s" class="sprite language">%s</a>',
-            url, self._context.englishname).escapedtext
+            url,
+            self._context.englishname,
+        ).escapedtext
 
     def displayname(self, view_name, rootsite=None):
         """See `ObjectFormatterAPI`."""
@@ -2856,16 +2934,16 @@ class POFileFormatterAPI(ObjectFormatterAPI):
     """Adapter for `IPOFile` objects to a formatted string."""
 
     traversable_names = {
-        'link': 'link',
-        'url': 'url',
-        'displayname': 'displayname',
+        "link": "link",
+        "url": "url",
+        "displayname": "displayname",
     }
 
-    def url(self, view_name=None, rootsite='translations'):
+    def url(self, view_name=None, rootsite="translations"):
         """See `ObjectFormatterAPI`."""
         return super().url(view_name, rootsite)
 
-    def link(self, view_name, rootsite='translations'):
+    def link(self, view_name, rootsite="translations"):
         """See `ObjectFormatterAPI`."""
         pofile = self._context
         url = self.url(view_name, rootsite)
@@ -2880,20 +2958,22 @@ def download_link(url, description, file_size):
     """Return HTML for downloading an item."""
     file_size = NumberFormatterAPI(file_size).bytes()
     formatted = structured(
-        '<a href="%s">%s</a> (%s)', url, description, file_size)
+        '<a href="%s">%s</a> (%s)', url, description, file_size
+    )
     return formatted.escapedtext
 
 
 class PackageDiffFormatterAPI(ObjectFormatterAPI):
-
     def link(self, view_name, rootsite=None):
         diff = self._context
         if not diff.date_fulfilled:
-            return structured('%s (pending)', diff.title).escapedtext
+            return structured("%s (pending)", diff.title).escapedtext
         else:
             return download_link(
-                diff.diff_content.http_url, diff.title,
-                diff.diff_content.content.filesize)
+                diff.diff_content.http_url,
+                diff.title,
+                diff.diff_content.content.filesize,
+            )
 
 
 @implementer(ITraversable)
@@ -2911,7 +2991,7 @@ class CSSFormatter:
 
     def select(self, furtherPath):
         if len(furtherPath) < 2:
-            raise TraversalError('select needs two subsequent path elements.')
+            raise TraversalError("select needs two subsequent path elements.")
         true_value = furtherPath.pop()
         false_value = furtherPath.pop()
         if self.context:
@@ -2931,8 +3011,8 @@ class IRCNicknameFormatterAPI(ObjectFormatterAPI):
     """Adapter from IrcID objects to a formatted string."""
 
     traversable_names = {
-        'displayname': 'displayname',
-        'formatted_displayname': 'formatted_displayname',
+        "displayname": "displayname",
+        "formatted_displayname": "formatted_displayname",
     }
 
     def displayname(self, view_name=None):
@@ -2940,9 +3020,13 @@ class IRCNicknameFormatterAPI(ObjectFormatterAPI):
 
     def formatted_displayname(self, view_name=None):
         return structured(
-            dedent("""\
+            dedent(
+                """\
                 <strong>%s</strong>
                 <span class="lesser"> on </span>
                 <strong>%s</strong>
-            """),
-            self._context.nickname, self._context.network).escapedtext
+            """
+            ),
+            self._context.nickname,
+            self._context.network,
+        ).escapedtext
diff --git a/lib/lp/app/browser/tests/test_base_layout.py b/lib/lp/app/browser/tests/test_base_layout.py
index 271237f..2ccf8de 100644
--- a/lib/lp/app/browser/tests/test_base_layout.py
+++ b/lib/lp/app/browser/tests/test_base_layout.py
@@ -17,25 +17,19 @@ from lp.registry.interfaces.person import PersonVisibility
 from lp.services.beautifulsoup import BeautifulSoup
 from lp.services.webapp.publisher import LaunchpadView
 from lp.services.webapp.servers import LaunchpadTestRequest
-from lp.testing import (
-    login,
-    person_logged_in,
-    TestCaseWithFactory,
-    )
+from lp.testing import TestCaseWithFactory, login, person_logged_in
 from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.pages import (
-    extract_text,
-    find_tag_by_id,
-    )
+from lp.testing.pages import extract_text, find_tag_by_id
 
 
 class TestBaseLayout(TestCaseWithFactory):
     """Test the page parts provided by the base-layout.pt."""
+
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
         super().setUp()
-        self.user = self.factory.makePerson(name='waffles')
+        self.user = self.factory.makePerson(name="waffles")
         self.context = None
 
     def makeTemplateView(self, layout, context=None, view_attributes=None):
@@ -47,11 +41,13 @@ class TestBaseLayout(TestCaseWithFactory):
 
         class TemplateView(LaunchpadView):
             """A simple view to test base-layout."""
-            __name__ = '+template'
-            __launchpad_facetname__ = 'overview'
+
+            __name__ = "+template"
+            __launchpad_facetname__ = "overview"
             template = ViewPageTemplateFile(
-                'testfiles/%s.pt' % layout.replace('_', '-'))
-            page_title = 'Test base-layout: %s' % layout
+                "testfiles/%s.pt" % layout.replace("_", "-")
+            )
+            page_title = "Test base-layout: %s" % layout
 
         if context is None:
             self.context = self.user
@@ -59,7 +55,8 @@ class TestBaseLayout(TestCaseWithFactory):
             self.context = context
 
         request = LaunchpadTestRequest(
-            SERVER_URL='http://launchpad.test', PATH_INFO='/~waffles/+layout')
+            SERVER_URL="http://launchpad.test";, PATH_INFO="/~waffles/+layout"
+        )
         request.setPrincipal(self.user)
         request.traversed_objects.append(self.context)
         view = TemplateView(self.context, request)
@@ -71,18 +68,17 @@ class TestBaseLayout(TestCaseWithFactory):
 
     def test_base_layout_doctype(self):
         # Verify that the document is a html DOCTYPE.
-        view = self.makeTemplateView('main_side')
+        view = self.makeTemplateView("main_side")
         markup = view()
-        self.assertTrue(markup.startswith('<!DOCTYPE html>'))
+        self.assertTrue(markup.startswith("<!DOCTYPE html>"))
 
     def verify_base_layout_html_element(self, content):
         # The html element states the namespace and language information.
-        self.assertEqual(
-            'http://www.w3.org/1999/xhtml', content.html['xmlns'])
+        self.assertEqual("http://www.w3.org/1999/xhtml";, content.html["xmlns"])
         html_tag = content.html
-        self.assertEqual('en', html_tag['xml:lang'])
-        self.assertEqual('en', html_tag['lang'])
-        self.assertEqual('ltr', html_tag['dir'])
+        self.assertEqual("en", html_tag["xml:lang"])
+        self.assertEqual("en", html_tag["lang"])
+        self.assertEqual("ltr", html_tag["dir"])
 
     def verify_base_layout_head_parts(self, view, content):
         # Verify the common head parts of every layout.
@@ -90,113 +86,120 @@ class TestBaseLayout(TestCaseWithFactory):
         # The page's title starts with the view's page_title.
         self.assertTrue(head.title.string.startswith(view.page_title))
         # The shortcut icon for the browser chrome is provided.
-        link_tag = head.find('link', rel='shortcut icon')
-        self.assertEqual(['shortcut', 'icon'], link_tag['rel'])
-        self.assertEqual('/@@/favicon.ico?v=2022', link_tag['href'])
+        link_tag = head.find("link", rel="shortcut icon")
+        self.assertEqual(["shortcut", "icon"], link_tag["rel"])
+        self.assertEqual("/@@/favicon.ico?v=2022", link_tag["href"])
         # The template loads the common scripts.
-        load_script = find_tag_by_id(head, 'base-layout-load-scripts').name
-        self.assertEqual('script', load_script)
+        load_script = find_tag_by_id(head, "base-layout-load-scripts").name
+        self.assertEqual("script", load_script)
 
     def verify_base_layout_body_parts(self, document):
         # Verify the common body parts of every layout.
-        self.assertEqual('body', document.name)
-        yui_layout = document.find('div', 'yui-d0')
+        self.assertEqual("body", document.name)
+        yui_layout = document.find("div", "yui-d0")
         self.assertTrue(yui_layout is not None)
         self.assertEqual(
-            ['login-logout'], yui_layout.find(True, id='locationbar')['class'])
+            ["login-logout"], yui_layout.find(True, id="locationbar")["class"]
+        )
         self.assertEqual(
-            ['yui-main'], yui_layout.find(True, id='maincontent')['class'])
+            ["yui-main"], yui_layout.find(True, id="maincontent")["class"]
+        )
         self.assertEqual(
-            ['footer'], yui_layout.find(True, id='footer')['class'])
+            ["footer"], yui_layout.find(True, id="footer")["class"]
+        )
 
     def verify_watermark(self, document):
         # Verify the parts of a watermark.
-        yui_layout = document.find('div', 'yui-d0')
-        watermark = yui_layout.find(True, id='watermark')
-        self.assertEqual(['watermark-apps-portlet'], watermark['class'])
+        yui_layout = document.find("div", "yui-d0")
+        watermark = yui_layout.find(True, id="watermark")
+        self.assertEqual(["watermark-apps-portlet"], watermark["class"])
         if self.context.is_team:
-            self.assertEqual('/@@/team-logo', watermark.img['src'])
-            self.assertEqual('\u201cWaffles\u201d team', watermark.h2.a.string)
+            self.assertEqual("/@@/team-logo", watermark.img["src"])
+            self.assertEqual("\u201cWaffles\u201d team", watermark.h2.a.string)
         else:
-            self.assertEqual('/@@/person-logo', watermark.img['src'])
-            self.assertEqual('Waffles', watermark.h2.a.string)
-        self.assertEqual(['facetmenu'], watermark.ul['class'])
+            self.assertEqual("/@@/person-logo", watermark.img["src"])
+            self.assertEqual("Waffles", watermark.h2.a.string)
+        self.assertEqual(["facetmenu"], watermark.ul["class"])
 
     def test_main_side(self):
         # The main_side layout has everything.
-        view = self.makeTemplateView('main_side')
+        view = self.makeTemplateView("main_side")
         content = BeautifulSoup(view())
-        self.assertIsNot(None, content.find(text=' Extra head content '))
+        self.assertIsNot(None, content.find(text=" Extra head content "))
         self.verify_base_layout_html_element(content)
         self.verify_base_layout_head_parts(view, content)
-        document = find_tag_by_id(content, 'document')
+        document = find_tag_by_id(content, "document")
         self.verify_base_layout_body_parts(document)
-        classes = 'tab-overview main_side public yui3-skin-sam'.split()
-        self.assertEqual(classes, document['class'])
+        classes = "tab-overview main_side public yui3-skin-sam".split()
+        self.assertEqual(classes, document["class"])
         self.verify_watermark(document)
         self.assertEqual(
-            ['registering'], document.find(True, id='registration')['class'])
+            ["registering"], document.find(True, id="registration")["class"]
+        )
         self.assertEqual(
-            'Registered on 2005-09-16 by Illuminati',
-            document.find(True, id='registration').string.strip(),
-            )
+            "Registered on 2005-09-16 by Illuminati",
+            document.find(True, id="registration").string.strip(),
+        )
         self.assertEndsWith(
-            extract_text(document.find(True, id='maincontent')),
-            'Main content of the page.')
+            extract_text(document.find(True, id="maincontent")),
+            "Main content of the page.",
+        )
         self.assertEqual(
-            ['yui-b', 'side'],
-            document.find(True, id='side-portlets')['class'])
-        self.assertEqual('form', document.find(True, id='globalsearch').name)
+            ["yui-b", "side"], document.find(True, id="side-portlets")["class"]
+        )
+        self.assertEqual("form", document.find(True, id="globalsearch").name)
 
     def test_main_only(self):
         # The main_only layout has everything except side portlets.
-        view = self.makeTemplateView('main_only')
+        view = self.makeTemplateView("main_only")
         content = BeautifulSoup(view())
         self.verify_base_layout_html_element(content)
         self.verify_base_layout_head_parts(view, content)
-        document = find_tag_by_id(content, 'document')
+        document = find_tag_by_id(content, "document")
         self.verify_base_layout_body_parts(document)
-        classes = 'tab-overview main_only public yui3-skin-sam'.split()
-        self.assertEqual(classes, document['class'])
+        classes = "tab-overview main_only public yui3-skin-sam".split()
+        self.assertEqual(classes, document["class"])
         self.verify_watermark(document)
         self.assertEqual(
-            ['registering'], document.find(True, id='registration')['class'])
-        self.assertEqual(None, document.find(True, id='side-portlets'))
-        self.assertEqual('form', document.find(True, id='globalsearch').name)
+            ["registering"], document.find(True, id="registration")["class"]
+        )
+        self.assertEqual(None, document.find(True, id="side-portlets"))
+        self.assertEqual("form", document.find(True, id="globalsearch").name)
 
     def test_searchless(self):
         # The searchless layout is missing side portlets and search.
-        view = self.makeTemplateView('searchless')
+        view = self.makeTemplateView("searchless")
         content = BeautifulSoup(view())
         self.verify_base_layout_html_element(content)
         self.verify_base_layout_head_parts(view, content)
-        document = find_tag_by_id(content, 'document')
+        document = find_tag_by_id(content, "document")
         self.verify_base_layout_body_parts(document)
         self.verify_watermark(document)
-        classes = 'tab-overview searchless public yui3-skin-sam'.split()
-        self.assertEqual(classes, document['class'])
+        classes = "tab-overview searchless public yui3-skin-sam".split()
+        self.assertEqual(classes, document["class"])
         self.assertEqual(
-            ['registering'], document.find(True, id='registration')['class'])
-        self.assertEqual(None, document.find(True, id='side-portlets'))
-        self.assertEqual(None, document.find(True, id='globalsearch'))
+            ["registering"], document.find(True, id="registration")["class"]
+        )
+        self.assertEqual(None, document.find(True, id="side-portlets"))
+        self.assertEqual(None, document.find(True, id="globalsearch"))
 
     def test_contact_support_logged_in(self):
         # The support link points to /support when the user is logged in.
-        view = self.makeTemplateView('main_only')
+        view = self.makeTemplateView("main_only")
         view._user = self.user
         content = BeautifulSoup(view())
-        footer = find_tag_by_id(content, 'footer')
-        link = footer.find('a', text='Contact Launchpad Support')
-        self.assertEqual('/support', link['href'])
+        footer = find_tag_by_id(content, "footer")
+        link = footer.find("a", text="Contact Launchpad Support")
+        self.assertEqual("/support", link["href"])
 
     def test_contact_support_anonymous(self):
         # The support link points to /feedback when the user is anonymous.
-        view = self.makeTemplateView('main_only')
+        view = self.makeTemplateView("main_only")
         view._user = None
         content = BeautifulSoup(view())
-        footer = find_tag_by_id(content, 'footer')
-        link = footer.find('a', text='Contact Launchpad Support')
-        self.assertEqual('/feedback', link['href'])
+        footer = find_tag_by_id(content, "footer")
+        link = footer.find("a", text="Contact Launchpad Support")
+        self.assertEqual("/feedback", link["href"])
 
     def test_user_without_launchpad_view(self):
         # When the user does not have launchpad.View on the context,
@@ -204,87 +207,92 @@ class TestBaseLayout(TestCaseWithFactory):
         owner = self.factory.makePerson()
         with person_logged_in(owner):
             team = self.factory.makeTeam(
-                displayname='Waffles', owner=owner,
-                visibility=PersonVisibility.PRIVATE)
+                displayname="Waffles",
+                owner=owner,
+                visibility=PersonVisibility.PRIVATE,
+            )
             archive = self.factory.makeArchive(private=True, owner=team)
             archive.newSubscription(self.user, registrant=owner)
         with person_logged_in(self.user):
-            view = self.makeTemplateView('main_side', context=team)
+            view = self.makeTemplateView("main_side", context=team)
             content = BeautifulSoup(view())
-        self.assertIs(None, content.find(text=' Extra head content '))
+        self.assertIs(None, content.find(text=" Extra head content "))
         self.verify_base_layout_html_element(content)
         self.verify_base_layout_head_parts(view, content)
-        document = find_tag_by_id(content, 'document')
+        document = find_tag_by_id(content, "document")
         self.verify_base_layout_body_parts(document)
         self.verify_watermark(document)
         # These parts are unique to the case without launchpad.View.
-        self.assertIsNone(document.find(True, id='side-portlets'))
-        self.assertIsNone(document.find(True, id='registration'))
+        self.assertIsNone(document.find(True, id="side-portlets"))
+        self.assertIsNone(document.find(True, id="registration"))
         self.assertEndsWith(
-            extract_text(document.find(True, id='maincontent')),
-            'The information in this page is not shared with you.')
+            extract_text(document.find(True, id="maincontent")),
+            "The information in this page is not shared with you.",
+        )
 
     def test_user_with_launchpad_view(self):
         # Users with launchpad.View do not see the sharing explanation.
         # See the main_side, main_only, and searchless tests to know
         # what content is provides to the user who can view.
-        view = self.makeTemplateView('main_side')
-        content = extract_text(find_tag_by_id(view(), 'maincontent'))
+        view = self.makeTemplateView("main_side")
+        content = extract_text(find_tag_by_id(view(), "maincontent"))
         self.assertNotIn(
-            'The information in this page is not shared with you.', content)
+            "The information in this page is not shared with you.", content
+        )
 
     def test_referrer_policy_set_private_view(self):
-        login('admin@xxxxxxxxxxxxx')
+        login("admin@xxxxxxxxxxxxx")
         owner = self.factory.makePerson()
         with person_logged_in(owner):
             team = self.factory.makeTeam(
-                owner=owner,
-                visibility=PersonVisibility.PRIVATE)
-        view = self.makeTemplateView('main_side', context=team)
+                owner=owner, visibility=PersonVisibility.PRIVATE
+            )
+        view = self.makeTemplateView("main_side", context=team)
         content = BeautifulSoup(view())
-        referrer = content.find('meta',
-                                {'name': 'referrer',
-                                 'content': 'origin-when-cross-origin'})
+        referrer = content.find(
+            "meta", {"name": "referrer", "content": "origin-when-cross-origin"}
+        )
         self.assertIsNotNone(referrer)
-        self.assertEqual(referrer.get('content'), 'origin-when-cross-origin')
-        self.assertEqual(referrer.get('name'), 'referrer')
+        self.assertEqual(referrer.get("content"), "origin-when-cross-origin")
+        self.assertEqual(referrer.get("name"), "referrer")
 
     def test_referrer_policy_set_public_view(self):
-        view = self.makeTemplateView('main_side')
+        view = self.makeTemplateView("main_side")
         content = BeautifulSoup(view())
-        referrer = content.find('meta', content="origin-when-cross-origin")
+        referrer = content.find("meta", content="origin-when-cross-origin")
         self.assertIsNone(referrer)
 
     def test_opengraph_metadata(self):
-        view = self.makeTemplateView('main_side')
+        view = self.makeTemplateView("main_side")
         content = BeautifulSoup(view())
 
         # https://ogp.me/ - "The four required properties for every page are:"
-        og_title = content.find('meta', {'property': 'og:title'})
+        og_title = content.find("meta", {"property": "og:title"})
         self.assertIsNotNone(og_title)
-        og_type = content.find('meta', {'property': 'og:type'})
+        og_type = content.find("meta", {"property": "og:type"})
         self.assertIsNotNone(og_type)
-        og_image = content.find('meta', {'property': 'og:image'})
+        og_image = content.find("meta", {"property": "og:image"})
         self.assertIsNotNone(og_image)
-        og_url = content.find('meta', {'property': 'og:url'})
+        og_url = content.find("meta", {"property": "og:url"})
         self.assertIsNotNone(og_url)
 
         # And some basic validity checks
-        self.assertEqual(og_type.get('content'), 'website')
-        self.assertIn('png', og_image.get('content'))
-        self.assertIn('Test', og_title.get('content'))
-        self.assertIn('http', og_url.get('content'))
+        self.assertEqual(og_type.get("content"), "website")
+        self.assertIn("png", og_image.get("content"))
+        self.assertIn("Test", og_title.get("content"))
+        self.assertIn("http", og_url.get("content"))
 
     def test_opengraph_metadata_missing_on_404_page(self):
         view = self.makeTemplateView(
-            'main_side', view_attributes={'show_opengraph_meta': False})
+            "main_side", view_attributes={"show_opengraph_meta": False}
+        )
         content = BeautifulSoup(view())
 
-        og_title = content.find('meta', {'property': 'og:title'})
+        og_title = content.find("meta", {"property": "og:title"})
         self.assertIsNone(og_title)
-        og_type = content.find('meta', {'property': 'og:type'})
+        og_type = content.find("meta", {"property": "og:type"})
         self.assertIsNone(og_type)
-        og_image = content.find('meta', {'property': 'og:image'})
+        og_image = content.find("meta", {"property": "og:image"})
         self.assertIsNone(og_image)
-        og_url = content.find('meta', {'property': 'og:url'})
+        og_url = content.find("meta", {"property": "og:url"})
         self.assertIsNone(og_url)
diff --git a/lib/lp/app/browser/tests/test_css_formatter.py b/lib/lp/app/browser/tests/test_css_formatter.py
index 5c28923..c60af07 100644
--- a/lib/lp/app/browser/tests/test_css_formatter.py
+++ b/lib/lp/app/browser/tests/test_css_formatter.py
@@ -5,10 +5,7 @@
 
 from testtools.matchers import Equals
 
-from lp.testing import (
-    test_tales,
-    TestCase,
-    )
+from lp.testing import TestCase, test_tales
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
@@ -17,20 +14,21 @@ class TestCSSFormatter(TestCase):
     layer = DatabaseFunctionalLayer
 
     def test_select(self):
-        value = test_tales('value/css:select/visible/hidden', value=None)
-        self.assertThat(value, Equals('hidden'))
-        value = test_tales('value/css:select/visible/hidden', value=False)
-        self.assertThat(value, Equals('hidden'))
-        value = test_tales('value/css:select/visible/hidden', value='')
-        self.assertThat(value, Equals('hidden'))
-        value = test_tales('value/css:select/visible/hidden', value=True)
-        self.assertThat(value, Equals('visible'))
-        value = test_tales('value/css:select/visible/hidden', value='Hello')
-        self.assertThat(value, Equals('visible'))
-        value = test_tales('value/css:select/visible/hidden', value=object())
-        self.assertThat(value, Equals('visible'))
+        value = test_tales("value/css:select/visible/hidden", value=None)
+        self.assertThat(value, Equals("hidden"))
+        value = test_tales("value/css:select/visible/hidden", value=False)
+        self.assertThat(value, Equals("hidden"))
+        value = test_tales("value/css:select/visible/hidden", value="")
+        self.assertThat(value, Equals("hidden"))
+        value = test_tales("value/css:select/visible/hidden", value=True)
+        self.assertThat(value, Equals("visible"))
+        value = test_tales("value/css:select/visible/hidden", value="Hello")
+        self.assertThat(value, Equals("visible"))
+        value = test_tales("value/css:select/visible/hidden", value=object())
+        self.assertThat(value, Equals("visible"))
 
     def test_select_chaining(self):
         value = test_tales(
-            'value/css:select/VISIBLE/hidden/fmt:lower', value=None)
-        self.assertThat(value, Equals('hidden'))
+            "value/css:select/VISIBLE/hidden/fmt:lower", value=None
+        )
+        self.assertThat(value, Equals("hidden"))
diff --git a/lib/lp/app/browser/tests/test_formatters.py b/lib/lp/app/browser/tests/test_formatters.py
index c917b5e..b659677 100644
--- a/lib/lp/app/browser/tests/test_formatters.py
+++ b/lib/lp/app/browser/tests/test_formatters.py
@@ -3,15 +3,9 @@
 
 """Tests for the TALES formatters."""
 
-from lp.app.browser.tales import (
-    ObjectFormatterAPI,
-    PillarFormatterAPI,
-    )
+from lp.app.browser.tales import ObjectFormatterAPI, PillarFormatterAPI
 from lp.services.webapp.publisher import canonical_url
-from lp.testing import (
-    FakeAdapterMixin,
-    TestCaseWithFactory,
-    )
+from lp.testing import FakeAdapterMixin, TestCaseWithFactory
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.views import create_view
 
@@ -21,90 +15,112 @@ class ObjectFormatterAPITestCase(TestCaseWithFactory, FakeAdapterMixin):
     layer = DatabaseFunctionalLayer
 
     def test_pagetitle_top_level(self):
-        project = self.factory.makeProduct(name='fnord')
-        view = create_view(project, name='+index', current_request=True)
+        project = self.factory.makeProduct(name="fnord")
+        view = create_view(project, name="+index", current_request=True)
         view.request.traversed_objects = [project, view]
         formatter = ObjectFormatterAPI(view)
-        self.assertEqual('Fnord in Launchpad', formatter.pagetitle())
+        self.assertEqual("Fnord in Launchpad", formatter.pagetitle())
 
     def test_pagetitle_vhost(self):
-        project = self.factory.makeProduct(name='fnord')
-        view = create_view(project, name='+bugs', rootsite='bugs',
-            current_request=True, server_url='https://bugs.launchpad.test/')
+        project = self.factory.makeProduct(name="fnord")
+        view = create_view(
+            project,
+            name="+bugs",
+            rootsite="bugs",
+            current_request=True,
+            server_url="https://bugs.launchpad.test/";,
+        )
         view.request.traversed_objects = [project, view]
         formatter = ObjectFormatterAPI(view)
-        self.assertEqual('Bugs : Fnord', formatter.pagetitle())
+        self.assertEqual("Bugs : Fnord", formatter.pagetitle())
 
     def test_pagetitle_lower_level_default_view(self):
-        project = self.factory.makeProduct(name='fnord')
+        project = self.factory.makeProduct(name="fnord")
         view = create_view(
-            project.development_focus, name='+index', current_request=True)
+            project.development_focus, name="+index", current_request=True
+        )
         view.request.traversed_objects = [
-            project, project.development_focus, view]
+            project,
+            project.development_focus,
+            view,
+        ]
         formatter = ObjectFormatterAPI(view)
-        self.assertEqual('Series trunk : Fnord', formatter.pagetitle())
+        self.assertEqual("Series trunk : Fnord", formatter.pagetitle())
 
     def test_pagetitle_lower_level_named_view(self):
-        project = self.factory.makeProduct(name='fnord')
+        project = self.factory.makeProduct(name="fnord")
         view = create_view(
-            project.development_focus, name='+edit', current_request=True)
+            project.development_focus, name="+edit", current_request=True
+        )
         view.request.traversed_objects = [
-            project, project.development_focus, view]
+            project,
+            project.development_focus,
+            view,
+        ]
         formatter = ObjectFormatterAPI(view)
         self.assertEqual(
-            'Edit Fnord trunk series : Series trunk : Fnord',
-            formatter.pagetitle())
+            "Edit Fnord trunk series : Series trunk : Fnord",
+            formatter.pagetitle(),
+        )
 
     def test_pagetitle_last_breadcrumb_detail(self):
-        project = self.factory.makeProduct(name='fnord')
-        bug = self.factory.makeBug(target=project, title='bang')
+        project = self.factory.makeProduct(name="fnord")
+        bug = self.factory.makeBug(target=project, title="bang")
         view = create_view(
-            bug.bugtasks[0], name='+index', rootsite='bugs',
-            current_request=True, server_url='https://bugs.launchpad.test/')
+            bug.bugtasks[0],
+            name="+index",
+            rootsite="bugs",
+            current_request=True,
+            server_url="https://bugs.launchpad.test/";,
+        )
         view.request.traversed_objects = [project, bug.bugtasks[0], view]
         formatter = ObjectFormatterAPI(view)
         self.assertEqual(
-            '%s \u201cbang\u201d : Bugs : Fnord' % bug.displayname,
-            formatter.pagetitle())
+            "%s \u201cbang\u201d : Bugs : Fnord" % bug.displayname,
+            formatter.pagetitle(),
+        )
 
     def test_pagetitle_last_breadcrumb_detail_too_long(self):
-        project = self.factory.makeProduct(name='fnord')
-        title = 'Bang out go the lights ' * 4
+        project = self.factory.makeProduct(name="fnord")
+        title = "Bang out go the lights " * 4
         bug = self.factory.makeBug(target=project, title=title)
         view = create_view(
-            bug.bugtasks[0], name='+index', rootsite='bugs',
-            current_request=True, server_url='https://bugs.launchpad.test/')
+            bug.bugtasks[0],
+            name="+index",
+            rootsite="bugs",
+            current_request=True,
+            server_url="https://bugs.launchpad.test/";,
+        )
         view.request.traversed_objects = [project, bug.bugtasks[0], view]
         formatter = ObjectFormatterAPI(view)
-        detail = '%s \u201c%s\u201d' % (bug.displayname, title)
-        expected_title = '%s...\u201d : Bugs : Fnord' % detail[0:64]
+        detail = "%s \u201c%s\u201d" % (bug.displayname, title)
+        expected_title = "%s...\u201d : Bugs : Fnord" % detail[0:64]
         self.assertEqual(expected_title, formatter.pagetitle())
 
     def test_global_css(self):
         person = self.factory.makePerson()
         view = create_view(person, name="+index")
         formatter = ObjectFormatterAPI(view)
-        self.assertEqual('public', formatter.global_css())
+        self.assertEqual("public", formatter.global_css())
 
         view = create_view(person, name="+archivesubscriptions")
         formatter = ObjectFormatterAPI(view)
-        self.assertEqual(
-            'private',
-            formatter.global_css())
+        self.assertEqual("private", formatter.global_css())
 
 
 class TestPillarFormatterAPI(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer
 
-    FORMATTER_CSS_CLASS = 'sprite product'
+    FORMATTER_CSS_CLASS = "sprite product"
 
     def setUp(self):
         super().setUp()
         self.product = self.factory.makeProduct()
         self.formatter = PillarFormatterAPI(self.product)
         self.product_url = canonical_url(
-            self.product, path_only_if_possible=True)
+            self.product, path_only_if_possible=True
+        )
 
     def test_link(self):
         # Calling PillarFormatterAPI.link() will return a link to the
@@ -113,10 +129,10 @@ class TestPillarFormatterAPI(TestCaseWithFactory):
         link = self.formatter.link(None)
         template = '<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
         mapping = {
-            'url': self.product_url,
-            'summary': self.product.displayname,
-            'css_class': self.FORMATTER_CSS_CLASS,
-            }
+            "url": self.product_url,
+            "summary": self.product.displayname,
+            "css_class": self.FORMATTER_CSS_CLASS,
+        }
         self.assertEqual(link, template % mapping)
 
     def test_link_with_displayname(self):
@@ -128,11 +144,11 @@ class TestPillarFormatterAPI(TestCaseWithFactory):
         template = (
             '<a href="%(url)s" class="%(css_class)s">%(summary)s</a>'
             '&nbsp;(<a href="%(url)s">%(name)s</a>)'
-            )
+        )
         mapping = {
-            'url': self.product_url,
-            'summary': self.product.displayname,
-            'name': self.product.name,
-            'css_class': self.FORMATTER_CSS_CLASS,
-            }
+            "url": self.product_url,
+            "summary": self.product.displayname,
+            "name": self.product.name,
+            "css_class": self.FORMATTER_CSS_CLASS,
+        }
         self.assertEqual(link, template % mapping)
diff --git a/lib/lp/app/browser/tests/test_inlineeditpickerwidget.py b/lib/lp/app/browser/tests/test_inlineeditpickerwidget.py
index bb78430..c6fe5c9 100644
--- a/lib/lp/app/browser/tests/test_inlineeditpickerwidget.py
+++ b/lib/lp/app/browser/tests/test_inlineeditpickerwidget.py
@@ -3,20 +3,14 @@
 
 """Tests for the InlineEditPickerWidget."""
 
-from zope.interface import (
-    implementer,
-    Interface,
-    )
+from zope.interface import Interface, implementer
 from zope.schema import Choice
 
 from lp.app.browser.lazrjs import (
     InlineEditPickerWidget,
     InlinePersonEditPickerWidget,
-    )
-from lp.testing import (
-    login_person,
-    TestCaseWithFactory,
-    )
+)
+from lp.testing import TestCaseWithFactory, login_person
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
@@ -27,39 +21,47 @@ class TestInlineEditPickerWidget(TestCaseWithFactory):
     def getWidget(self, **kwargs):
         class ITest(Interface):
             test_field = Choice(**kwargs)
+
         return InlineEditPickerWidget(
-            None, ITest['test_field'], None, edit_url='fake')
+            None, ITest["test_field"], None, edit_url="fake"
+        )
 
     def test_huge_vocabulary_is_searchable(self):
         # Make sure that when given a field for a huge vocabulary, the picker
         # is set to show the search box.
-        widget = self.getWidget(vocabulary='ValidPersonOrTeam')
-        self.assertTrue(widget.config['show_search_box'])
+        widget = self.getWidget(vocabulary="ValidPersonOrTeam")
+        self.assertTrue(widget.config["show_search_box"])
 
     def test_vocabulary_filters(self):
         # Make sure that when given a vocabulary which supports vocab filters,
         # the vocab filters are include in the widget config.
-        widget = self.getWidget(vocabulary='ValidPersonOrTeam')
-        self.assertEqual([
-            {'name': 'ALL',
-             'title': 'All',
-             'description': 'Display all search results'},
-            {'name': 'PERSON',
-             'title': 'Person',
-             'description':
-                 'Display search results for people only'},
-            {'name': 'TEAM',
-             'title': 'Team',
-             'description':
-                 'Display search results for teams only'}
+        widget = self.getWidget(vocabulary="ValidPersonOrTeam")
+        self.assertEqual(
+            [
+                {
+                    "name": "ALL",
+                    "title": "All",
+                    "description": "Display all search results",
+                },
+                {
+                    "name": "PERSON",
+                    "title": "Person",
+                    "description": "Display search results for people only",
+                },
+                {
+                    "name": "TEAM",
+                    "title": "Team",
+                    "description": "Display search results for teams only",
+                },
             ],
-            widget.config['vocabulary_filters'])
+            widget.config["vocabulary_filters"],
+        )
 
     def test_normal_vocabulary_is_not_searchable(self):
         # Make sure that when given a field for a normal vocabulary, the
         # picker is set to show the search box.
-        widget = self.getWidget(vocabulary='UserTeamsParticipation')
-        self.assertFalse(widget.config['show_search_box'])
+        widget = self.getWidget(vocabulary="UserTeamsParticipation")
+        self.assertFalse(widget.config["show_search_box"])
 
 
 class TestInlinePersonEditPickerWidget(TestCaseWithFactory):
@@ -72,52 +74,60 @@ class TestInlinePersonEditPickerWidget(TestCaseWithFactory):
 
         @implementer(ITest)
         class Test:
-
             def __init__(self):
                 self.test_field = widget_value
 
         context = Test()
         return InlinePersonEditPickerWidget(
-            context, ITest['test_field'], None, edit_url='fake',
-            show_create_team=show_create_team)
+            context,
+            ITest["test_field"],
+            None,
+            edit_url="fake",
+            show_create_team=show_create_team,
+        )
 
     def test_person_selected_value_meta(self):
         # The widget has the correct meta value for a person value.
         widget_value = self.factory.makePerson()
-        widget = self.getWidget(widget_value, vocabulary='ValidPersonOrTeam')
-        self.assertEqual('person', widget.config['selected_value_metadata'])
+        widget = self.getWidget(widget_value, vocabulary="ValidPersonOrTeam")
+        self.assertEqual("person", widget.config["selected_value_metadata"])
 
     def test_team_selected_value_meta(self):
         # The widget has the correct meta value for a team value.
         widget_value = self.factory.makeTeam()
-        widget = self.getWidget(widget_value, vocabulary='ValidPersonOrTeam')
-        self.assertEqual('team', widget.config['selected_value_metadata'])
+        widget = self.getWidget(widget_value, vocabulary="ValidPersonOrTeam")
+        self.assertEqual("team", widget.config["selected_value_metadata"])
 
     def test_required_fields_dont_have_a_remove_link(self):
         widget = self.getWidget(
-            None, vocabulary='ValidPersonOrTeam', required=True)
-        self.assertFalse(widget.config['show_remove_button'])
+            None, vocabulary="ValidPersonOrTeam", required=True
+        )
+        self.assertFalse(widget.config["show_remove_button"])
 
     def test_optional_fields_do_have_a_remove_link(self):
         widget = self.getWidget(
-            None, vocabulary='ValidPersonOrTeam', required=False)
-        self.assertTrue(widget.config['show_remove_button'])
+            None, vocabulary="ValidPersonOrTeam", required=False
+        )
+        self.assertTrue(widget.config["show_remove_button"])
 
     def test_assign_me_exists_if_user_in_vocabulary(self):
         widget = self.getWidget(
-            None, vocabulary='ValidPersonOrTeam', required=True)
+            None, vocabulary="ValidPersonOrTeam", required=True
+        )
         login_person(self.factory.makePerson())
-        self.assertTrue(widget.config['show_assign_me_button'])
+        self.assertTrue(widget.config["show_assign_me_button"])
 
     def test_assign_me_not_shown_if_user_not_in_vocabulary(self):
-        widget = self.getWidget(
-            None, vocabulary='TargetPPAs', required=True)
+        widget = self.getWidget(None, vocabulary="TargetPPAs", required=True)
         login_person(self.factory.makePerson())
-        self.assertFalse(widget.config['show_assign_me_button'])
+        self.assertFalse(widget.config["show_assign_me_button"])
 
     def test_show_create_team_link(self):
         widget = self.getWidget(
-            None, vocabulary='ValidPersonOrTeam', required=True,
-            show_create_team=True)
+            None,
+            vocabulary="ValidPersonOrTeam",
+            required=True,
+            show_create_team=True,
+        )
         login_person(self.factory.makePerson())
-        self.assertTrue(widget.config['show_create_team'])
+        self.assertTrue(widget.config["show_create_team"])
diff --git a/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py b/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py
index 745f203..2e0af72 100644
--- a/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py
+++ b/lib/lp/app/browser/tests/test_inlinemulticheckboxwidget.py
@@ -3,11 +3,8 @@
 
 """Tests for the InlineMultiCheckboxWidget."""
 
-from lazr.enum import (
-    EnumeratedType,
-    Item,
-    )
 import simplejson
+from lazr.enum import EnumeratedType, Item
 from zope.interface import Interface
 from zope.schema import List
 from zope.schema._field import Choice
@@ -21,6 +18,7 @@ from lp.testing.layers import DatabaseFunctionalLayer
 
 class Alphabet(EnumeratedType):
     """A vocabulary for testing."""
+
     A = Item("A", "Letter A")
     B = Item("B", "Letter B")
 
@@ -30,33 +28,35 @@ class TestInlineMultiCheckboxWidget(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def _getWidget(self, **kwargs):
-
         class ITest(Interface):
-            test_field = List(
-                Choice(vocabulary='BuildableDistroSeries'))
+            test_field = List(Choice(vocabulary="BuildableDistroSeries"))
+
         return InlineMultiCheckboxWidget(
-            None, ITest['test_field'], "Label", edit_url='fake', **kwargs)
+            None, ITest["test_field"], "Label", edit_url="fake", **kwargs
+        )
 
     def _makeExpectedItems(self, vocab, selected=list(), value_fn=None):
         if value_fn is None:
             value_fn = lambda item: item.value.name
         expected_items = []
-        style = 'font-weight: normal;'
+        style = "font-weight: normal;"
         for item in vocab:
             new_item = {
-                'name': item.title,
-                'token': item.token,
-                'style': style,
-                'checked': (item.value in selected),
-                'value': value_fn(item)}
+                "name": item.title,
+                "token": item.token,
+                "style": style,
+                "checked": (item.value in selected),
+                "value": value_fn(item),
+            }
             expected_items.append(new_item)
         return expected_items
 
     def test_items_for_field_vocabulary(self):
         widget = self._getWidget(attribute_type="reference")
-        vocab = getVocabularyRegistry().get(None, 'BuildableDistroSeries')
+        vocab = getVocabularyRegistry().get(None, "BuildableDistroSeries")
         value_fn = lambda item: canonical_url(
-            item.value, force_local_path=True)
+            item.value, force_local_path=True
+        )
         expected_items = self._makeExpectedItems(vocab, value_fn=value_fn)
         self.assertEqual(simplejson.dumps(expected_items), widget.json_items)
 
@@ -73,7 +73,9 @@ class TestInlineMultiCheckboxWidget(TestCaseWithFactory):
 
     def test_selected_items_checked(self):
         widget = self._getWidget(
-            vocabulary=Alphabet, selected_items=[Alphabet.A])
+            vocabulary=Alphabet, selected_items=[Alphabet.A]
+        )
         expected_items = self._makeExpectedItems(
-            Alphabet, selected=[Alphabet.A])
+            Alphabet, selected=[Alphabet.A]
+        )
         self.assertEqual(simplejson.dumps(expected_items), widget.json_items)
diff --git a/lib/lp/app/browser/tests/test_launchpad.py b/lib/lp/app/browser/tests/test_launchpad.py
index 3b2ceaa..72605c3 100644
--- a/lib/lp/app/browser/tests/test_launchpad.py
+++ b/lib/lp/app/browser/tests/test_launchpad.py
@@ -3,29 +3,22 @@
 
 """Tests for traversal from the root branch object."""
 
-from zope.component import (
-    getMultiAdapter,
-    getUtility,
-    )
+from zope.component import getMultiAdapter, getUtility
 from zope.publisher.interfaces import NotFound
 from zope.security.interfaces import Unauthorized
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.browser.launchpad import (
-    iter_view_registrations,
     LaunchpadRootNavigation,
-    )
+    iter_view_registrations,
+)
 from lp.app.enums import InformationType
 from lp.app.errors import GoneError
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.app.interfaces.services import IService
 from lp.code.interfaces.gitrepository import IGitRepositorySet
 from lp.code.interfaces.linkedbranch import ICanHasLinkedBranch
-from lp.registry.enums import (
-    PersonVisibility,
-    SharingPermission,
-    VCSType,
-    )
+from lp.registry.enums import PersonVisibility, SharingPermission, VCSType
 from lp.registry.interfaces.person import IPersonSet
 from lp.services.identity.interfaces.account import AccountStatus
 from lp.services.webapp import canonical_url
@@ -33,39 +26,34 @@ from lp.services.webapp.escaping import html_escape
 from lp.services.webapp.interfaces import (
     BrowserNotificationLevel,
     ILaunchpadRoot,
-    )
+)
 from lp.services.webapp.servers import (
     LaunchpadTestRequest,
     WebServiceTestRequest,
-    )
+)
 from lp.services.webapp.url import urlappend
 from lp.testing import (
-    admin_logged_in,
     ANONYMOUS,
+    TestCaseWithFactory,
+    admin_logged_in,
     celebrity_logged_in,
     login,
     login_person,
     person_logged_in,
-    TestCaseWithFactory,
-    )
-from lp.testing.layers import (
-    DatabaseFunctionalLayer,
-    FunctionalLayer,
-    )
+)
+from lp.testing.layers import DatabaseFunctionalLayer, FunctionalLayer
 from lp.testing.publication import test_traverse
 from lp.testing.views import create_view
 
-
 # We set the request header HTTP_REFERER  when we want to simulate navigation
 # from a valid page. This is used in the assertDisplaysNotification check.
-DEFAULT_REFERER = 'http://launchpad.test'
+DEFAULT_REFERER = "http://launchpad.test";
 
 
 class TraversalMixin:
-
     def _validateNotificationContext(
-        self, request, notification=None,
-        level=BrowserNotificationLevel.INFO):
+        self, request, notification=None, level=BrowserNotificationLevel.INFO
+    ):
         """Check the browser notifications associated with the request.
 
         Ensure that the notification instances attached to the request match
@@ -86,8 +74,8 @@ class TraversalMixin:
         self.assertEqual(notification, notifications[0].message)
 
     def assertDisplaysNotification(
-        self, path, notification=None,
-        level=BrowserNotificationLevel.INFO):
+        self, path, notification=None, level=BrowserNotificationLevel.INFO
+    ):
         """Assert that an invalid path redirects back to referrer.
 
         The request object is expected to have a notification message to
@@ -103,19 +91,24 @@ class TraversalMixin:
         redirection = self.traverse(path)
         self.assertIs(redirection.target, DEFAULT_REFERER)
         self._validateNotificationContext(
-            redirection.request, notification, level)
+            redirection.request, notification, level
+        )
 
     def assertNotFound(self, path, use_default_referer=True):
         self.assertRaises(
-            NotFound, self.traverse, path,
-            use_default_referer=use_default_referer)
+            NotFound,
+            self.traverse,
+            path,
+            use_default_referer=use_default_referer,
+        )
 
     def assertRedirects(self, segments, url, webservice=False):
         redirection = self.traverse(segments, webservice=webservice)
         self.assertEqual(url, redirection.target)
 
-    def traverse(self, path, first_segment, use_default_referer=True,
-                 webservice=False):
+    def traverse(
+        self, path, first_segment, use_default_referer=True, webservice=False
+    ):
         """Traverse to 'path' using a 'LaunchpadRootNavigation' object.
 
         Using the Zope traversal machinery, traverse to the path given by
@@ -134,16 +127,18 @@ class TraversalMixin:
         """
         # XXX: What's the difference between first_segment and path? -- mbp
         # 2011-06-27.
-        extra = {'PATH_INFO': urlappend('/%s' % first_segment, path)}
+        extra = {"PATH_INFO": urlappend("/%s" % first_segment, path)}
         if use_default_referer:
-            extra['HTTP_REFERER'] = DEFAULT_REFERER
+            extra["HTTP_REFERER"] = DEFAULT_REFERER
         request_factory = (
-            WebServiceTestRequest if webservice else LaunchpadTestRequest)
+            WebServiceTestRequest if webservice else LaunchpadTestRequest
+        )
         request = request_factory(**extra)
-        segments = reversed(path.split('/'))
+        segments = reversed(path.split("/"))
         request.setTraversalStack(segments)
         traverser = LaunchpadRootNavigation(
-            getUtility(ILaunchpadRoot), request=request)
+            getUtility(ILaunchpadRoot), request=request
+        )
         return traverser.publishTraverse(request, first_segment)
 
 
@@ -159,15 +154,17 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
     def assertDisplaysNotice(self, path, notification):
         """Assert that traversal redirects back with the specified notice."""
         self.assertDisplaysNotification(
-            path, notification, BrowserNotificationLevel.INFO)
+            path, notification, BrowserNotificationLevel.INFO
+        )
 
     def assertDisplaysError(self, path, notification):
         """Assert that traversal redirects back with the specified notice."""
         self.assertDisplaysNotification(
-            path, notification, BrowserNotificationLevel.ERROR)
+            path, notification, BrowserNotificationLevel.ERROR
+        )
 
     def traverse(self, path, **kwargs):
-        return super().traverse(path, '+branch', **kwargs)
+        return super().traverse(path, "+branch", **kwargs)
 
     def test_unique_name_traversal(self):
         # Traversing to /+branch/<unique_name> redirects to the page for that
@@ -175,26 +172,31 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         branch = self.factory.makeAnyBranch()
         self.assertRedirects(branch.unique_name, canonical_url(branch))
         self.assertRedirects(
-            branch.unique_name, canonical_url(branch, rootsite='api'),
-            webservice=True)
+            branch.unique_name,
+            canonical_url(branch, rootsite="api"),
+            webservice=True,
+        )
 
     def test_no_such_unique_name(self):
         # Traversing to /+branch/<unique_name> where 'unique_name' is for a
         # branch that doesn't exist will display an error message.
         branch = self.factory.makeAnyBranch()
-        bad_name = branch.unique_name + 'wibble'
+        bad_name = branch.unique_name + "wibble"
         required_message = html_escape(
-            "No such branch: '%s'." % (branch.name + "wibble"))
+            "No such branch: '%s'." % (branch.name + "wibble")
+        )
         self.assertDisplaysError(bad_name, required_message)
 
     def test_private_branch(self):
         # If an attempt is made to access a private branch, display an error.
         branch = self.factory.makeProductBranch(
-            information_type=InformationType.USERDATA)
+            information_type=InformationType.USERDATA
+        )
         branch_unique_name = removeSecurityProxy(branch).unique_name
         login(ANONYMOUS)
         required_message = html_escape(
-            "No such branch: '%s'." % branch_unique_name)
+            "No such branch: '%s'." % branch_unique_name
+        )
         self.assertDisplaysError(branch_unique_name, required_message)
 
     def test_product_alias(self):
@@ -211,17 +213,16 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         branch = self.factory.makeProductBranch()
         naked_product = removeSecurityProxy(branch.product)
         ICanHasLinkedBranch(naked_product).setBranch(branch)
-        removeSecurityProxy(branch).information_type = (
-            InformationType.USERDATA)
+        removeSecurityProxy(branch).information_type = InformationType.USERDATA
         login(ANONYMOUS)
         requiredMessage = (
-            "The target %s does not have a linked branch." %
-            naked_product.name)
+            "The target %s does not have a linked branch." % naked_product.name
+        )
         self.assertDisplaysNotice(naked_product.name, requiredMessage)
 
     def test_nonexistent_product(self):
         # Traversing to /+branch/<no-such-product> displays an error message.
-        non_existent = 'non-existent'
+        non_existent = "non-existent"
         required_message = "No such product: '%s'." % non_existent
         self.assertDisplaysError(non_existent, html_escape(required_message))
 
@@ -229,7 +230,7 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         # Traversing to /+branch/<no-such-product> without a referer results
         # in a 404 error. This happens if the user hacks the URL rather than
         # navigating via a link
-        self.assertNotFound('non-existent', use_default_referer=False)
+        self.assertNotFound("non-existent", use_default_referer=False)
 
     def test_private_without_referer(self):
         # If the development focus of a product is private and there is no
@@ -238,8 +239,7 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         branch = self.factory.makeProductBranch()
         naked_product = removeSecurityProxy(branch.product)
         ICanHasLinkedBranch(naked_product).setBranch(branch)
-        removeSecurityProxy(branch).information_type = (
-            InformationType.USERDATA)
+        removeSecurityProxy(branch).information_type = InformationType.USERDATA
         login(ANONYMOUS)
         self.assertNotFound(naked_product.name, use_default_referer=False)
 
@@ -248,7 +248,8 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         # user message on the same page.
         product = self.factory.makeProduct()
         requiredMessage = (
-            "The target %s does not have a linked branch." % product.name)
+            "The target %s does not have a linked branch." % product.name
+        )
         self.assertDisplaysNotice(product.name, requiredMessage)
 
     def test_distro_package_alias(self):
@@ -270,31 +271,31 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         sourcepackage = self.factory.makeSourcePackage()
         branch = self.factory.makePackageBranch(
             sourcepackage=sourcepackage,
-            information_type=InformationType.USERDATA)
+            information_type=InformationType.USERDATA,
+        )
         distro_package = sourcepackage.distribution_sourcepackage
         registrant = distro_package.distribution.owner
         with person_logged_in(registrant):
             ICanHasLinkedBranch(distro_package).setBranch(branch, registrant)
         login(ANONYMOUS)
         path = ICanHasLinkedBranch(distro_package).bzr_path
-        requiredMessage = (
-            "The target %s does not have a linked branch." % path)
+        requiredMessage = "The target %s does not have a linked branch." % path
         self.assertDisplaysNotice(path, requiredMessage)
 
     def test_trailing_path_redirect(self):
         # If there are any trailing path segments after the branch identifier,
         # these stick around at the redirected URL.
         branch = self.factory.makeAnyBranch()
-        path = urlappend(branch.unique_name, '+edit')
-        self.assertRedirects(path, canonical_url(branch, view_name='+edit'))
+        path = urlappend(branch.unique_name, "+edit")
+        self.assertRedirects(path, canonical_url(branch, view_name="+edit"))
 
     def test_alias_trailing_path_redirect(self):
         # Redirects also support trailing path segments with aliases.
         branch = self.factory.makeProductBranch()
         with person_logged_in(branch.product.owner):
             branch.product.development_focus.branch = branch
-        path = '%s/+edit' % branch.product.name
-        self.assertRedirects(path, canonical_url(branch, view_name='+edit'))
+        path = "%s/+edit" % branch.product.name
+        self.assertRedirects(path, canonical_url(branch, view_name="+edit"))
 
     def test_product_series_redirect(self):
         # Traversing to /+branch/<product>/<series> redirects to the branch
@@ -302,27 +303,27 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         branch = self.factory.makeBranch()
         series = self.factory.makeProductSeries(branch=branch)
         self.assertRedirects(
-            ICanHasLinkedBranch(series).bzr_path, canonical_url(branch))
+            ICanHasLinkedBranch(series).bzr_path, canonical_url(branch)
+        )
 
     def test_no_branch_for_series(self):
         # If there's no branch for a product series, display a
         # message telling the user there is no linked branch.
         series = self.factory.makeProductSeries()
         path = ICanHasLinkedBranch(series).bzr_path
-        requiredMessage = (
-            "The target %s does not have a linked branch." % path)
+        requiredMessage = "The target %s does not have a linked branch." % path
         self.assertDisplaysNotice(path, requiredMessage)
 
     def test_private_branch_for_series(self):
         # If the development focus of a product series is private, display a
         # message telling the user there is no linked branch.
         branch = self.factory.makeBranch(
-            information_type=InformationType.USERDATA)
+            information_type=InformationType.USERDATA
+        )
         series = self.factory.makeProductSeries(branch=branch)
         login(ANONYMOUS)
         path = ICanHasLinkedBranch(series).bzr_path
-        requiredMessage = (
-            "The target %s does not have a linked branch." % path)
+        requiredMessage = "The target %s does not have a linked branch." % path
         self.assertDisplaysNotice(path, requiredMessage)
 
     def test_too_short_branch_name(self):
@@ -330,13 +331,14 @@ class TestBranchTraversal(TestCaseWithFactory, TraversalMixin):
         # that's too short to be a real unique name.
         owner = self.factory.makePerson()
         requiredMessage = html_escape(
-            "Cannot understand namespace name: '%s'" % owner.name)
-        self.assertDisplaysError('~%s' % owner.name, requiredMessage)
+            "Cannot understand namespace name: '%s'" % owner.name
+        )
+        self.assertDisplaysError("~%s" % owner.name, requiredMessage)
 
     def test_invalid_product_name(self):
         # error notification if the thing following +branch has an invalid
         # product name.
-        self.assertDisplaysError('_foo', "Invalid name for product: _foo.")
+        self.assertDisplaysError("_foo", "Invalid name for product: _foo.")
 
     def test_invalid_product_name_without_referer(self):
         # error notification if the thing following +branch has an invalid
@@ -349,42 +351,48 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
     layer = DatabaseFunctionalLayer
 
     def traverse(self, path, **kwargs):
-        return super().traverse(path, '+code', **kwargs)
+        return super().traverse(path, "+code", **kwargs)
 
     def test_project_bzr_branch(self):
         branch = self.factory.makeAnyBranch()
         self.assertRedirects(branch.unique_name, canonical_url(branch))
         self.assertRedirects(
-            branch.unique_name, canonical_url(branch, rootsite='api'),
-            webservice=True)
+            branch.unique_name,
+            canonical_url(branch, rootsite="api"),
+            webservice=True,
+        )
 
     def test_project_git_branch(self):
         git_repo = self.factory.makeGitRepository()
         self.assertRedirects(git_repo.unique_name, canonical_url(git_repo))
         self.assertRedirects(
-            git_repo.unique_name, canonical_url(git_repo, rootsite='api'),
-            webservice=True)
+            git_repo.unique_name,
+            canonical_url(git_repo, rootsite="api"),
+            webservice=True,
+        )
 
     def test_no_such_bzr_unique_name(self):
         branch = self.factory.makeAnyBranch()
-        bad_name = branch.unique_name + 'wibble'
+        bad_name = branch.unique_name + "wibble"
         self.assertNotFound(bad_name)
 
     def test_no_such_git_unique_name(self):
         repo = self.factory.makeGitRepository()
-        bad_name = repo.unique_name + 'wibble'
+        bad_name = repo.unique_name + "wibble"
         self.assertNotFound(bad_name)
 
     def test_private_bzr_branch(self):
         branch = self.factory.makeProductBranch(
-            information_type=InformationType.USERDATA)
+            information_type=InformationType.USERDATA
+        )
         branch_unique_name = removeSecurityProxy(branch).unique_name
         login(ANONYMOUS)
         self.assertNotFound(branch_unique_name)
 
     def test_private_git_branch(self):
         git_repo = self.factory.makeGitRepository(
-            information_type=InformationType.USERDATA)
+            information_type=InformationType.USERDATA
+        )
         repo_unique_name = removeSecurityProxy(git_repo).unique_name
         login(ANONYMOUS)
         self.assertNotFound(repo_unique_name)
@@ -401,15 +409,15 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         naked_project = removeSecurityProxy(project)
         with person_logged_in(repo.target.owner):
             getUtility(IGitRepositorySet).setDefaultRepository(
-                repo.target, repo)
+                repo.target, repo
+            )
         self.assertRedirects(naked_project.name, canonical_url(repo))
 
     def test_private_bzr_branch_for_product(self):
         branch = self.factory.makeProductBranch()
         naked_product = removeSecurityProxy(branch.product)
         ICanHasLinkedBranch(naked_product).setBranch(branch)
-        removeSecurityProxy(branch).information_type = (
-            InformationType.USERDATA)
+        removeSecurityProxy(branch).information_type = InformationType.USERDATA
         login(ANONYMOUS)
         self.assertNotFound(naked_product.name)
 
@@ -418,17 +426,17 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         repo = self.factory.makeGitRepository(target=project)
         with person_logged_in(repo.target.owner):
             getUtility(IGitRepositorySet).setDefaultRepository(
-                repo.target, repo)
+                repo.target, repo
+            )
 
-        removeSecurityProxy(repo).information_type = (
-            InformationType.USERDATA)
+        removeSecurityProxy(repo).information_type = InformationType.USERDATA
         login(ANONYMOUS)
 
         naked_project = removeSecurityProxy(project)
         self.assertNotFound(naked_project.name)
 
     def test_nonexistent_product(self):
-        non_existent = 'non-existent'
+        non_existent = "non-existent"
         self.assertNotFound(non_existent)
 
     def test_product_without_dev_focus(self):
@@ -452,7 +460,8 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
 
         with admin_logged_in():
             getUtility(IGitRepositorySet).setDefaultRepository(
-                distro_package, repo)
+                distro_package, repo
+            )
 
         self.assertRedirects("%s" % repo.shortened_path, canonical_url(repo))
 
@@ -460,7 +469,8 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         sourcepackage = self.factory.makeSourcePackage()
         branch = self.factory.makePackageBranch(
             sourcepackage=sourcepackage,
-            information_type=InformationType.USERDATA)
+            information_type=InformationType.USERDATA,
+        )
         distro_package = sourcepackage.distribution_sourcepackage
         registrant = distro_package.distribution.owner
         with person_logged_in(registrant):
@@ -473,46 +483,47 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         sourcepackage = self.factory.makeSourcePackage()
         distro_package = sourcepackage.distribution_sourcepackage
         repo = self.factory.makeGitRepository(
-            target=distro_package,
-            information_type=InformationType.USERDATA)
+            target=distro_package, information_type=InformationType.USERDATA
+        )
         with admin_logged_in():
             getUtility(IGitRepositorySet).setDefaultRepository(
-                distro_package, repo)
+                distro_package, repo
+            )
         login(ANONYMOUS)
         path = removeSecurityProxy(repo).shortened_path
         self.assertNotFound(path)
 
     def test_trailing_path_redirect_bzr(self):
         branch = self.factory.makeAnyBranch()
-        path = urlappend(branch.unique_name, '+edit')
-        self.assertRedirects(path, canonical_url(branch, view_name='+edit'))
+        path = urlappend(branch.unique_name, "+edit")
+        self.assertRedirects(path, canonical_url(branch, view_name="+edit"))
 
     def test_trailing_path_redirect_git(self):
         repo = self.factory.makeGitRepository()
-        path = urlappend(repo.unique_name, '+edit')
-        self.assertRedirects(path, canonical_url(repo, view_name='+edit'))
+        path = urlappend(repo.unique_name, "+edit")
+        self.assertRedirects(path, canonical_url(repo, view_name="+edit"))
 
     def test_alias_trailing_path_redirect_bzr(self):
         branch = self.factory.makeProductBranch()
         with person_logged_in(branch.product.owner):
             branch.product.development_focus.branch = branch
-        path = '%s/+edit' % branch.product.name
-        self.assertRedirects(path, canonical_url(branch, view_name='+edit'))
+        path = "%s/+edit" % branch.product.name
+        self.assertRedirects(path, canonical_url(branch, view_name="+edit"))
 
     def test_alias_trailing_path_redirect_git(self):
         project = self.factory.makeProduct()
         repo = self.factory.makeGitRepository(target=project)
         with admin_logged_in():
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                project, repo)
-        path = '%s/+edit' % project.name
-        self.assertRedirects(path, canonical_url(repo, view_name='+edit'))
+            getUtility(IGitRepositorySet).setDefaultRepository(project, repo)
+        path = "%s/+edit" % project.name
+        self.assertRedirects(path, canonical_url(repo, view_name="+edit"))
 
     def test_product_series_redirect_bzr(self):
         branch = self.factory.makeBranch()
         series = self.factory.makeProductSeries(branch=branch)
         self.assertRedirects(
-            ICanHasLinkedBranch(series).bzr_path, canonical_url(branch))
+            ICanHasLinkedBranch(series).bzr_path, canonical_url(branch)
+        )
 
     def test_no_branch_for_series(self):
         # If there's no branch for a product series, display a
@@ -525,7 +536,8 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         # If the development focus of a product series is private, display a
         # message telling the user there is no linked branch.
         branch = self.factory.makeBranch(
-            information_type=InformationType.USERDATA)
+            information_type=InformationType.USERDATA
+        )
         series = self.factory.makeProductSeries(branch=branch)
         login(ANONYMOUS)
         path = ICanHasLinkedBranch(series).bzr_path
@@ -533,10 +545,10 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
 
     def test_too_short_branch_name(self):
         owner = self.factory.makePerson()
-        self.assertNotFound('~%s' % owner.name)
+        self.assertNotFound("~%s" % owner.name)
 
     def test_invalid_product_name(self):
-        self.assertNotFound('_foo')
+        self.assertNotFound("_foo")
 
     def test_invalid_product_name_without_referer(self):
         self.assertNotFound("_foo", use_default_referer=False)
@@ -556,8 +568,7 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         repo = self.factory.makeGitRepository(target=project)
         with person_logged_in(project.owner):
             ICanHasLinkedBranch(project).setBranch(bzr_branch, project.owner)
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                project, repo)
+            getUtility(IGitRepositorySet).setDefaultRepository(project, repo)
 
         self.assertNotFound(project.name)
 
@@ -567,8 +578,7 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         repo = self.factory.makeGitRepository(target=project)
         with person_logged_in(project.owner):
             ICanHasLinkedBranch(project).setBranch(bzr_branch, project.owner)
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                project, repo)
+            getUtility(IGitRepositorySet).setDefaultRepository(project, repo)
             project.vcs = VCSType.GIT
 
         self.assertRedirects(project.name, canonical_url(repo))
@@ -579,8 +589,7 @@ class TestCodeTraversal(TestCaseWithFactory, TraversalMixin):
         repo = self.factory.makeGitRepository(target=project)
         with person_logged_in(project.owner):
             ICanHasLinkedBranch(project).setBranch(bzr_branch, project.owner)
-            getUtility(IGitRepositorySet).setDefaultRepository(
-                project, repo)
+            getUtility(IGitRepositorySet).setDefaultRepository(project, repo)
             project.vcs = VCSType.BZR
 
         self.assertRedirects(project.name, canonical_url(bzr_branch))
@@ -603,7 +612,7 @@ class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
     def setUp(self):
         super().setUp()
         self.any_user = self.factory.makePerson()
-        self.admin = getUtility(IPersonSet).getByName('name16')
+        self.admin = getUtility(IPersonSet).getByName("name16")
         self.registry_expert = self.factory.makePerson()
         registry = getUtility(ILaunchpadCelebrities).registry_experts
         with person_logged_in(registry.teamowner):
@@ -611,19 +620,19 @@ class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
 
     def test_person(self):
         # Verify a user is returned.
-        name = 'active-person'
+        name = "active-person"
         person = self.factory.makePerson(name=name)
-        segment = '~%s' % name
+        segment = "~%s" % name
         traversed = self.traverse(segment, segment)
         self.assertEqual(person, traversed)
 
     def test_suspended_person_visibility(self):
         # Verify a suspended user is only traversable by an admin.
-        name = 'suspended-person'
+        name = "suspended-person"
         person = self.factory.makePerson(name=name)
         login_person(self.admin)
-        person.setAccountStatus(AccountStatus.SUSPENDED, None, 'Go away')
-        segment = '~%s' % name
+        person.setAccountStatus(AccountStatus.SUSPENDED, None, "Go away")
+        segment = "~%s" % name
         # Admins can see the suspended user.
         traversed = self.traverse(segment, segment)
         self.assertEqual(person, traversed)
@@ -636,10 +645,10 @@ class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
 
     def test_placeholder_person_visibility(self):
         # Verify a placeholder user is only traversable by an admin.
-        name = 'placeholder-person'
+        name = "placeholder-person"
         person = getUtility(IPersonSet).createPlaceholderPerson(name, name)
         login_person(self.admin)
-        segment = '~%s' % name
+        segment = "~%s" % name
         # Admins can see the placeholder user.
         traversed = self.traverse(segment, segment)
         self.assertEqual(person, traversed)
@@ -653,19 +662,19 @@ class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
 
     def test_public_team(self):
         # Verify a public team is returned.
-        name = 'public-team'
+        name = "public-team"
         team = self.factory.makeTeam(name=name)
-        segment = '~%s' % name
+        segment = "~%s" % name
         traversed = self.traverse(segment, segment)
         self.assertEqual(team, traversed)
 
     def test_private_team_visible_to_admin_and_members_only(self):
         # Verify a private team is  team is returned.
-        name = 'private-team'
+        name = "private-team"
         team = self.factory.makeTeam(name=name)
         login_person(self.admin)
         team.visibility = PersonVisibility.PRIVATE
-        segment = '~%s' % name
+        segment = "~%s" % name
         # Admins can traverse to the team.
         traversed = self.traverse(segment, segment)
         self.assertEqual(team, traversed)
@@ -681,36 +690,33 @@ class TestPersonTraversal(TestCaseWithFactory, TraversalMixin):
         # Just /~/ expands to the current user.  (Bug 785800).
         person = self.factory.makePerson()
         login_person(person)
-        obj, view, req = test_traverse('http://launchpad.test/~')
+        obj, view, req = test_traverse("http://launchpad.test/~";)
         view = removeSecurityProxy(view)
-        self.assertEqual(
-            canonical_url(person),
-            view.target.rstrip('/'))
+        self.assertEqual(canonical_url(person), view.target.rstrip("/"))
 
     def test_self_url_not_logged_in(self):
         # /~/ when not logged in asks you to log in.
-        self.assertRaises(Unauthorized,
-            test_traverse, 'http://launchpad.test/~')
+        self.assertRaises(
+            Unauthorized, test_traverse, "http://launchpad.test/~";
+        )
 
     def test_self_url_pathinfo(self):
         # You can traverse below /~/.
         person = self.factory.makePerson()
         login_person(person)
-        obj, view, req = test_traverse('http://launchpad.test/~/+editsshkeys')
+        obj, view, req = test_traverse("http://launchpad.test/~/+editsshkeys";)
         view = removeSecurityProxy(view)
-        self.assertEqual(
-            canonical_url(person) + '/+editsshkeys',
-            view.target)
+        self.assertEqual(canonical_url(person) + "/+editsshkeys", view.target)
 
     def test_self_url_app_domain(self):
         # You can traverse below /~/.
         person = self.factory.makePerson()
         login_person(person)
-        obj, view, req = test_traverse('http://bugs.launchpad.test/~')
+        obj, view, req = test_traverse("http://bugs.launchpad.test/~";)
         view = removeSecurityProxy(view)
         self.assertEqual(
-            canonical_url(person, rootsite='bugs'),
-            view.target.rstrip('/'))
+            canonical_url(person, rootsite="bugs"), view.target.rstrip("/")
+        )
 
 
 class TestErrorViews(TestCaseWithFactory):
@@ -718,9 +724,9 @@ class TestErrorViews(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def test_GoneError(self):
-        error = GoneError('User is suspended')
-        view = create_view(error, 'index.html')
-        self.assertEqual('Error: Page gone', view.page_title)
+        error = GoneError("User is suspended")
+        view = create_view(error, "index.html")
+        self.assertEqual("Error: Page gone", view.page_title)
         self.assertEqual(410, view.request.response.getStatus())
 
 
@@ -729,17 +735,17 @@ class ExceptionHierarchyTestCase(TestCaseWithFactory):
     layer = FunctionalLayer
 
     def test_exception(self):
-        view = create_view(IndexError('test'), '+hierarchy')
+        view = create_view(IndexError("test"), "+hierarchy")
         view.request.traversed_objects = [getUtility(ILaunchpadRoot)]
         self.assertEqual([], view.objects)
 
     def test_zope_exception(self):
-        view = create_view(Unauthorized('test'), '+hierarchy')
+        view = create_view(Unauthorized("test"), "+hierarchy")
         view.request.traversed_objects = [getUtility(ILaunchpadRoot)]
         self.assertEqual([], view.objects)
 
     def test_launchapd_exception(self):
-        view = create_view(NotFound(None, 'test'), '+hierarchy')
+        view = create_view(NotFound(None, "test"), "+hierarchy")
         view.request.traversed_objects = [getUtility(ILaunchpadRoot)]
         self.assertEqual([], view.objects)
 
@@ -751,11 +757,11 @@ class TestIterViewRegistrations(TestCaseWithFactory):
     def test_iter_view_registrations(self):
         """iter_view_registrations provides only registrations of class."""
         macros = getMultiAdapter(
-            (object(), LaunchpadTestRequest()), name='+base-layout-macros')
-        names = {
-            reg.name for reg in iter_view_registrations(macros.__class__)}
-        self.assertIn('+base-layout-macros', names)
-        self.assertNotIn('+related-pages', names)
+            (object(), LaunchpadTestRequest()), name="+base-layout-macros"
+        )
+        names = {reg.name for reg in iter_view_registrations(macros.__class__)}
+        self.assertIn("+base-layout-macros", names)
+        self.assertNotIn("+related-pages", names)
 
 
 class TestProductTraversal(TestCaseWithFactory, TraversalMixin):
@@ -770,10 +776,12 @@ class TestProductTraversal(TestCaseWithFactory, TraversalMixin):
         self.proprietary_product_owner = self.factory.makePerson()
         self.active_proprietary_product = self.factory.makeProduct(
             owner=self.proprietary_product_owner,
-            information_type=InformationType.PROPRIETARY)
+            information_type=InformationType.PROPRIETARY,
+        )
         self.inactive_proprietary_product = self.factory.makeProduct(
             owner=self.proprietary_product_owner,
-            information_type=InformationType.PROPRIETARY)
+            information_type=InformationType.PROPRIETARY,
+        )
         removeSecurityProxy(self.inactive_proprietary_product).active = False
 
     def traverse_to_active_public_product(self):
@@ -798,11 +806,14 @@ class TestProductTraversal(TestCaseWithFactory, TraversalMixin):
             self.traverse_to_active_public_product()
             # Access to other products raises a NotFound error.
             self.assertRaises(
-                NotFound, self.traverse_to_inactive_public_product)
+                NotFound, self.traverse_to_inactive_public_product
+            )
             self.assertRaises(
-                NotFound, self.traverse_to_active_proprietary_product)
+                NotFound, self.traverse_to_active_proprietary_product
+            )
             self.assertRaises(
-                NotFound, self.traverse_to_inactive_proprietary_product)
+                NotFound, self.traverse_to_inactive_proprietary_product
+            )
 
     def test_access_for_ordinary_users(self):
         # Ordinary logged in users can see only public active products.
@@ -810,32 +821,41 @@ class TestProductTraversal(TestCaseWithFactory, TraversalMixin):
             self.traverse_to_active_public_product()
             # Access to other products raises a NotFound error.
             self.assertRaises(
-                NotFound, self.traverse_to_inactive_public_product)
+                NotFound, self.traverse_to_inactive_public_product
+            )
             self.assertRaises(
-                NotFound, self.traverse_to_active_proprietary_product)
+                NotFound, self.traverse_to_active_proprietary_product
+            )
             self.assertRaises(
-                NotFound, self.traverse_to_inactive_proprietary_product)
+                NotFound, self.traverse_to_inactive_proprietary_product
+            )
 
     def test_access_for_person_with_pillar_grant(self):
         # Persons with a policy grant for a proprietary product can
         # access this product, if it is active.
         user = self.factory.makePerson()
         with person_logged_in(self.proprietary_product_owner):
-            getUtility(IService, 'sharing').sharePillarInformation(
-                self.active_proprietary_product, user,
+            getUtility(IService, "sharing").sharePillarInformation(
+                self.active_proprietary_product,
+                user,
                 self.proprietary_product_owner,
-                {InformationType.PROPRIETARY: SharingPermission.ALL})
-            getUtility(IService, 'sharing').sharePillarInformation(
-                self.inactive_proprietary_product, user,
+                {InformationType.PROPRIETARY: SharingPermission.ALL},
+            )
+            getUtility(IService, "sharing").sharePillarInformation(
+                self.inactive_proprietary_product,
+                user,
                 self.proprietary_product_owner,
-                {InformationType.PROPRIETARY: SharingPermission.ALL})
+                {InformationType.PROPRIETARY: SharingPermission.ALL},
+            )
         with person_logged_in(user):
             self.traverse_to_active_public_product()
             self.assertRaises(
-                NotFound, self.traverse_to_inactive_public_product)
+                NotFound, self.traverse_to_inactive_public_product
+            )
             self.traverse_to_active_proprietary_product()
             self.assertRaises(
-                NotFound, self.traverse_to_inactive_proprietary_product)
+                NotFound, self.traverse_to_inactive_proprietary_product
+            )
 
     def test_access_for_persons_with_artifact_grant(self):
         # Persons with an artifact grant related to a private product
@@ -844,9 +864,11 @@ class TestProductTraversal(TestCaseWithFactory, TraversalMixin):
         with person_logged_in(self.proprietary_product_owner):
             bug = self.factory.makeBug(
                 target=self.active_proprietary_product,
-                information_type=InformationType.PROPRIETARY)
-            getUtility(IService, 'sharing').ensureAccessGrants(
-                [user], self.proprietary_product_owner, bugs=[bug])
+                information_type=InformationType.PROPRIETARY,
+            )
+            getUtility(IService, "sharing").ensureAccessGrants(
+                [user], self.proprietary_product_owner, bugs=[bug]
+            )
         with person_logged_in(user):
             self.traverse_to_active_proprietary_product()
 
@@ -859,8 +881,8 @@ class TestProductTraversal(TestCaseWithFactory, TraversalMixin):
     def test_access_for_persons_with_special_permissions(self):
         # Admins have access all products, including inactive and propretary
         # products.
-        with celebrity_logged_in('admin'):
+        with celebrity_logged_in("admin"):
             self.check_admin_access()
         # Commercial admins have access to all products.
-        with celebrity_logged_in('commercial_admin'):
+        with celebrity_logged_in("commercial_admin"):
             self.check_admin_access()
diff --git a/lib/lp/app/browser/tests/test_launchpadform.py b/lib/lp/app/browser/tests/test_launchpadform.py
index cb08991..56f7f91 100644
--- a/lib/lp/app/browser/tests/test_launchpadform.py
+++ b/lib/lp/app/browser/tests/test_launchpadform.py
@@ -3,49 +3,32 @@
 
 """Tests for the lp.app.browser.launchpadform module."""
 
-from os.path import (
-    dirname,
-    join,
-    )
+from os.path import dirname, join
 
-from lxml import html
 import simplejson
+from lxml import html
 from testtools.content import text_content
 from zope.browserpage import ViewPageTemplateFile
 from zope.formlib.form import action
 from zope.interface import Interface
-from zope.schema import (
-    Choice,
-    Text,
-    TextLine,
-    )
+from zope.schema import Choice, Text, TextLine
 from zope.schema.vocabulary import SimpleVocabulary
 
-from lp.app.browser.launchpadform import (
-    has_structured_doc,
-    LaunchpadFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, has_structured_doc
 from lp.services.config import config
 from lp.services.webapp.servers import LaunchpadTestRequest
-from lp.testing import (
-    test_tales,
-    TestCase,
-    TestCaseWithFactory,
-    )
-from lp.testing.layers import (
-    DatabaseFunctionalLayer,
-    FunctionalLayer,
-    )
+from lp.testing import TestCase, TestCaseWithFactory, test_tales
+from lp.testing.layers import DatabaseFunctionalLayer, FunctionalLayer
 
 
 class TestInterface(Interface):
     """Test interface for the view below."""
 
-    normal = Text(title='normal', description='plain text')
+    normal = Text(title="normal", description="plain text")
 
     structured = has_structured_doc(
-        Text(title='structured',
-             description='<strong>structured text</strong'))
+        Text(title="structured", description="<strong>structured text</strong")
+    )
 
 
 class TestView(LaunchpadFormView):
@@ -59,7 +42,7 @@ class TestHasStructuredDoc(TestCase):
     layer = FunctionalLayer
 
     def _widget_annotation(self, widget):
-        return widget.context.queryTaggedValue('has_structured_doc')
+        return widget.context.queryTaggedValue("has_structured_doc")
 
     def test_has_structured_doc_sets_attribute(self):
         # Test that has_structured_doc sets the field annotation.
@@ -81,18 +64,25 @@ class TestQueryTalesForHasStructuredDoc(TestCase):
         view = TestView(None, request)
         view.initialize()
         normal_widget, structured_widget = view.widgets
-        self.assertIs(None, test_tales(
-                'widget/query:has-structured-doc', widget=normal_widget))
-        self.assertTrue(test_tales(
-                'widget/query:has-structured-doc', widget=structured_widget))
+        self.assertIs(
+            None,
+            test_tales(
+                "widget/query:has-structured-doc", widget=normal_widget
+            ),
+        )
+        self.assertTrue(
+            test_tales(
+                "widget/query:has-structured-doc", widget=structured_widget
+            )
+        )
 
 
 class TestHelpLinksInterface(Interface):
     """Test interface for the view below."""
 
-    nickname = Text(title='nickname')
+    nickname = Text(title="nickname")
 
-    displayname = Text(title='displayname')
+    displayname = Text(title="displayname")
 
 
 class TestHelpLinksView(LaunchpadFormView):
@@ -102,12 +92,13 @@ class TestHelpLinksView(LaunchpadFormView):
 
     page_title = "TestHelpLinksView"
     template = ViewPageTemplateFile(
-        config.root + '/lib/lp/app/templates/generic-edit.pt')
+        config.root + "/lib/lp/app/templates/generic-edit.pt"
+    )
 
     help_links = {
         "nickname": "http://widget.example.com/name";,
         "displayname": "http://widget.example.com/displayname";,
-        }
+    }
 
 
 class TestHelpLinks(TestCaseWithFactory):
@@ -122,11 +113,12 @@ class TestHelpLinks(TestCaseWithFactory):
         view.initialize()
         nickname_widget, displayname_widget = view.widgets
         self.assertEqual(
-            "http://widget.example.com/name";,
-            nickname_widget.help_link)
+            "http://widget.example.com/name";, nickname_widget.help_link
+        )
         self.assertEqual(
             "http://widget.example.com/displayname";,
-            displayname_widget.help_link)
+            displayname_widget.help_link,
+        )
 
     def test_help_links_render(self):
         # The values in a view's help_links dictionary are rendered in the
@@ -138,25 +130,28 @@ class TestHelpLinks(TestCaseWithFactory):
         view.initialize()
         root = html.fromstring(view.render())
         [nickname_help_link] = root.cssselect(
-            "label[for$=nickname] ~ a[target=help]")
+            "label[for$=nickname] ~ a[target=help]"
+        )
         self.assertEqual(
-            "http://widget.example.com/name";,
-            nickname_help_link.get("href"))
+            "http://widget.example.com/name";, nickname_help_link.get("href")
+        )
         [displayname_help_link] = root.cssselect(
-            "label[for$=displayname] ~ a[target=help]")
+            "label[for$=displayname] ~ a[target=help]"
+        )
         self.assertEqual(
             "http://widget.example.com/displayname";,
-            displayname_help_link.get("href"))
+            displayname_help_link.get("href"),
+        )
 
 
 class TestWidgetDivInterface(Interface):
     """Test interface for the view below."""
 
-    single_line = TextLine(title='single_line')
-    multi_line = Text(title='multi_line')
+    single_line = TextLine(title="single_line")
+    multi_line = Text(title="multi_line")
     checkbox = Choice(
-        vocabulary=SimpleVocabulary.fromItems(
-            (('yes', True), ('no', False))))
+        vocabulary=SimpleVocabulary.fromItems((("yes", True), ("no", False)))
+    )
 
 
 class TestWidgetDivView(LaunchpadFormView):
@@ -164,7 +159,8 @@ class TestWidgetDivView(LaunchpadFormView):
 
     schema = TestWidgetDivInterface
     template = ViewPageTemplateFile(
-        join(dirname(__file__), "test-widget-div.pt"))
+        join(dirname(__file__), "test-widget-div.pt")
+    )
 
 
 class TestWidgetDiv(TestCase):
@@ -181,7 +177,8 @@ class TestWidgetDiv(TestCase):
         # All the widgets appear in the page.
         self.assertEqual(
             ["field.single_line", "field.multi_line", "field.checkbox"],
-            root.xpath("//@id"))
+            root.xpath("//@id"),
+        )
 
     def test_all_widgets_present_but_hidden(self):
         request = LaunchpadTestRequest()
@@ -194,28 +191,36 @@ class TestWidgetDiv(TestCase):
         root = html.fromstring(content)
         # All the widgets appear in the page as hidden inputs.
         self.assertEqual(
-            ["field.single_line", "hidden",
-             "field.multi_line", "hidden",
-             "field.checkbox", "hidden"],
-            root.xpath("//input/@id | //input/@type"))
+            [
+                "field.single_line",
+                "hidden",
+                "field.multi_line",
+                "hidden",
+                "field.checkbox",
+                "hidden",
+            ],
+            root.xpath("//input/@id | //input/@type"),
+        )
 
 
 class TestFormView(TestWidgetDivView):
     """A trivial view with an action and a validator which sets errors."""
-    @action('Test', name='test',
-        failure=LaunchpadFormView.ajax_failure_handler)
+
+    @action(
+        "Test", name="test", failure=LaunchpadFormView.ajax_failure_handler
+    )
     def test_action(self, action, data):
-        single_line_value = data['single_line']
-        if single_line_value == 'success':
+        single_line_value = data["single_line"]
+        if single_line_value == "success":
             return
         self.addError("An action error")
 
     def validate(self, data):
-        single_line_value = data['single_line']
-        if single_line_value != 'error':
+        single_line_value = data["single_line"]
+        if single_line_value != "error":
             return
-        self.setFieldError('single_line', 'An error occurred')
-        self.addError('A form error')
+        self.setFieldError("single_line", "An error occurred")
+        self.addError("A form error")
 
 
 class TestAjaxValidator(TestCase):
@@ -227,58 +232,66 @@ class TestAjaxValidator(TestCase):
 
     def test_ajax_failure_handler(self):
         # Validation errors are recorded properly.
-        extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+        extra = {"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}
         request = LaunchpadTestRequest(
-            method='POST',
-            form={
-                'field.actions.test': 'Test',
-                'field.single_line': 'error'},
-            **extra)
+            method="POST",
+            form={"field.actions.test": "Test", "field.single_line": "error"},
+            **extra,
+        )
         view = TestFormView({}, request)
         view.initialize()
         self.assertEqual(
-                {"error_summary": "There are 2 errors.",
-                 "errors": {"field.single_line": "An error occurred"},
-                 "form_wide_errors": ["A form error"]},
-            simplejson.loads(view.form_result))
+            {
+                "error_summary": "There are 2 errors.",
+                "errors": {"field.single_line": "An error occurred"},
+                "form_wide_errors": ["A form error"],
+            },
+            simplejson.loads(view.form_result),
+        )
 
     def test_non_ajax_failure_handler(self):
         # The ajax error handler is not run if the request is not ajax.
         request = LaunchpadTestRequest(
-            method='POST',
-            form={
-                'field.actions.test': 'Test',
-                'field.single_line': 'error'})
+            method="POST",
+            form={"field.actions.test": "Test", "field.single_line": "error"},
+        )
         view = TestFormView({}, request)
         view.initialize()
         self.assertIsNone(view.form_result)
 
     def test_ajax_action_success(self):
         # When there are no errors, form_result is None.
-        extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+        extra = {"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}
         request = LaunchpadTestRequest(
-            method='POST',
+            method="POST",
             form={
-                'field.actions.test': 'Test',
-                'field.single_line': 'success'},
-            **extra)
+                "field.actions.test": "Test",
+                "field.single_line": "success",
+            },
+            **extra,
+        )
         view = TestFormView({}, request)
         view.initialize()
         self.assertIsNone(view.form_result)
 
     def test_ajax_action_failure(self):
         # When there are errors performing the action, these are recorded.
-        extra = {'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
+        extra = {"HTTP_X_REQUESTED_WITH": "XMLHttpRequest"}
         request = LaunchpadTestRequest(
-            method='POST',
+            method="POST",
             form={
-                'field.actions.test': 'Test',
-                'field.single_line': 'failure'},
-            **extra)
+                "field.actions.test": "Test",
+                "field.single_line": "failure",
+            },
+            **extra,
+        )
         view = TestFormView({}, request)
         view.initialize()
         self.assertEqual(
-                {"error_summary": "There is 1 error.",
-                 "errors": {},
-                 "form_wide_errors": ["An action error"]},
-            simplejson.loads(view.form_result))
+            {
+                "error_summary": "There is 1 error.",
+                "errors": {},
+                "form_wide_errors": ["An action error"],
+            },
+            simplejson.loads(view.form_result),
+        )
diff --git a/lib/lp/app/browser/tests/test_launchpadform_doc.py b/lib/lp/app/browser/tests/test_launchpadform_doc.py
index caafbcb..a628ee2 100644
--- a/lib/lp/app/browser/tests/test_launchpadform_doc.py
+++ b/lib/lp/app/browser/tests/test_launchpadform_doc.py
@@ -4,21 +4,15 @@
 import doctest
 import unittest
 
-from zope.formlib.interfaces import (
-    IDisplayWidget,
-    IInputWidget,
-    )
-from zope.interface import (
-    directlyProvides,
-    implementer,
-    )
+from zope.formlib.interfaces import IDisplayWidget, IInputWidget
+from zope.interface import directlyProvides, implementer
 
 from lp.app.browser.launchpadform import LaunchpadFormView
 from lp.services.webapp.interfaces import (
     ICheckBoxWidgetLayout,
     IMultiLineWidgetLayout,
     ISingleLineWidgetLayout,
-    )
+)
 from lp.testing.layers import FunctionalLayer
 
 
@@ -38,7 +32,7 @@ class LaunchpadFormTest(unittest.TestCase):
             pass
 
         widget = FakeWidget()
-        form.widgets = {'widget': widget}
+        form.widgets = {"widget": widget}
         # test every combination of the three interfaces:
         for use_single_line in [False, True]:
             for use_multi_line in [False, True]:
@@ -54,16 +48,19 @@ class LaunchpadFormTest(unittest.TestCase):
 
                     # Now count how many of the is* functions return True:
                     count = 0
-                    if form.isSingleLineLayout('widget'):
+                    if form.isSingleLineLayout("widget"):
                         count += 1
-                    if form.isMultiLineLayout('widget'):
+                    if form.isMultiLineLayout("widget"):
                         count += 1
-                    if form.isCheckBoxLayout('widget'):
+                    if form.isCheckBoxLayout("widget"):
                         count += 1
 
-                    self.assertEqual(count, 1,
-                                     'Expected count of 1 for %r.  Got %d'
-                                     % (provides, count))
+                    self.assertEqual(
+                        count,
+                        1,
+                        "Expected count of 1 for %r.  Got %d"
+                        % (provides, count),
+                    )
 
     def test_showOptionalMarker(self):
         """Verify a field marked .for_display has no (Optional) marker."""
@@ -75,11 +72,11 @@ class LaunchpadFormTest(unittest.TestCase):
             def __init__(self, required):
                 self.required = required
 
-        form.widgets = {'widget': FakeInputWidget(required=False)}
-        self.assertTrue(form.showOptionalMarker('widget'))
+        form.widgets = {"widget": FakeInputWidget(required=False)}
+        self.assertTrue(form.showOptionalMarker("widget"))
         # Required IInputWidgets have no (Optional) marker.
-        form.widgets = {'widget': FakeInputWidget(required=True)}
-        self.assertFalse(form.showOptionalMarker('widget'))
+        form.widgets = {"widget": FakeInputWidget(required=True)}
+        self.assertFalse(form.showOptionalMarker("widget"))
 
         # IDisplayWidgets have no (Optional) marker, regardless of whether
         # they are required or not, since they are read only.
@@ -88,10 +85,10 @@ class LaunchpadFormTest(unittest.TestCase):
             def __init__(self, required):
                 self.required = required
 
-        form.widgets = {'widget': FakeDisplayWidget(required=False)}
-        self.assertFalse(form.showOptionalMarker('widget'))
-        form.widgets = {'widget': FakeDisplayWidget(required=True)}
-        self.assertFalse(form.showOptionalMarker('widget'))
+        form.widgets = {"widget": FakeDisplayWidget(required=False)}
+        self.assertFalse(form.showOptionalMarker("widget"))
+        form.widgets = {"widget": FakeDisplayWidget(required=True)}
+        self.assertFalse(form.showOptionalMarker("widget"))
 
 
 def doctest_custom_widget_with_setUpFields_override():
@@ -139,7 +136,9 @@ def doctest_custom_widget_with_setUpFields_override():
 
 
 def test_suite():
-    return unittest.TestSuite((
-        unittest.TestLoader().loadTestsFromName(__name__),
-        doctest.DocTestSuite()
-        ))
+    return unittest.TestSuite(
+        (
+            unittest.TestLoader().loadTestsFromName(__name__),
+            doctest.DocTestSuite(),
+        )
+    )
diff --git a/lib/lp/app/browser/tests/test_launchpadroot.py b/lib/lp/app/browser/tests/test_launchpadroot.py
index 5c3b1d7..c5e5329 100644
--- a/lib/lp/app/browser/tests/test_launchpadroot.py
+++ b/lib/lp/app/browser/tests/test_launchpadroot.py
@@ -10,43 +10,34 @@ from zope.security.checker import selectChecker
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.pillar import IPillarNameSet
-from lp.services.beautifulsoup import (
-    BeautifulSoup,
-    SoupStrainer,
-    )
+from lp.services.beautifulsoup import BeautifulSoup, SoupStrainer
 from lp.services.config import config
 from lp.services.features.testing import FeatureFixture
 from lp.services.memcache.interfaces import IMemcacheClient
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.interfaces import ILaunchpadRoot
 from lp.testing import (
+    TestCaseWithFactory,
     anonymous_logged_in,
     login_admin,
     login_person,
     record_two_runs,
-    TestCaseWithFactory,
-    )
-from lp.testing.layers import (
-    DatabaseFunctionalLayer,
-    LaunchpadFunctionalLayer,
-    )
+)
+from lp.testing.layers import DatabaseFunctionalLayer, LaunchpadFunctionalLayer
 from lp.testing.matchers import HasQueryCount
 from lp.testing.publication import test_traverse
-from lp.testing.views import (
-    create_initialized_view,
-    create_view,
-    )
+from lp.testing.views import create_initialized_view, create_view
 
 
 class LaunchpadRootPermissionTest(TestCaseWithFactory):
     """Test for the ILaunchpadRoot permission"""
+
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
         TestCaseWithFactory.setUp(self)
         self.root = getUtility(ILaunchpadRoot)
-        self.admin = getUtility(IPersonSet).getByEmail(
-            'foo.bar@xxxxxxxxxxxxx')
+        self.admin = getUtility(IPersonSet).getByEmail("foo.bar@xxxxxxxxxxxxx")
         # Use a FakeLogger fixture to prevent Memcached warnings to be
         # printed to stdout while browsing pages.
         self.useFixture(FakeLogger())
@@ -56,44 +47,55 @@ class LaunchpadRootPermissionTest(TestCaseWithFactory):
         login_person(self.admin)
         self.expert = self.factory.makePerson()
         getUtility(ILaunchpadCelebrities).registry_experts.addMember(
-            self.expert, self.admin)
+            self.expert, self.admin
+        )
         login_person(self.expert)
 
     def test_anonymous_cannot_edit(self):
-        self.assertFalse(check_permission('launchpad.Edit', self.root),
-            "Anonymous user shouldn't have launchpad.Edit on ILaunchpadRoot")
+        self.assertFalse(
+            check_permission("launchpad.Edit", self.root),
+            "Anonymous user shouldn't have launchpad.Edit on ILaunchpadRoot",
+        )
 
     def test_regular_user_cannot_edit(self):
         login_person(self.factory.makePerson())
-        self.assertFalse(check_permission('launchpad.Edit', self.root),
-            "Regular users shouldn't have launchpad.Edit on ILaunchpadRoot")
+        self.assertFalse(
+            check_permission("launchpad.Edit", self.root),
+            "Regular users shouldn't have launchpad.Edit on ILaunchpadRoot",
+        )
 
     def test_registry_expert_can_edit(self):
         self.setUpRegistryExpert()
-        self.assertTrue(check_permission('launchpad.Edit', self.root),
-            "Registry experts should have launchpad.Edit on ILaunchpadRoot")
+        self.assertTrue(
+            check_permission("launchpad.Edit", self.root),
+            "Registry experts should have launchpad.Edit on ILaunchpadRoot",
+        )
 
     def test_admins_can_edit(self):
         login_person(self.admin)
-        self.assertTrue(check_permission('launchpad.Edit', self.root),
-            "Admins should have launchpad.Edit on ILaunchpadRoot")
+        self.assertTrue(
+            check_permission("launchpad.Edit", self.root),
+            "Admins should have launchpad.Edit on ILaunchpadRoot",
+        )
 
     def test_featured_projects_view_requires_edit(self):
-        view = create_view(self.root, '+featuredprojects')
+        view = create_view(self.root, "+featuredprojects")
         checker = selectChecker(view)
-        self.assertEqual('launchpad.Edit', checker.permission_id('__call__'))
+        self.assertEqual("launchpad.Edit", checker.permission_id("__call__"))
 
     def test_featured_projects_manage_link_requires_edit(self):
         self.setUpRegistryExpert()
         view = create_initialized_view(
-            self.root, 'index.html', principal=self.expert)
+            self.root, "index.html", principal=self.expert
+        )
         # Stub out the getRecentBlogPosts which fetches a blog feed using
         # urlfetch.
         view.getRecentBlogPosts = lambda: []
-        content = BeautifulSoup(view(), parse_only=SoupStrainer('a'))
+        content = BeautifulSoup(view(), parse_only=SoupStrainer("a"))
         self.assertTrue(
-            content.find('a', href='+featuredprojects'),
-            "Cannot find the +featuredprojects link on the first page")
+            content.find("a", href="+featuredprojects"),
+            "Cannot find the +featuredprojects link on the first page",
+        )
 
 
 class TestLaunchpadRootNavigation(TestCaseWithFactory):
@@ -103,23 +105,25 @@ class TestLaunchpadRootNavigation(TestCaseWithFactory):
 
     def test_support(self):
         # The /support link redirects to answers.
-        context, view, request = test_traverse(
-            'http://launchpad.test/support')
+        context, view, request = test_traverse("http://launchpad.test/support";)
         view()
         self.assertEqual(301, request.response.getStatus())
         self.assertEqual(
-            'http://answers.launchpad.test/launchpad',
-            request.response.getHeader('location'))
+            "http://answers.launchpad.test/launchpad";,
+            request.response.getHeader("location"),
+        )
 
     def test_feedback(self):
         # The /feedback link redirects to the help site.
         context, view, request = test_traverse(
-            'http://launchpad.test/feedback')
+            "http://launchpad.test/feedback";
+        )
         view()
         self.assertEqual(301, request.response.getStatus())
         self.assertEqual(
-            'https://help.launchpad.net/Feedback',
-            request.response.getHeader('location'))
+            "https://help.launchpad.net/Feedback";,
+            request.response.getHeader("location"),
+        )
 
 
 class LaunchpadRootIndexViewTestCase(TestCaseWithFactory):
@@ -136,94 +140,95 @@ class LaunchpadRootIndexViewTestCase(TestCaseWithFactory):
         root = getUtility(ILaunchpadRoot)
         user = self.factory.makePerson()
         login_person(user)
-        view = create_initialized_view(root, 'index.html', principal=user)
+        view = create_initialized_view(root, "index.html", principal=user)
         # Replace the blog posts so the view does not make a network request.
         view.getRecentBlogPosts = lambda: []
-        markup = BeautifulSoup(view(), parse_only=SoupStrainer(id='document'))
+        markup = BeautifulSoup(view(), parse_only=SoupStrainer(id="document"))
         self.assertIs(False, view.has_watermark)
-        self.assertIs(None, markup.find(True, id='watermark'))
-        logo = markup.find(True, id='launchpad-logo-and-name')
+        self.assertIs(None, markup.find(True, id="watermark"))
+        logo = markup.find(True, id="launchpad-logo-and-name")
         self.assertIsNot(None, logo)
-        self.assertEqual('/@@/launchpad-logo-and-name.png', logo['src'])
+        self.assertEqual("/@@/launchpad-logo-and-name.png", logo["src"])
 
     @staticmethod
     def _make_blog_post(linkid, title, body, date):
         return {
-            'title': title,
-            'description': body,
-            'link': "http://blog.invalid/%s"; % (linkid,),
-            'date': date,
-            }
+            "title": title,
+            "description": body,
+            "link": "http://blog.invalid/%s"; % (linkid,),
+            "date": date,
+        }
 
     def test_blog_posts(self):
         """Posts from the launchpad blog are shown when feature is enabled"""
-        self.useFixture(FeatureFixture({'app.root_blog.enabled': True}))
+        self.useFixture(FeatureFixture({"app.root_blog.enabled": True}))
         posts = [
             self._make_blog_post(1, "A post", "Post contents.", "2002"),
             self._make_blog_post(2, "Another post", "More contents.", "2003"),
-            ]
+        ]
         calls = []
 
         def _get_blog_posts():
-            calls.append('called')
+            calls.append("called")
             return posts
 
         root = getUtility(ILaunchpadRoot)
         with anonymous_logged_in():
-            view = create_initialized_view(root, 'index.html')
+            view = create_initialized_view(root, "index.html")
             view.getRecentBlogPosts = _get_blog_posts
             result = view()
         markup = BeautifulSoup(
-            result, parse_only=SoupStrainer(id='homepage-blogposts'))
-        self.assertEqual(['called'], calls)
-        items = markup.find_all('li', 'news')
+            result, parse_only=SoupStrainer(id="homepage-blogposts")
+        )
+        self.assertEqual(["called"], calls)
+        items = markup.find_all("li", "news")
         # Notice about launchpad being opened is always added at the end
         self.assertEqual(3, len(items))
         a = items[-1].find("a")
         self.assertEqual("Launchpad now open source", a.string.strip())
         for post, item in zip(posts, items):
             a = item.find("a")
-            self.assertEqual(post['link'], a["href"])
-            self.assertEqual(post['title'], a.string)
+            self.assertEqual(post["link"], a["href"])
+            self.assertEqual(post["title"], a.string)
 
     def test_blog_disabled(self):
         """Launchpad blog not queried for display without feature"""
         calls = []
 
         def _get_blog_posts():
-            calls.append('called')
+            calls.append("called")
             return []
 
         root = getUtility(ILaunchpadRoot)
         user = self.factory.makePerson()
         login_person(user)
-        view = create_initialized_view(root, 'index.html', principal=user)
+        view = create_initialized_view(root, "index.html", principal=user)
         view.getRecentBlogPosts = _get_blog_posts
-        markup = BeautifulSoup(
-            view(), parse_only=SoupStrainer(id='homepage'))
+        markup = BeautifulSoup(view(), parse_only=SoupStrainer(id="homepage"))
         self.assertEqual([], calls)
-        self.assertIs(None, markup.find(True, id='homepage-blogposts'))
+        self.assertIs(None, markup.find(True, id="homepage-blogposts"))
         # Even logged in users should get the launchpad intro text in the left
         # column rather than blank space when the blog is not being displayed.
         self.assertTrue(view.show_whatslaunchpad)
-        self.assertTrue(markup.find(True, 'homepage-whatslaunchpad'))
+        self.assertTrue(markup.find(True, "homepage-whatslaunchpad"))
 
     def test_blog_posts_with_memcache(self):
-        self.useFixture(FeatureFixture({'app.root_blog.enabled': True}))
+        self.useFixture(FeatureFixture({"app.root_blog.enabled": True}))
         posts = [
             self._make_blog_post(1, "A post", "Post contents.", "2002"),
             self._make_blog_post(2, "Another post", "More contents.", "2003"),
-            ]
-        key = '%s:homepage-blog-posts' % config.instance_name
+        ]
+        key = "%s:homepage-blog-posts" % config.instance_name
         getUtility(IMemcacheClient).set(key, posts)
 
         root = getUtility(ILaunchpadRoot)
         with anonymous_logged_in():
-            view = create_initialized_view(root, 'index.html')
+            view = create_initialized_view(root, "index.html")
             result = view()
         markup = BeautifulSoup(
-            result, parse_only=SoupStrainer(id='homepage-blogposts'))
-        items = markup.find_all('li', 'news')
+            result, parse_only=SoupStrainer(id="homepage-blogposts")
+        )
+        items = markup.find_all("li", "news")
         self.assertEqual(3, len(items))
 
     def test_featured_projects_query_count(self):
@@ -239,6 +244,10 @@ class LaunchpadRootIndexViewTestCase(TestCaseWithFactory):
         user = self.factory.makePerson()
         recorder1, recorder2 = record_two_runs(
             lambda: create_initialized_view(
-                root, 'index.html', principal=user)(),
-            add_featured_projects, 5, login_method=login_admin)
+                root, "index.html", principal=user
+            )(),
+            add_featured_projects,
+            5,
+            login_method=login_admin,
+        )
         self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
diff --git a/lib/lp/app/browser/tests/test_linkchecker.py b/lib/lp/app/browser/tests/test_linkchecker.py
index 2781e2a..9393a2e 100644
--- a/lib/lp/app/browser/tests/test_linkchecker.py
+++ b/lib/lp/app/browser/tests/test_linkchecker.py
@@ -19,17 +19,17 @@ class TestLinkCheckerAPI(TestCaseWithFactory):
 
     layer = DatabaseFunctionalLayer
 
-    BRANCH_URL_TEMPLATE = '/+code/%s'
+    BRANCH_URL_TEMPLATE = "/+code/%s"
 
     def check_invalid_links(self, result_json, link_type, invalid_links):
         link_dict = simplejson.loads(result_json)
-        links_to_check = link_dict[link_type]['invalid']
+        links_to_check = link_dict[link_type]["invalid"]
         self.assertEqual(len(invalid_links), len(links_to_check))
         self.assertEqual(set(invalid_links), set(links_to_check))
 
     def check_valid_links(self, result_json, link_type, valid_links):
         link_dict = simplejson.loads(result_json)
-        links_to_check = link_dict[link_type]['valid']
+        links_to_check = link_dict[link_type]["valid"]
         self.assertEqual(len(valid_links), len(links_to_check))
         self.assertEqual(set(valid_links), set(links_to_check))
 
@@ -43,13 +43,16 @@ class TestLinkCheckerAPI(TestCaseWithFactory):
 
         # git branches are a thing now as well!
         project_git_repo = removeSecurityProxy(
-            self.factory.makeGitRepository(target=product))
+            self.factory.makeGitRepository(target=product)
+        )
         dsp = self.factory.makeDistributionSourcePackage()
         dsp_git_repo = removeSecurityProxy(
-            self.factory.makeGitRepository(target=dsp))
+            self.factory.makeGitRepository(target=dsp)
+        )
         person = self.factory.makePerson()
         person_git_repo = removeSecurityProxy(
-            self.factory.makeGitRepository(target=person))
+            self.factory.makeGitRepository(target=person)
+        )
 
         return [
             valid_branch_url,
@@ -61,18 +64,15 @@ class TestLinkCheckerAPI(TestCaseWithFactory):
 
     def make_invalid_branch_links(self):
         return [
-            self.BRANCH_URL_TEMPLATE % 'foo',
-            self.BRANCH_URL_TEMPLATE % 'bar',
-            ]
+            self.BRANCH_URL_TEMPLATE % "foo",
+            self.BRANCH_URL_TEMPLATE % "bar",
+        ]
 
     def make_valid_bug_links(self):
         bug1 = self.factory.makeBug()
         bug2 = self.factory.makeBug()
         self.factory.makeBugTask(bug=bug2)
-        return [
-            '/bugs/%d' % (bug1.id),
-            '/bugs/%d' % (bug2.id)
-            ]
+        return ["/bugs/%d" % (bug1.id), "/bugs/%d" % (bug2.id)]
 
     def make_invalid_bug_links(self):
         """
@@ -80,12 +80,17 @@ class TestLinkCheckerAPI(TestCaseWithFactory):
         currently authenticated user
         """
         bug_private = self.factory.makeBug(
-            information_type=InformationType.USERDATA)
-        return ['/bugs/%d' % (bug_private.id)]
+            information_type=InformationType.USERDATA
+        )
+        return ["/bugs/%d" % (bug_private.id)]
 
     def invoke_link_checker(
-        self, valid_branch_urls=None, invalid_branch_urls=None,
-        valid_bug_urls=None, invalid_bug_urls=None):
+        self,
+        valid_branch_urls=None,
+        invalid_branch_urls=None,
+        valid_bug_urls=None,
+        invalid_bug_urls=None,
+    ):
         if valid_branch_urls is None:
             valid_branch_urls = {}
         if invalid_branch_urls is None:
@@ -110,11 +115,10 @@ class TestLinkCheckerAPI(TestCaseWithFactory):
         link_checker = LinkCheckerAPI(object(), request)
         result_json = link_checker()
         self.check_invalid_links(
-            result_json, 'branch_links', invalid_branch_urls)
-        self.check_invalid_links(
-            result_json, 'bug_links', invalid_bug_urls)
-        self.check_valid_links(
-            result_json, 'bug_links', valid_bug_urls)
+            result_json, "branch_links", invalid_branch_urls
+        )
+        self.check_invalid_links(result_json, "bug_links", invalid_bug_urls)
+        self.check_valid_links(result_json, "bug_links", valid_bug_urls)
 
     def test_with_no_data(self):
         request = LaunchpadTestRequest()
@@ -127,13 +131,15 @@ class TestLinkCheckerAPI(TestCaseWithFactory):
         branch_urls = self.make_valid_branch_links()
         bug_urls = self.make_valid_bug_links()
         self.invoke_link_checker(
-            valid_branch_urls=branch_urls, valid_bug_urls=bug_urls)
+            valid_branch_urls=branch_urls, valid_bug_urls=bug_urls
+        )
 
     def test_only_invalid_links(self):
         branch_urls = self.make_invalid_branch_links()
         bug_urls = self.make_invalid_bug_links()
         self.invoke_link_checker(
-            invalid_branch_urls=branch_urls, invalid_bug_urls=bug_urls)
+            invalid_branch_urls=branch_urls, invalid_bug_urls=bug_urls
+        )
 
     def test_valid_and_invald_links(self):
         valid_branch_urls = self.make_valid_branch_links()
@@ -144,4 +150,5 @@ class TestLinkCheckerAPI(TestCaseWithFactory):
             valid_branch_urls=valid_branch_urls,
             invalid_branch_urls=invalid_branch_urls,
             valid_bug_urls=valid_bug_urls,
-            invalid_bug_urls=invalid_bug_urls)
+            invalid_bug_urls=invalid_bug_urls,
+        )
diff --git a/lib/lp/app/browser/tests/test_macro_view.py b/lib/lp/app/browser/tests/test_macro_view.py
index 0d26beb..dc97cbe 100644
--- a/lib/lp/app/browser/tests/test_macro_view.py
+++ b/lib/lp/app/browser/tests/test_macro_view.py
@@ -18,32 +18,32 @@ class TestMacroNontraversability(TestCaseWithFactory):
     # Names of some macros that are tested to ensure that they're not
     # accessable via URL.  This is not an exhaustive list.
     macro_names = (
-        'feed-entry-atom',
-        '+base-layout-macros',
-        '+main-template-macros',
-        'launchpad_form',
-        'launchpad_widget_macros',
-        '+forbidden-page-macros',
-        '+search-form',
+        "feed-entry-atom",
+        "+base-layout-macros",
+        "+main-template-macros",
+        "launchpad_form",
+        "launchpad_widget_macros",
+        "+forbidden-page-macros",
+        "+search-form",
         '+primary-search-form"',
-        'form-picker-macros',
-        '+filebug-macros',
-        '+bugtarget-macros-search',
-        'bugcomment-macros',
-        'bug-attachment-macros',
-        '+portlet-malone-bugmail-filtering-faq',
-        '+bugtask-macros-tableview',
-        'bugtask-macros-cve',
-        '+bmp-macros',
-        '+announcement-macros',
-        '+person-macros',
-        '+milestone-macros',
-        '+distributionmirror-macros',
-        '+timeline-macros',
-        '+macros',
-        '+translations-macros',
-        '+object-reassignment',
-        '+team-bugs-macro',
+        "form-picker-macros",
+        "+filebug-macros",
+        "+bugtarget-macros-search",
+        "bugcomment-macros",
+        "bug-attachment-macros",
+        "+portlet-malone-bugmail-filtering-faq",
+        "+bugtask-macros-tableview",
+        "bugtask-macros-cve",
+        "+bmp-macros",
+        "+announcement-macros",
+        "+person-macros",
+        "+milestone-macros",
+        "+distributionmirror-macros",
+        "+timeline-macros",
+        "+macros",
+        "+translations-macros",
+        "+object-reassignment",
+        "+team-bugs-macro",
     )
 
     @staticmethod
@@ -51,6 +51,7 @@ class TestMacroNontraversability(TestCaseWithFactory):
         def traverse_and_call():
             view = test_traverse(path)[1]
             view()
+
         try:
             traverse_and_call()
         except NotFound:
@@ -60,5 +61,7 @@ class TestMacroNontraversability(TestCaseWithFactory):
 
     def test_macro_names_not_traversable(self):
         for name in self.macro_names:
-            self.assertTrue(self.is_not_found('http://launchpad.test/' + name),
-                'macro name %r should not be URL accessable' % name)
+            self.assertTrue(
+                self.is_not_found("http://launchpad.test/"; + name),
+                "macro name %r should not be URL accessable" % name,
+            )
diff --git a/lib/lp/app/browser/tests/test_mixed_visibility.py b/lib/lp/app/browser/tests/test_mixed_visibility.py
index ee7bcce..b0661f5 100644
--- a/lib/lp/app/browser/tests/test_mixed_visibility.py
+++ b/lib/lp/app/browser/tests/test_mixed_visibility.py
@@ -3,10 +3,7 @@
 
 from lp.app.browser.tales import TeamFormatterAPI
 from lp.registry.interfaces.person import PersonVisibility
-from lp.testing import (
-    person_logged_in,
-    TestCaseWithFactory,
-    )
+from lp.testing import TestCaseWithFactory, person_logged_in
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
@@ -21,7 +18,7 @@ class TestMixedVisibility(TestCaseWithFactory):
         viewer = self.factory.makePerson()
         with person_logged_in(viewer):
             self.assertEqual(
-                '<hidden>', TeamFormatterAPI(team).displayname(None))
+                "<hidden>", TeamFormatterAPI(team).displayname(None)
+            )
         self.assertEqual(1, len(self.oopses))
-        self.assertTrue(
-            'MixedVisibilityError' in self.oopses[0]['tb_text'])
+        self.assertTrue("MixedVisibilityError" in self.oopses[0]["tb_text"])
diff --git a/lib/lp/app/browser/tests/test_page_macro.py b/lib/lp/app/browser/tests/test_page_macro.py
index fb8f1fd..599bb17 100644
--- a/lib/lp/app/browser/tests/test_page_macro.py
+++ b/lib/lp/app/browser/tests/test_page_macro.py
@@ -13,15 +13,12 @@ from lp.app.interfaces.launchpad import IPrivacy
 from lp.app.security import AuthorizationBase
 from lp.testing import (
     FakeAdapterMixin,
-    login_person,
-    test_tales,
     TestCase,
     TestCaseWithFactory,
-    )
-from lp.testing.layers import (
-    DatabaseFunctionalLayer,
-    FunctionalLayer,
-    )
+    login_person,
+    test_tales,
+)
+from lp.testing.layers import DatabaseFunctionalLayer, FunctionalLayer
 from lp.testing.views import create_view
 
 
@@ -31,23 +28,20 @@ class ITest(IPrivacy):
 
 @implementer(ITest)
 class TestObject:
-
     def __init__(self):
         self.private = False
 
 
 class TestView:
-
     def __init__(self, context, request):
         self.context = context
         self.request = request
 
 
 class TestPageMacroDispatcherMixin(FakeAdapterMixin):
-
     def _setUpView(self):
-        self.registerBrowserViewAdapter(TestView, ITest, '+index')
-        self.view = create_view(TestObject(), name='+index')
+        self.registerBrowserViewAdapter(TestView, ITest, "+index")
+        self.view = create_view(TestObject(), name="+index")
 
     def _call_test_tales(self, path):
         test_tales(path, view=self.view)
@@ -60,6 +54,7 @@ class PageMacroDispatcherTestCase(TestPageMacroDispatcherMixin, TestCase):
     Templates should start by specifying the kind of pagetype they use.
     <html metal:use-macro="view/macro:page/main_side" />
     """
+
     layer = FunctionalLayer
 
     def setUp(self):
@@ -68,62 +63,75 @@ class PageMacroDispatcherTestCase(TestPageMacroDispatcherMixin, TestCase):
 
     def test_base_template(self):
         # Requests on the launchpad.test vhost use the Launchpad base template.
-        adapter = self.getAdapter([self.view], IPathAdapter, name='macro')
+        adapter = self.getAdapter([self.view], IPathAdapter, name="macro")
         template_path = os.path.normpath(adapter.base.filename)
-        self.assertIn('lp/app/templates', template_path)
+        self.assertIn("lp/app/templates", template_path)
         # The base template defines a 'master' macro as the adapter expects.
-        self.assertIn('master', adapter.base.macros.keys())
+        self.assertIn("master", adapter.base.macros.keys())
 
     def test_page(self):
         # A view can be adpated to a page macro object.
-        page_macro = test_tales('view/macro:page/main_side', view=self.view)
-        self.assertEqual('main_side', self.view.__pagetype__)
-        self.assertEqual(('mode', 'html'), page_macro[1])
+        page_macro = test_tales("view/macro:page/main_side", view=self.view)
+        self.assertEqual("main_side", self.view.__pagetype__)
+        self.assertEqual(("mode", "html"), page_macro[1])
         source_file = page_macro[3]
-        self.assertEqual('setSourceFile', source_file[0])
+        self.assertEqual("setSourceFile", source_file[0])
         self.assertEqual(
-            '/templates/base-layout.pt', source_file[1].split('..')[1])
+            "/templates/base-layout.pt", source_file[1].split("..")[1]
+        )
 
     def test_page_unknown_type(self):
         # An error is raised of the pagetype is not defined.
         self.assertRaisesWithContent(
-            LocationError, "'unknown pagetype: not-defined'",
-            self._call_test_tales, 'view/macro:page/not-defined')
+            LocationError,
+            "'unknown pagetype: not-defined'",
+            self._call_test_tales,
+            "view/macro:page/not-defined",
+        )
 
     def test_pagetype(self):
         # The pagetype is 'unset', until macro:page is called.
-        self.assertIs(None, getattr(self.view, '__pagetype__', None))
+        self.assertIs(None, getattr(self.view, "__pagetype__", None))
         self.assertEqual(
-            'unset', test_tales('view/macro:pagetype', view=self.view))
-        test_tales('view/macro:page/main_side', view=self.view)
-        self.assertEqual('main_side', self.view.__pagetype__)
+            "unset", test_tales("view/macro:pagetype", view=self.view)
+        )
+        test_tales("view/macro:page/main_side", view=self.view)
+        self.assertEqual("main_side", self.view.__pagetype__)
         self.assertEqual(
-            'main_side', test_tales('view/macro:pagetype', view=self.view))
+            "main_side", test_tales("view/macro:pagetype", view=self.view)
+        )
 
     def test_pagehas(self):
         # After the page type is set, the page macro can be queried
         # for what LayoutElements it supports supports.
-        test_tales('view/macro:page/main_side', view=self.view)
+        test_tales("view/macro:page/main_side", view=self.view)
         self.assertTrue(
-            test_tales('view/macro:pagehas/portlets', view=self.view))
+            test_tales("view/macro:pagehas/portlets", view=self.view)
+        )
 
     def test_pagehas_unset_pagetype(self):
         # The page macro type must be set before the page macro can be
         # queried for what LayoutElements it supports.
         self.assertRaisesWithContent(
-            KeyError, "'unset'",
-            self._call_test_tales, 'view/macro:pagehas/fnord')
+            KeyError,
+            "'unset'",
+            self._call_test_tales,
+            "view/macro:pagehas/fnord",
+        )
 
     def test_pagehas_unknown_attribute(self):
         # An error is raised if the LayoutElement does not exist.
-        test_tales('view/macro:page/main_side', view=self.view)
+        test_tales("view/macro:page/main_side", view=self.view)
         self.assertRaisesWithContent(
-            KeyError, "'fnord'",
-            self._call_test_tales, 'view/macro:pagehas/fnord')
+            KeyError,
+            "'fnord'",
+            self._call_test_tales,
+            "view/macro:pagehas/fnord",
+        )
 
     def test_has_watermark_default(self):
         # All pages have a watermark if the view does not provide the attr.
-        has_watermark = test_tales('view/macro:has-watermark', view=self.view)
+        has_watermark = test_tales("view/macro:has-watermark", view=self.view)
         self.assertIs(True, has_watermark)
 
     def test_has_watermark_false(self):
@@ -131,9 +139,9 @@ class PageMacroDispatcherTestCase(TestPageMacroDispatcherMixin, TestCase):
         class NoWatermarkView(TestView):
             has_watermark = False
 
-        self.registerBrowserViewAdapter(NoWatermarkView, ITest, '+test')
-        view = create_view(TestObject(), name='+test')
-        has_watermark = test_tales('view/macro:has-watermark', view=view)
+        self.registerBrowserViewAdapter(NoWatermarkView, ITest, "+test")
+        view = create_view(TestObject(), name="+test")
+        has_watermark = test_tales("view/macro:has-watermark", view=view)
         self.assertIs(False, has_watermark)
 
     def test_has_watermark_true(self):
@@ -141,14 +149,15 @@ class PageMacroDispatcherTestCase(TestPageMacroDispatcherMixin, TestCase):
         class NoWatermarkView(TestView):
             has_watermark = True
 
-        self.registerBrowserViewAdapter(NoWatermarkView, ITest, '+test')
-        view = create_view(TestObject(), name='+test')
-        has_watermark = test_tales('view/macro:has-watermark', view=view)
+        self.registerBrowserViewAdapter(NoWatermarkView, ITest, "+test")
+        view = create_view(TestObject(), name="+test")
+        has_watermark = test_tales("view/macro:has-watermark", view=view)
         self.assertIs(True, has_watermark)
 
 
-class PageMacroDispatcherInteractionTestCase(TestPageMacroDispatcherMixin,
-                                             TestCaseWithFactory):
+class PageMacroDispatcherInteractionTestCase(
+    TestPageMacroDispatcherMixin, TestCaseWithFactory
+):
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
@@ -160,6 +169,7 @@ class PageMacroDispatcherInteractionTestCase(TestPageMacroDispatcherMixin,
         # Setup a specific permission for the test object.
         class FakeSecurityChecker(AuthorizationBase):
             """A class to instrument a specific permission."""
+
             @classmethod
             def __call__(adaptee):
                 return FakeSecurityChecker(adaptee)
@@ -174,23 +184,25 @@ class PageMacroDispatcherInteractionTestCase(TestPageMacroDispatcherMixin,
                 return has_permission
 
         self.registerAuthorizationAdapter(
-            FakeSecurityChecker, ITest, 'launchpad.View')
+            FakeSecurityChecker, ITest, "launchpad.View"
+        )
 
     def test_is_page_contentless_public(self):
         # Public objects always have content to be shown.
         self.assertFalse(
-            test_tales('view/macro:is-page-contentless', view=self.view))
+            test_tales("view/macro:is-page-contentless", view=self.view)
+        )
 
     def test_is_page_contentless_private_with_view(self):
         # Private objects the user can view have content to be shown.
         self.view.context.private = True
         self._setUpPermissions(has_permission=True)
-        result = test_tales('view/macro:is-page-contentless', view=self.view)
+        result = test_tales("view/macro:is-page-contentless", view=self.view)
         self.assertFalse(result)
 
     def test_is_page_contentless_private_without_view(self):
         # Private objects the view cannot view cannot show content.
         self.view.context.private = True
         self._setUpPermissions(has_permission=False)
-        result = test_tales('view/macro:is-page-contentless', view=self.view)
+        result = test_tales("view/macro:is-page-contentless", view=self.view)
         self.assertTrue(result)
diff --git a/lib/lp/app/browser/tests/test_stringformatter.py b/lib/lp/app/browser/tests/test_stringformatter.py
index 1663f49..5490b46 100644
--- a/lib/lp/app/browser/tests/test_stringformatter.py
+++ b/lib/lp/app/browser/tests/test_stringformatter.py
@@ -3,33 +3,24 @@
 
 """Unit tests for the string TALES formatter."""
 
+import unittest
 from doctest import DocTestSuite
 from textwrap import dedent
-import unittest
 
-from testtools.matchers import (
-    Equals,
-    Matcher,
-    )
+from testtools.matchers import Equals, Matcher
 from zope.component import getUtility
 
 from lp.app.browser.stringformatter import (
     FormattersAPI,
     linkify_bug_numbers,
     parse_diff,
-    )
+)
 from lp.services.config import config
 from lp.services.features.testing import FeatureFixture
 from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.webapp.publisher import canonical_url
-from lp.testing import (
-    TestCase,
-    TestCaseWithFactory,
-    )
-from lp.testing.layers import (
-    DatabaseFunctionalLayer,
-    ZopelessLayer,
-    )
+from lp.testing import TestCase, TestCaseWithFactory
+from lp.testing.layers import DatabaseFunctionalLayer, ZopelessLayer
 from lp.testing.pages import find_tags_by_class
 
 
@@ -123,24 +114,23 @@ def test_break_long_words():
 
 
 class TestLinkifyingBugs(TestCase):
-
     def test_regular_bug_case_works(self):
         test_strings = [
             "bug 34434",
             "bugnumber 34434",
             "bug number 34434",
-            ]
+        ]
         expected_html = [
+            '<p><a href="/bugs/34434" ' 'class="bug-link">bug 34434</a></p>',
             '<p><a href="/bugs/34434" '
-                'class="bug-link">bug 34434</a></p>',
+            'class="bug-link">bugnumber 34434</a></p>',
             '<p><a href="/bugs/34434" '
-                'class="bug-link">bugnumber 34434</a></p>',
-            '<p><a href="/bugs/34434" '
-                'class="bug-link">bug number 34434</a></p>',
-            ]
+            'class="bug-link">bug number 34434</a></p>',
+        ]
         self.assertEqual(
             expected_html,
-            [FormattersAPI(text).text_to_html() for text in test_strings])
+            [FormattersAPI(text).text_to_html() for text in test_strings],
+        )
 
     def test_things_do_not_link_if_they_should_not(self):
         test_strings = [
@@ -148,22 +138,24 @@ class TestLinkifyingBugs(TestCase):
             "bug number.4",
             "bugno.4",
             "bug no.4",
-            ]
+        ]
         expected_html = [
             "<p>bugnumber.4</p>",
             "<p>bug number.4</p>",
             "<p>bugno.4</p>",
             "<p>bug no.4</p>",
-            ]
+        ]
         self.assertEqual(
             expected_html,
-            [FormattersAPI(text).text_to_html() for text in test_strings])
+            [FormattersAPI(text).text_to_html() for text in test_strings],
+        )
 
     def test_explicit_bug_linkification(self):
-        text = 'LP: #10'
+        text = "LP: #10"
         self.assertEqual(
             'LP: <a href="/bugs/10" class="bug-link">#10</a>',
-            linkify_bug_numbers(text))
+            linkify_bug_numbers(text),
+        )
 
 
 class TestLinkifyingProtocols(TestCaseWithFactory):
@@ -176,56 +168,76 @@ class TestLinkifyingProtocols(TestCaseWithFactory):
             "http://example.com/";,
             "http://example.com/path";,
             "http://example.com/path/";,
-            ]
+        ]
 
         expected_strings = [
-            ('<p><a rel="nofollow" href="http://example.com";>'
-             'http://<wbr />example.<wbr />com</a></p>'),
-            ('<p><a rel="nofollow" href="http://example.com/";>'
-             'http://<wbr />example.<wbr />com/</a></p>'),
-            ('<p><a rel="nofollow" href="http://example.com/path";>'
-             'http://<wbr />example.<wbr />com/path</a></p>'),
-            ('<p><a rel="nofollow" href="http://example.com/path/";>'
-             'http://<wbr />example.<wbr />com/path/</a></p>'),
-            ]
+            (
+                '<p><a rel="nofollow" href="http://example.com";>'
+                "http://<wbr />example.<wbr />com</a></p>"
+            ),
+            (
+                '<p><a rel="nofollow" href="http://example.com/";>'
+                "http://<wbr />example.<wbr />com/</a></p>"
+            ),
+            (
+                '<p><a rel="nofollow" href="http://example.com/path";>'
+                "http://<wbr />example.<wbr />com/path</a></p>"
+            ),
+            (
+                '<p><a rel="nofollow" href="http://example.com/path/";>'
+                "http://<wbr />example.<wbr />com/path/</a></p>"
+            ),
+        ]
 
         self.assertEqual(
             expected_strings,
-            [FormattersAPI(text).text_to_html() for text in test_strings])
+            [FormattersAPI(text).text_to_html() for text in test_strings],
+        )
 
     def test_parens_handled_well(self):
         test_strings = [
-            '(http://example.com)',
-            'http://example.com/path_(with_parens)',
-            '(http://example.com/path_(with_parens))',
-            '(http://example.com/path_(with_parens)and_stuff)',
-            'http://example.com/path_(with_parens',
-            ]
+            "(http://example.com)",
+            "http://example.com/path_(with_parens)",
+            "(http://example.com/path_(with_parens))",
+            "(http://example.com/path_(with_parens)and_stuff)",
+            "http://example.com/path_(with_parens",
+        ]
 
         expected_html = [
-            ('<p>(<a rel="nofollow" href="http://example.com";>'
-             'http://<wbr />example.<wbr />com</a>)</p>'),
-            ('<p><a rel="nofollow" '
-             'href="http://example.com/path_(with_parens)">'
-             'http://<wbr />example.<wbr />com/path_'
-             '<wbr />(with_parens)</a></p>'),
-            ('<p>(<a rel="nofollow" '
-             'href="http://example.com/path_(with_parens)">'
-             'http://<wbr />example.<wbr />com/path_'
-             '<wbr />(with_parens)</a>)</p>'),
-            ('<p>(<a rel="nofollow" '
-             'href="http://example.com/path_(with_parens)and_stuff">'
-             'http://<wbr />example.<wbr />com'
-             '/path_<wbr />(with_parens)<wbr />and_stuff</a>)</p>'),
-            ('<p><a rel="nofollow" '
-             'href="http://example.com/path_(with_parens">'
-             'http://<wbr />example.<wbr />com'
-             '/path_<wbr />(with_parens</a></p>'),
-            ]
+            (
+                '<p>(<a rel="nofollow" href="http://example.com";>'
+                "http://<wbr />example.<wbr />com</a>)</p>"
+            ),
+            (
+                '<p><a rel="nofollow" '
+                'href="http://example.com/path_(with_parens)">'
+                "http://<wbr />example.<wbr />com/path_"
+                "<wbr />(with_parens)</a></p>"
+            ),
+            (
+                '<p>(<a rel="nofollow" '
+                'href="http://example.com/path_(with_parens)">'
+                "http://<wbr />example.<wbr />com/path_"
+                "<wbr />(with_parens)</a>)</p>"
+            ),
+            (
+                '<p>(<a rel="nofollow" '
+                'href="http://example.com/path_(with_parens)and_stuff">'
+                "http://<wbr />example.<wbr />com"
+                "/path_<wbr />(with_parens)<wbr />and_stuff</a>)</p>"
+            ),
+            (
+                '<p><a rel="nofollow" '
+                'href="http://example.com/path_(with_parens">'
+                "http://<wbr />example.<wbr />com"
+                "/path_<wbr />(with_parens</a></p>"
+            ),
+        ]
 
         self.assertEqual(
             expected_html,
-            [FormattersAPI(text).text_to_html() for text in test_strings])
+            [FormattersAPI(text).text_to_html() for text in test_strings],
+        )
 
     def test_protocol_alone_does_not_link(self):
         test_string = "This doesn't link: apt:"
@@ -239,21 +251,23 @@ class TestLinkifyingProtocols(TestCaseWithFactory):
         self.assertEqual(expected_html, html)
 
     def test_apt_is_linked(self):
-        test_string = 'This becomes a link: apt:some-package'
+        test_string = "This becomes a link: apt:some-package"
         html = FormattersAPI(test_string).text_to_html()
         expected_html = (
-            '<p>This becomes a link: '
+            "<p>This becomes a link: "
             '<a rel="nofollow" '
-                'href="apt:some-package">apt:some-<wbr />package</a></p>')
+            'href="apt:some-package">apt:some-<wbr />package</a></p>'
+        )
         self.assertEqual(expected_html, html)
 
         # Do it again for apt://
-        test_string = 'This becomes a link: apt://some-package'
+        test_string = "This becomes a link: apt://some-package"
         html = FormattersAPI(test_string).text_to_html()
         expected_html = (
-            '<p>This becomes a link: '
+            "<p>This becomes a link: "
             '<a rel="nofollow" '
-            'href="apt://some-package">apt://some-<wbr />package</a></p>')
+            'href="apt://some-package">apt://some-<wbr />package</a></p>'
+        )
         self.assertEqual(expected_html, html)
 
     def test_file_is_not_linked(self):
@@ -261,44 +275,50 @@ class TestLinkifyingProtocols(TestCaseWithFactory):
         html = FormattersAPI(test_string).text_to_html()
         expected_html = (
             "<p>This doesn&#x27;t become a link: "
-            "file://<wbr />some/file.<wbr />txt</p>")
+            "file://<wbr />some/file.<wbr />txt</p>"
+        )
         self.assertEqual(expected_html, html)
 
     def test_no_link_with_linkify_text_false(self):
         test_string = "This doesn't become a link: http://www.example.com/";
         html = FormattersAPI(test_string).text_to_html(linkify_text=False)
         expected_html = (
-            "<p>This doesn&#x27;t become a link: http://www.example.com/</p>")
+            "<p>This doesn&#x27;t become a link: http://www.example.com/</p>"
+        )
         self.assertEqual(expected_html, html)
 
     def test_no_link_html_code_with_linkify_text_false(self):
         test_string = '<a href="http://example.com/";>http://example.com/</a>'
         html = FormattersAPI(test_string).text_to_html(linkify_text=False)
         expected_html = (
-            '<p>&lt;a href=&quot;http://example.com/&quot;&gt;'
-            'http://example.com/&lt;/a&gt;</p>')
+            "<p>&lt;a href=&quot;http://example.com/&quot;&gt;";
+            "http://example.com/&lt;/a&gt;</p>"
+        )
         self.assertEqual(expected_html, html)
 
     def test_double_email_in_linkify_email(self):
-        person = self.factory.makePerson(email='foo@xxxxxxxxxxx')
+        person = self.factory.makePerson(email="foo@xxxxxxxxxxx")
         test_string = (
-            ' * Foo. &lt;foo@xxxxxxxxxxx&gt;\n * Bar &lt;foo@xxxxxxxxxxx&gt;')
+            " * Foo. &lt;foo@xxxxxxxxxxx&gt;\n * Bar &lt;foo@xxxxxxxxxxx&gt;"
+        )
         html = FormattersAPI(test_string).linkify_email()
         url = canonical_url(person)
         expected_html = (
             ' * Foo. &lt;<a href="%s" class="sprite person">foo@xxxxxxxxxxx'
             '</a>&gt;\n * Bar &lt;<a href="%s" class="sprite person">'
-            'foo@xxxxxxxxxxx</a>&gt;' % (url, url))
+            "foo@xxxxxxxxxxx</a>&gt;" % (url, url)
+        )
         self.assertEqual(expected_html, html)
 
 
 class TestLastParagraphClass(TestCase):
-
     def test_last_paragraph_class(self):
         self.assertEqual(
             '<p>Foo</p>\n<p class="last">Bar</p>',
             FormattersAPI("Foo\n\nBar").text_to_html(
-                last_paragraph_class="last"))
+                last_paragraph_class="last"
+            ),
+        )
 
 
 class TestParseDiff(TestCase):
@@ -306,28 +326,33 @@ class TestParseDiff(TestCase):
 
     def test_emptyString(self):
         # An empty string yields no lines.
-        self.assertEqual([], list(parse_diff('')))
+        self.assertEqual([], list(parse_diff("")))
 
     def test_almostEmptyString(self):
         # White space yields a single line of text.
-        self.assertEqual([('text', 1, 0, 0, ' ')], list(parse_diff(' ')))
+        self.assertEqual([("text", 1, 0, 0, " ")], list(parse_diff(" ")))
 
     def test_unicode(self):
         # Diffs containing Unicode work too.
         self.assertEqual(
-            [('text', 1, 0, 0, 'Unicode \u1010')],
-            list(parse_diff('Unicode \u1010')))
+            [("text", 1, 0, 0, "Unicode \u1010")],
+            list(parse_diff("Unicode \u1010")),
+        )
 
     def assertParses(self, expected, diff):
         diff_lines = diff.splitlines()
         self.assertEqual(
-            [(css_class, row + 1, orig_row, mod_row, diff_lines[row])
-             for row, (css_class, orig_row, mod_row) in enumerate(expected)],
-            list(parse_diff(diff)))
+            [
+                (css_class, row + 1, orig_row, mod_row, diff_lines[row])
+                for row, (css_class, orig_row, mod_row) in enumerate(expected)
+            ],
+            list(parse_diff(diff)),
+        )
 
     def test_basic_bzr(self):
         # A basic Bazaar diff with a few different classes is parsed correctly.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             === modified file 'tales.py'
             --- tales.py
             +++ tales.py
@@ -341,27 +366,29 @@ class TestParseDiff(TestCase):
             ++++++++ a line of pluses
             ########
             # A merge directive comment.
-            ''')
+            """
+        )
         expected = [
-            ('diff-file text', None, None),
-            ('diff-header text', None, None),
-            ('diff-header text', None, None),
-            ('diff-chunk text', None, None),
-            ('text', 2435, 2439),
-            ('diff-removed text', 2436, None),
-            ('diff-added text', None, 2440),
-            ('diff-added text', None, 2441),
-            ('text', 2437, 2442),
-            ('diff-removed text', 2438, None),
-            ('diff-added text', None, 2443),
-            ('diff-comment text', None, None),
-            ('diff-comment text', None, None),
-            ]
+            ("diff-file text", None, None),
+            ("diff-header text", None, None),
+            ("diff-header text", None, None),
+            ("diff-chunk text", None, None),
+            ("text", 2435, 2439),
+            ("diff-removed text", 2436, None),
+            ("diff-added text", None, 2440),
+            ("diff-added text", None, 2441),
+            ("text", 2437, 2442),
+            ("diff-removed text", 2438, None),
+            ("diff-added text", None, 2443),
+            ("diff-comment text", None, None),
+            ("diff-comment text", None, None),
+        ]
         self.assertParses(expected, diff)
 
     def test_basic_git(self):
         # A basic Git diff with a few different classes is parsed correctly.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             diff --git a/tales.py b/tales.py
             index aaaaaaa..bbbbbbb 100644
             --- a/tales.py
@@ -374,27 +401,29 @@ class TestParseDiff(TestCase):
                      something in between
             -------- a sql style comment
             ++++++++ a line of pluses
-            ''')
+            """
+        )
         expected = [
-            ('diff-file text', None, None),
-            ('diff-file text', None, None),
-            ('diff-header text', None, None),
-            ('diff-header text', None, None),
-            ('diff-chunk text', None, None),
-            ('text', 2435, 2439),
-            ('diff-removed text', 2436, None),
-            ('diff-added text', None, 2440),
-            ('diff-added text', None, 2441),
-            ('text', 2437, 2442),
-            ('diff-removed text', 2438, None),
-            ('diff-added text', None, 2443),
-            ]
+            ("diff-file text", None, None),
+            ("diff-file text", None, None),
+            ("diff-header text", None, None),
+            ("diff-header text", None, None),
+            ("diff-chunk text", None, None),
+            ("text", 2435, 2439),
+            ("diff-removed text", 2436, None),
+            ("diff-added text", None, 2440),
+            ("diff-added text", None, 2441),
+            ("text", 2437, 2442),
+            ("diff-removed text", 2438, None),
+            ("diff-added text", None, 2443),
+        ]
         self.assertParses(expected, diff)
 
     def test_config_value_limits_line_count(self):
         # The config.diff.max_line_format contains the maximum number of
         # lines to parse.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             === modified file 'tales.py'
             --- tales.py
             +++ tales.py
@@ -404,19 +433,21 @@ class TestParseDiff(TestCase):
             +        added this line
             ########
             # A merge directive comment.
-            ''')
+            """
+        )
         expected = [
-            ('diff-file text', None, None),
-            ('diff-header text', None, None),
-            ('diff-header text', None, None),
-            ]
+            ("diff-file text", None, None),
+            ("diff-header text", None, None),
+            ("diff-header text", None, None),
+        ]
         self.pushConfig("diff", max_format_lines=3)
         self.assertParses(expected, diff)
 
     def test_multiple_hunks(self):
         # Diffs containing multiple hunks are parsed reasonably, and the
         # original and modified row numbers are adjusted for each hunk.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             @@ -2,2 +2,3 @@
              a
             -b
@@ -427,19 +458,20 @@ class TestParseDiff(TestCase):
              f
             -g
             +h
-            ''')
+            """
+        )
         expected = [
-            ('diff-chunk text', None, None),
-            ('text', 2, 2),
-            ('diff-removed text', 3, None),
-            ('diff-added text', None, 3),
-            ('diff-added text', None, 4),
-            ('diff-chunk text', None, None),
-            ('diff-removed text', 10, None),
-            ('text', 11, 11),
-            ('diff-removed text', 12, None),
-            ('diff-added text', None, 12),
-            ]
+            ("diff-chunk text", None, None),
+            ("text", 2, 2),
+            ("diff-removed text", 3, None),
+            ("diff-added text", None, 3),
+            ("diff-added text", None, 4),
+            ("diff-chunk text", None, None),
+            ("diff-removed text", 10, None),
+            ("text", 11, 11),
+            ("diff-removed text", 12, None),
+            ("diff-added text", None, 12),
+        ]
         self.assertParses(expected, diff)
 
 
@@ -450,23 +482,28 @@ class TestParseDiffErrors(TestCaseWithFactory):
     def assertParses(self, expected, diff):
         diff_lines = diff.splitlines()
         self.assertEqual(
-            [(css_class, row + 1, orig_row, mod_row, diff_lines[row])
-             for row, (css_class, orig_row, mod_row) in enumerate(expected)],
-            list(parse_diff(diff)))
+            [
+                (css_class, row + 1, orig_row, mod_row, diff_lines[row])
+                for row, (css_class, orig_row, mod_row) in enumerate(expected)
+            ],
+            list(parse_diff(diff)),
+        )
 
     def test_bad_hunk_header(self):
         # A bad hunk header causes the parser to record an OOPS but continue
         # anyway.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             @@ some nonsense @@
                  def method(self):
-            ''')
+            """
+        )
         expected = [
-            ('diff-chunk text', None, None),
-            ('text', 1, 1),
-            ]
+            ("diff-chunk text", None, None),
+            ("text", 1, 1),
+        ]
         self.assertParses(expected, diff)
-        self.assertEqual('MalformedHunkHeader', self.oopses[0]['type'])
+        self.assertEqual("MalformedHunkHeader", self.oopses[0]["type"])
 
 
 class TestDiffFormatter(TestCase):
@@ -474,28 +511,30 @@ class TestDiffFormatter(TestCase):
 
     def test_emptyString(self):
         # An empty string gives an empty string.
-        self.assertEqual(
-            '', FormattersAPI('').format_diff())
+        self.assertEqual("", FormattersAPI("").format_diff())
 
     def test_almostEmptyString(self):
         # White space doesn't count as empty, and is formatted.
         self.assertEqual(
             '<table class="diff unidiff"><tr id="diff-line-1">'
             '<td class="line-no unselectable">1</td><td class="text"> '
-            '</td></tr></table>',
-            FormattersAPI(' ').format_diff())
+            "</td></tr></table>",
+            FormattersAPI(" ").format_diff(),
+        )
 
     def test_format_unicode(self):
         # Sometimes the strings contain unicode, those should work too.
         self.assertEqual(
             '<table class="diff unidiff"><tr id="diff-line-1">'
             '<td class="line-no unselectable">1</td><td class="text">'
-            'Unicode \u1010</td></tr></table>',
-            FormattersAPI('Unicode \u1010').format_diff())
+            "Unicode \u1010</td></tr></table>",
+            FormattersAPI("Unicode \u1010").format_diff(),
+        )
 
     def test_cssClasses(self):
         # Different parts of the diff have different css classes.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             === modified file 'tales.py'
             --- tales.py
             +++ tales.py
@@ -507,31 +546,37 @@ class TestDiffFormatter(TestCase):
             ++++++++ a line of pluses
             ########
             # A merge directive comment.
-            ''')
+            """
+        )
         html = FormattersAPI(diff).format_diff()
-        line_numbers = find_tags_by_class(html, 'line-no')
+        line_numbers = find_tags_by_class(html, "line-no")
         self.assertEqual(
-            ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'],
-            [tag.decode_contents() for tag in line_numbers])
-        text = find_tags_by_class(html, 'text')
+            ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"],
+            [tag.decode_contents() for tag in line_numbers],
+        )
+        text = find_tags_by_class(html, "text")
         self.assertEqual(
-            [['diff-file', 'text'],
-             ['diff-header', 'text'],
-             ['diff-header', 'text'],
-             ['diff-chunk', 'text'],
-             ['text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['diff-comment', 'text'],
-             ['diff-comment', 'text']],
-            [tag['class'] for tag in text])
+            [
+                ["diff-file", "text"],
+                ["diff-header", "text"],
+                ["diff-header", "text"],
+                ["diff-chunk", "text"],
+                ["text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["diff-comment", "text"],
+                ["diff-comment", "text"],
+            ],
+            [tag["class"] for tag in text],
+        )
 
     def test_cssClasses_git(self):
         # Git diffs look slightly different, so check that they also end up
         # with the correct CSS classes.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             diff --git a/tales.py b/tales.py
             index aaaaaaa..bbbbbbb 100644
             --- a/tales.py
@@ -542,25 +587,30 @@ class TestDiffFormatter(TestCase):
             +        added this line
             -------- a sql style comment
             ++++++++ a line of pluses
-            ''')
+            """
+        )
         html = FormattersAPI(diff).format_diff()
-        line_numbers = find_tags_by_class(html, 'line-no')
+        line_numbers = find_tags_by_class(html, "line-no")
         self.assertEqual(
-            ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10'],
-            [tag.decode_contents() for tag in line_numbers])
-        text = find_tags_by_class(html, 'text')
+            ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
+            [tag.decode_contents() for tag in line_numbers],
+        )
+        text = find_tags_by_class(html, "text")
         self.assertEqual(
-            [['diff-file', 'text'],
-             ['diff-file', 'text'],
-             ['diff-header', 'text'],
-             ['diff-header', 'text'],
-             ['diff-chunk', 'text'],
-             ['text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text']],
-            [tag['class'] for tag in text])
+            [
+                ["diff-file", "text"],
+                ["diff-file", "text"],
+                ["diff-header", "text"],
+                ["diff-header", "text"],
+                ["diff-chunk", "text"],
+                ["text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+            ],
+            [tag["class"] for tag in text],
+        )
 
 
 class TestSideBySideDiffFormatter(TestCase):
@@ -568,8 +618,7 @@ class TestSideBySideDiffFormatter(TestCase):
 
     def test_emptyString(self):
         # An empty string gives an empty string.
-        self.assertEqual(
-            '', FormattersAPI('').format_ssdiff())
+        self.assertEqual("", FormattersAPI("").format_ssdiff())
 
     def test_almostEmptyString(self):
         # White space doesn't count as empty, and is formatted.
@@ -580,8 +629,9 @@ class TestSideBySideDiffFormatter(TestCase):
             '<td class="text"></td>'
             '<td class="ss-line-no unselectable">0</td>'
             '<td class="text"></td>'
-            '</tr></table>',
-            FormattersAPI(' ').format_ssdiff())
+            "</tr></table>",
+            FormattersAPI(" ").format_ssdiff(),
+        )
 
     def test_format_unicode(self):
         # Sometimes the strings contain unicode, those should work too.
@@ -592,12 +642,14 @@ class TestSideBySideDiffFormatter(TestCase):
             '<td class="text">Unicode \u1010</td>'
             '<td class="ss-line-no unselectable">0</td>'
             '<td class="text">Unicode \u1010</td>'
-            '</tr></table>',
-            FormattersAPI('Unicode \u1010').format_ssdiff())
+            "</tr></table>",
+            FormattersAPI("Unicode \u1010").format_ssdiff(),
+        )
 
     def test_cssClasses(self):
         # Different parts of the diff have different css classes.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             === modified file 'tales.py'
             --- tales.py
             +++ tales.py
@@ -611,41 +663,58 @@ class TestSideBySideDiffFormatter(TestCase):
             ++++++++ a line of pluses
             ########
             # A merge directive comment.
-            ''')
+            """
+        )
         html = FormattersAPI(diff).format_ssdiff()
-        line_numbers = find_tags_by_class(html, 'line-no')
+        line_numbers = find_tags_by_class(html, "line-no")
         self.assertEqual(
-            ['1', '2', '3', '4', '5', '7', '8', '9', '11', '12', '13'],
-            [tag.decode_contents() for tag in line_numbers])
-        ss_line_numbers = find_tags_by_class(html, 'ss-line-no')
+            ["1", "2", "3", "4", "5", "7", "8", "9", "11", "12", "13"],
+            [tag.decode_contents() for tag in line_numbers],
+        )
+        ss_line_numbers = find_tags_by_class(html, "ss-line-no")
         self.assertEqual(
-            ['2435', '2439', '2436', '2440', '', '2441', '2437', '2442',
-             '2438', '2443'],
-            [tag.decode_contents() for tag in ss_line_numbers])
-        text = find_tags_by_class(html, 'text')
+            [
+                "2435",
+                "2439",
+                "2436",
+                "2440",
+                "",
+                "2441",
+                "2437",
+                "2442",
+                "2438",
+                "2443",
+            ],
+            [tag.decode_contents() for tag in ss_line_numbers],
+        )
+        text = find_tags_by_class(html, "text")
         self.assertEqual(
-            [['diff-file', 'text'],
-             ['diff-header', 'text'],
-             ['diff-header', 'text'],
-             ['diff-chunk', 'text'],
-             ['text'],
-             ['text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['text'],
-             ['text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['diff-comment', 'text'],
-             ['diff-comment', 'text']],
-            [tag['class'] for tag in text])
+            [
+                ["diff-file", "text"],
+                ["diff-header", "text"],
+                ["diff-header", "text"],
+                ["diff-chunk", "text"],
+                ["text"],
+                ["text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["text"],
+                ["text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["diff-comment", "text"],
+                ["diff-comment", "text"],
+            ],
+            [tag["class"] for tag in text],
+        )
 
     def test_cssClasses_git(self):
         # Git diffs look slightly different, so check that they also end up
         # with the correct CSS classes.
-        diff = dedent('''\
+        diff = dedent(
+            """\
             diff --git a/tales.py b/tales.py
             index aaaaaaa..bbbbbbb 100644
             --- a/tales.py
@@ -658,35 +727,51 @@ class TestSideBySideDiffFormatter(TestCase):
                      something in between
             -------- a sql style comment
             ++++++++ a line of pluses
-            ''')
+            """
+        )
         html = FormattersAPI(diff).format_ssdiff()
-        line_numbers = find_tags_by_class(html, 'line-no')
+        line_numbers = find_tags_by_class(html, "line-no")
         self.assertEqual(
-            ['1', '2', '3', '4', '5', '6', '8', '9', '10', '12'],
-            [tag.decode_contents() for tag in line_numbers])
-        ss_line_numbers = find_tags_by_class(html, 'ss-line-no')
+            ["1", "2", "3", "4", "5", "6", "8", "9", "10", "12"],
+            [tag.decode_contents() for tag in line_numbers],
+        )
+        ss_line_numbers = find_tags_by_class(html, "ss-line-no")
         self.assertEqual(
-            ['2435', '2439', '2436', '2440', '', '2441', '2437', '2442',
-             '2438', '2443'],
-            [tag.decode_contents() for tag in ss_line_numbers])
-        text = find_tags_by_class(html, 'text')
+            [
+                "2435",
+                "2439",
+                "2436",
+                "2440",
+                "",
+                "2441",
+                "2437",
+                "2442",
+                "2438",
+                "2443",
+            ],
+            [tag.decode_contents() for tag in ss_line_numbers],
+        )
+        text = find_tags_by_class(html, "text")
         self.assertEqual(
-            [['diff-file', 'text'],
-             ['diff-file', 'text'],
-             ['diff-header', 'text'],
-             ['diff-header', 'text'],
-             ['diff-chunk', 'text'],
-             ['text'],
-             ['text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text'],
-             ['text'],
-             ['text'],
-             ['diff-removed', 'text'],
-             ['diff-added', 'text']],
-            [tag['class'] for tag in text])
+            [
+                ["diff-file", "text"],
+                ["diff-file", "text"],
+                ["diff-header", "text"],
+                ["diff-header", "text"],
+                ["diff-chunk", "text"],
+                ["text"],
+                ["text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+                ["text"],
+                ["text"],
+                ["diff-removed", "text"],
+                ["diff-added", "text"],
+            ],
+            [tag["class"] for tag in text],
+        )
 
 
 class TestOOPSFormatter(TestCase):
@@ -701,55 +786,61 @@ class TestOOPSFormatter(TestCase):
 
     def test_doesnt_linkify_for_non_developers(self):
         # OOPS IDs won't be linkified for non-developers.
-        oops_id = 'OOPS-12345TEST'
+        oops_id = "OOPS-12345TEST"
         formatter = FormattersAPI(oops_id)
         formatted_string = formatter.oops_id()
 
         self.assertEqual(
-            oops_id, formatted_string,
-            "Formatted string should be '%s', was '%s'" % (
-                oops_id, formatted_string))
+            oops_id,
+            formatted_string,
+            "Formatted string should be '%s', was '%s'"
+            % (oops_id, formatted_string),
+        )
 
     def test_linkifies_for_developers(self):
         # OOPS IDs will be linkified for Launchpad developers.
-        oops_id = 'OOPS-12345TEST'
+        oops_id = "OOPS-12345TEST"
         formatter = FormattersAPI(oops_id)
         self._setDeveloper(True)
         formatted_string = formatter.oops_id()
 
         expected_string = '<a href="%s">%s</a>' % (
-            config.launchpad.oops_root_url + oops_id, oops_id)
+            config.launchpad.oops_root_url + oops_id,
+            oops_id,
+        )
 
         self.assertEqual(
-            expected_string, formatted_string,
-            "Formatted string should be '%s', was '%s'" % (
-                expected_string, formatted_string))
+            expected_string,
+            formatted_string,
+            "Formatted string should be '%s', was '%s'"
+            % (expected_string, formatted_string),
+        )
 
 
 class MarksDownAs(Matcher):
-
     def __init__(self, expected_html):
         self.expected_html = expected_html
 
     def match(self, input_string):
         return Equals(self.expected_html).match(
-            FormattersAPI(input_string).markdown())
+            FormattersAPI(input_string).markdown()
+        )
 
 
 class TestMarkdownDisabled(TestCase):
-    """Feature flag can turn Markdown stuff off.
-    """
+    """Feature flag can turn Markdown stuff off."""
 
     layer = DatabaseFunctionalLayer  # Fixtures need the database for now
 
     def setUp(self):
         super().setUp()
-        self.useFixture(FeatureFixture({'markdown.enabled': None}))
+        self.useFixture(FeatureFixture({"markdown.enabled": None}))
 
     def test_plain_text(self):
         self.assertThat(
-            'hello **simple** world',
-            MarksDownAs('<p>hello **simple** world</p>'))
+            "hello **simple** world",
+            MarksDownAs("<p>hello **simple** world</p>"),
+        )
 
 
 class TestMarkdown(TestCase):
@@ -763,12 +854,10 @@ class TestMarkdown(TestCase):
 
     def setUp(self):
         super().setUp()
-        self.useFixture(FeatureFixture({'markdown.enabled': 'on'}))
+        self.useFixture(FeatureFixture({"markdown.enabled": "on"}))
 
     def test_plain_text(self):
-        self.assertThat(
-            'hello world',
-            MarksDownAs('<p>hello world</p>'))
+        self.assertThat("hello world", MarksDownAs("<p>hello world</p>"))
 
 
 def test_suite():
diff --git a/lib/lp/app/browser/tests/test_views.py b/lib/lp/app/browser/tests/test_views.py
index 1a8c266..6a3e4b4 100644
--- a/lib/lp/app/browser/tests/test_views.py
+++ b/lib/lp/app/browser/tests/test_views.py
@@ -8,19 +8,11 @@ import os
 
 from lp.services.features.testing import FeatureFixture
 from lp.services.testing import build_test_suite
-from lp.testing.layers import (
-    BingLaunchpadFunctionalLayer,
-    PageTestLayer,
-    )
-from lp.testing.systemdocs import (
-    LayeredDocFileSuite,
-    setUp,
-    tearDown,
-    )
-
+from lp.testing.layers import BingLaunchpadFunctionalLayer, PageTestLayer
+from lp.testing.systemdocs import LayeredDocFileSuite, setUp, tearDown
 
 here = os.path.dirname(os.path.realpath(__file__))
-bing_flag = FeatureFixture({'sitesearch.engine.name': 'bing'})
+bing_flag = FeatureFixture({"sitesearch.engine.name": "bing"})
 
 
 def setUp_bing(test):
@@ -37,19 +29,23 @@ def tearDown_bing(test):
 # that require something special like the librarian must run on a layer
 # that sets those services up.
 special = {
-    'launchpad-search-pages.txt(Bing)': LayeredDocFileSuite(
-        '../doc/launchpad-search-pages.txt',
-        id_extensions=['launchpad-search-pages.txt(Bing)'],
-        setUp=setUp_bing, tearDown=tearDown_bing,
+    "launchpad-search-pages.txt(Bing)": LayeredDocFileSuite(
+        "../doc/launchpad-search-pages.txt",
+        id_extensions=["launchpad-search-pages.txt(Bing)"],
+        setUp=setUp_bing,
+        tearDown=tearDown_bing,
         layer=BingLaunchpadFunctionalLayer,
-        stdout_logging_level=logging.WARNING),
+        stdout_logging_level=logging.WARNING,
+    ),
     # Run these doctests again with the default search engine.
-    'launchpad-search-pages.txt': LayeredDocFileSuite(
-        '../doc/launchpad-search-pages.txt',
-        setUp=setUp, tearDown=tearDown,
+    "launchpad-search-pages.txt": LayeredDocFileSuite(
+        "../doc/launchpad-search-pages.txt",
+        setUp=setUp,
+        tearDown=tearDown,
         layer=PageTestLayer,
-        stdout_logging_level=logging.WARNING),
-    }
+        stdout_logging_level=logging.WARNING,
+    ),
+}
 
 
 def test_suite():
diff --git a/lib/lp/app/browser/tests/test_vocabulary.py b/lib/lp/app/browser/tests/test_vocabulary.py
index 8a04ba5..05e5fee 100644
--- a/lib/lp/app/browser/tests/test_vocabulary.py
+++ b/lib/lp/app/browser/tests/test_vocabulary.py
@@ -8,10 +8,7 @@ from urllib.parse import urlencode
 
 import pytz
 import simplejson
-from zope.component import (
-    getSiteManager,
-    getUtility,
-    )
+from zope.component import getSiteManager, getUtility
 from zope.formlib.interfaces import MissingInputError
 from zope.interface import implementer
 from zope.schema.interfaces import IVocabularyFactory
@@ -19,9 +16,9 @@ from zope.schema.vocabulary import SimpleTerm
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.browser.vocabulary import (
-    IPickerEntrySource,
     MAX_DESCRIPTION_LENGTH,
-    )
+    IPickerEntrySource,
+)
 from lp.app.errors import UnexpectedFormData
 from lp.registry.interfaces.irc import IIrcIDSet
 from lp.registry.interfaces.person import TeamMembershipPolicy
@@ -31,23 +28,17 @@ from lp.services.webapp.vocabulary import (
     CountableIterator,
     IHugeVocabulary,
     VocabularyFilter,
-    )
-from lp.testing import (
-    celebrity_logged_in,
-    login_person,
-    TestCaseWithFactory,
-    )
-from lp.testing.layers import (
-    DatabaseFunctionalLayer,
-    LaunchpadFunctionalLayer,
-    )
+)
+from lp.testing import TestCaseWithFactory, celebrity_logged_in, login_person
+from lp.testing.layers import DatabaseFunctionalLayer, LaunchpadFunctionalLayer
 from lp.testing.views import create_view
 
 
 def get_picker_entry(item_subject, context_object, **kwargs):
     """Adapt `item_subject` to `IPickerEntrySource` and return its item."""
     [entry] = IPickerEntrySource(item_subject).getPickerEntries(
-        [item_subject], context_object, **kwargs)
+        [item_subject], context_object, **kwargs
+    )
     return entry
 
 
@@ -67,13 +58,14 @@ class DefaultPickerEntrySourceAdapterTestCase(TestCaseWithFactory):
         # sprite adapter rules, the generic sprite is used.
         thing = object()
         entry = get_picker_entry(thing, object())
-        self.assertEqual('sprite bullet', entry.css)
+        self.assertEqual("sprite bullet", entry.css)
         self.assertEqual(None, entry.image)
 
     def test_css_image_entry_with_icon(self):
         # When the context has a custom icon the URL is used.
         icon = self.factory.makeLibraryFileAlias(
-            filename='smurf.png', content_type='image/png')
+            filename="smurf.png", content_type="image/png"
+        )
         product = self.factory.makeProduct(icon=icon)
         entry = get_picker_entry(product, object())
         self.assertEqual(None, entry.css)
@@ -92,29 +84,32 @@ class PersonPickerEntrySourceAdapterTestCase(TestCaseWithFactory):
 
     def test_PersonPickerEntrySourceAdapter_email_anonymous(self):
         # Anonymous users cannot see entry email addresses.
-        person = self.factory.makePerson(email='snarf@xxxxxx')
+        person = self.factory.makePerson(email="snarf@xxxxxx")
         self.assertEqual(
             "<email address hidden>",
-            get_picker_entry(person, None).description)
+            get_picker_entry(person, None).description,
+        )
 
     def test_PersonPickerEntrySourceAdapter_visible_email_logged_in(self):
         # Logged in users can see visible email addresses.
         observer = self.factory.makePerson()
         login_person(observer)
-        person = self.factory.makePerson(email='snarf@xxxxxx')
+        person = self.factory.makePerson(email="snarf@xxxxxx")
         self.assertEqual(
-            'snarf@xxxxxx', get_picker_entry(person, None).description)
+            "snarf@xxxxxx", get_picker_entry(person, None).description
+        )
 
     def test_PersonPickerEntrySourceAdapter_hidden_email_logged_in(self):
         # Logged in users cannot see hidden email addresses.
-        person = self.factory.makePerson(email='snarf@xxxxxx')
+        person = self.factory.makePerson(email="snarf@xxxxxx")
         login_person(person)
         person.hide_email_addresses = True
         observer = self.factory.makePerson()
         login_person(observer)
         self.assertEqual(
             "<email address hidden>",
-            get_picker_entry(person, None).description)
+            get_picker_entry(person, None).description,
+        )
 
     def test_PersonPickerEntrySourceAdapter_no_email_logged_in(self):
         # Teams without email address have no desriptions.
@@ -127,65 +122,73 @@ class PersonPickerEntrySourceAdapterTestCase(TestCaseWithFactory):
         # Logged in users can see visible email addresses.
         observer = self.factory.makePerson()
         login_person(observer)
-        person = self.factory.makePerson(
-            email='snarf@xxxxxx', name='snarf')
+        person = self.factory.makePerson(email="snarf@xxxxxx", name="snarf")
         entry = get_picker_entry(person, None)
-        self.assertEqual('sprite person', entry.css)
-        self.assertEqual('sprite new-window', entry.link_css)
+        self.assertEqual("sprite person", entry.css)
+        self.assertEqual("sprite new-window", entry.link_css)
 
     def test_PersonPickerEntrySourceAdapter_user(self):
         # The person picker provides more information for users.
-        person = self.factory.makePerson(email='snarf@xxxxxx', name='snarf')
-        creation_date = datetime(2005, 1, 30, 0, 0, 0, 0, pytz.timezone('UTC'))
+        person = self.factory.makePerson(email="snarf@xxxxxx", name="snarf")
+        creation_date = datetime(2005, 1, 30, 0, 0, 0, 0, pytz.timezone("UTC"))
         removeSecurityProxy(person).datecreated = creation_date
-        getUtility(IIrcIDSet).new(person, 'eg.dom', 'snarf')
-        getUtility(IIrcIDSet).new(person, 'ex.dom', 'pting')
+        getUtility(IIrcIDSet).new(person, "eg.dom", "snarf")
+        getUtility(IIrcIDSet).new(person, "ex.dom", "pting")
         entry = get_picker_entry(person, None, picker_expander_enabled=True)
-        self.assertEqual('http://launchpad.test/~snarf', entry.alt_title_link)
+        self.assertEqual("http://launchpad.test/~snarf";, entry.alt_title_link)
         self.assertEqual(
-            ['snarf on eg.dom, pting on ex.dom', 'Member since 2005-01-30'],
-            entry.details)
+            ["snarf on eg.dom, pting on ex.dom", "Member since 2005-01-30"],
+            entry.details,
+        )
 
     def test_PersonPickerEntrySourceAdapter_team(self):
         # The person picker provides more information for teams.
-        team = self.factory.makeTeam(email='fnord@xxxxxx', name='fnord')
+        team = self.factory.makeTeam(email="fnord@xxxxxx", name="fnord")
         entry = get_picker_entry(team, None, picker_expander_enabled=True)
-        self.assertEqual('http://launchpad.test/~fnord', entry.alt_title_link)
-        self.assertEqual(['Team members: 1'], entry.details)
+        self.assertEqual("http://launchpad.test/~fnord";, entry.alt_title_link)
+        self.assertEqual(["Team members: 1"], entry.details)
 
     def test_PersonPickerEntryAdapter_badges(self):
         # The person picker provides affiliation information.
-        person = self.factory.makePerson(email='snarf@xxxxxx', name='snarf')
+        person = self.factory.makePerson(email="snarf@xxxxxx", name="snarf")
         project = self.factory.makeProduct(
-            name='fnord', owner=person, bug_supervisor=person)
+            name="fnord", owner=person, bug_supervisor=person
+        )
         bugtask = self.factory.makeBugTask(target=project)
         entry = get_picker_entry(
-            person, bugtask, picker_expander_enabled=True,
-            personpicker_affiliation_enabled=True)
+            person,
+            bugtask,
+            picker_expander_enabled=True,
+            personpicker_affiliation_enabled=True,
+        )
         self.assertEqual(3, len(entry.badges))
-        self.assertEqual('/@@/product-badge', entry.badges[0]['url'])
-        self.assertEqual('Fnord', entry.badges[0]['label'])
-        self.assertEqual('maintainer', entry.badges[0]['role'])
-        self.assertEqual('/@@/product-badge', entry.badges[1]['url'])
-        self.assertEqual('Fnord', entry.badges[1]['label'])
-        self.assertEqual('driver', entry.badges[1]['role'])
-        self.assertEqual('/@@/product-badge', entry.badges[2]['url'])
-        self.assertEqual('Fnord', entry.badges[2]['label'])
-        self.assertEqual('bug supervisor', entry.badges[2]['role'])
+        self.assertEqual("/@@/product-badge", entry.badges[0]["url"])
+        self.assertEqual("Fnord", entry.badges[0]["label"])
+        self.assertEqual("maintainer", entry.badges[0]["role"])
+        self.assertEqual("/@@/product-badge", entry.badges[1]["url"])
+        self.assertEqual("Fnord", entry.badges[1]["label"])
+        self.assertEqual("driver", entry.badges[1]["role"])
+        self.assertEqual("/@@/product-badge", entry.badges[2]["url"])
+        self.assertEqual("Fnord", entry.badges[2]["label"])
+        self.assertEqual("bug supervisor", entry.badges[2]["role"])
 
     def test_PersonPickerEntryAdapter_badges_without_IHasAffiliation(self):
         # The person picker handles objects that do not support
         # IHasAffilliation.
-        person = self.factory.makePerson(email='snarf@xxxxxx', name='snarf')
+        person = self.factory.makePerson(email="snarf@xxxxxx", name="snarf")
         thing = object()
         entry = get_picker_entry(
-            person, thing, picker_expander_enabled=True,
-            personpicker_affiliation_enabled=True)
+            person,
+            thing,
+            picker_expander_enabled=True,
+            personpicker_affiliation_enabled=True,
+        )
         self.assertIsNot(None, entry)
 
 
 class TestDistributionSourcePackagePickerEntrySourceAdapter(
-        TestCaseWithFactory):
+    TestCaseWithFactory
+):
 
     layer = DatabaseFunctionalLayer
 
@@ -201,12 +204,12 @@ class TestDistributionSourcePackagePickerEntrySourceAdapter(
         dsp = self.factory.makeDistributionSourcePackage()
         series = self.factory.makeDistroSeries(distribution=dsp.distribution)
         release = self.factory.makeSourcePackageRelease(
-            distroseries=series,
-            sourcepackagename=dsp.sourcepackagename)
+            distroseries=series, sourcepackagename=dsp.sourcepackagename
+        )
         self.factory.makeSourcePackagePublishingHistory(
-            distroseries=series,
-            sourcepackagerelease=release)
-        self.assertEqual('package', self.getPickerEntry(dsp).target_type)
+            distroseries=series, sourcepackagerelease=release
+        )
+        self.assertEqual("package", self.getPickerEntry(dsp).target_type)
 
     def test_dsp_provides_details_no_maintainer(self):
         dsp = self.factory.makeDistributionSourcePackage(with_db=True)
@@ -215,47 +218,54 @@ class TestDistributionSourcePackagePickerEntrySourceAdapter(
     def test_dsp_provides_summary_unbuilt(self):
         dsp = self.factory.makeDistributionSourcePackage(with_db=True)
         self.assertEqual(
-            "Not yet built.", self.getPickerEntry(dsp).description)
+            "Not yet built.", self.getPickerEntry(dsp).description
+        )
 
     def test_dsp_provides_summary_built(self):
         dsp = self.factory.makeDistributionSourcePackage(with_db=True)
         series = self.factory.makeDistroSeries(distribution=dsp.distribution)
         release = self.factory.makeSourcePackageRelease(
-            distroseries=series,
-            sourcepackagename=dsp.sourcepackagename)
+            distroseries=series, sourcepackagename=dsp.sourcepackagename
+        )
         self.factory.makeSourcePackagePublishingHistory(
-            distroseries=series,
-            sourcepackagerelease=release)
+            distroseries=series, sourcepackagerelease=release
+        )
         archseries = self.factory.makeDistroArchSeries(distroseries=series)
-        bpn = self.factory.makeBinaryPackageName(name='fnord')
+        bpn = self.factory.makeBinaryPackageName(name="fnord")
         self.factory.makeBinaryPackagePublishingHistory(
             binarypackagename=bpn,
             source_package_release=release,
             sourcepackagename=dsp.sourcepackagename,
-            distroarchseries=archseries)
+            distroarchseries=archseries,
+        )
         self.assertEqual("fnord", self.getPickerEntry(dsp).description)
 
     def test_dsp_alt_title_is_none(self):
         # DSP titles are contructed from the distro and package Launchapd Ids,
         # alt_titles are redundant because they are also Launchpad Ids.
-        distro = self.factory.makeDistribution(name='fnord')
+        distro = self.factory.makeDistribution(name="fnord")
         series = self.factory.makeDistroSeries(
-            name='pting', distribution=distro)
+            name="pting", distribution=distro
+        )
         self.factory.makeSourcePackage(
-            sourcepackagename='snarf', distroseries=series, publish=True)
-        dsp = distro.getSourcePackage('snarf')
+            sourcepackagename="snarf", distroseries=series, publish=True
+        )
+        dsp = distro.getSourcePackage("snarf")
         self.assertEqual(None, self.getPickerEntry(dsp).alt_title)
 
     def test_dsp_provides_alt_title_link(self):
-        distro = self.factory.makeDistribution(name='fnord')
+        distro = self.factory.makeDistribution(name="fnord")
         series = self.factory.makeDistroSeries(
-            name='pting', distribution=distro)
+            name="pting", distribution=distro
+        )
         self.factory.makeSourcePackage(
-            sourcepackagename='snarf', distroseries=series, publish=True)
-        dsp = distro.getSourcePackage('snarf')
+            sourcepackagename="snarf", distroseries=series, publish=True
+        )
+        dsp = distro.getSourcePackage("snarf")
         self.assertEqual(
-            'http://launchpad.test/fnord/+source/snarf',
-            self.getPickerEntry(dsp).alt_title_link)
+            "http://launchpad.test/fnord/+source/snarf";,
+            self.getPickerEntry(dsp).alt_title_link,
+        )
 
 
 class TestProductPickerEntrySourceAdapter(TestCaseWithFactory):
@@ -278,60 +288,66 @@ class TestProductPickerEntrySourceAdapter(TestCaseWithFactory):
         product = self.factory.makeProduct()
         # We check for project, not product, because users don't see
         # products.
-        self.assertEqual('project', self.getPickerEntry(product).target_type)
+        self.assertEqual("project", self.getPickerEntry(product).target_type)
 
     def test_product_provides_details(self):
         product = self.factory.makeProduct()
         self.assertEqual(
             "Maintainer: %s" % product.owner.displayname,
-            self.getPickerEntry(product).details[0])
+            self.getPickerEntry(product).details[0],
+        )
 
     def test_product_provides_summary(self):
         product = self.factory.makeProduct()
         self.assertEqual(
-            product.summary, self.getPickerEntry(product).description)
+            product.summary, self.getPickerEntry(product).description
+        )
 
     def test_product_truncates_summary(self):
-        summary = ("This is a deliberately, overly long summary. It goes on"
-                   "and on and on so as to break things up a good bit.")
+        summary = (
+            "This is a deliberately, overly long summary. It goes on"
+            "and on and on so as to break things up a good bit."
+        )
         product = self.factory.makeProduct(summary=summary)
-        index = summary.rfind(' ', 0, 45)
-        expected_summary = summary[:index + 1]
+        index = summary.rfind(" ", 0, 45)
+        expected_summary = summary[: index + 1]
         expected_details = summary[index:]
         entry = self.getPickerEntry(product)
-        self.assertEqual(
-            expected_summary, entry.description)
-        self.assertEqual(
-            expected_details, entry.details[0])
+        self.assertEqual(expected_summary, entry.description)
+        self.assertEqual(expected_details, entry.details[0])
 
     def test_product_provides_alt_title_link(self):
-        product = self.factory.makeProduct(name='fnord')
+        product = self.factory.makeProduct(name="fnord")
         self.assertEqual(
-            'http://launchpad.test/fnord',
-            self.getPickerEntry(product).alt_title_link)
+            "http://launchpad.test/fnord";,
+            self.getPickerEntry(product).alt_title_link,
+        )
 
     def test_provides_commercial_subscription_none(self):
-        product = self.factory.makeProduct(name='fnord')
+        product = self.factory.makeProduct(name="fnord")
         self.assertEqual(
-            'Commercial Subscription: None',
-            self.getPickerEntry(product).details[1])
+            "Commercial Subscription: None",
+            self.getPickerEntry(product).details[1],
+        )
 
     def test_provides_commercial_subscription_active(self):
-        product = self.factory.makeProduct(name='fnord')
+        product = self.factory.makeProduct(name="fnord")
         self.factory.makeCommercialSubscription(product)
         self.assertEqual(
-            'Commercial Subscription: Active',
-            self.getPickerEntry(product).details[1])
+            "Commercial Subscription: Active",
+            self.getPickerEntry(product).details[1],
+        )
 
     def test_provides_commercial_subscription_expired(self):
-        product = self.factory.makeProduct(name='fnord')
+        product = self.factory.makeProduct(name="fnord")
         self.factory.makeCommercialSubscription(product)
         then = datetime(2005, 6, 15, 0, 0, 0, 0, pytz.UTC)
-        with celebrity_logged_in('admin'):
+        with celebrity_logged_in("admin"):
             product.commercial_subscription.date_expires = then
         self.assertEqual(
-            'Commercial Subscription: Expired',
-            self.getPickerEntry(product).details[1])
+            "Commercial Subscription: Expired",
+            self.getPickerEntry(product).details[1],
+        )
 
 
 class TestProjectGroupPickerEntrySourceAdapter(TestCaseWithFactory):
@@ -349,43 +365,47 @@ class TestProjectGroupPickerEntrySourceAdapter(TestCaseWithFactory):
     def test_projectgroup_provides_alt_title(self):
         projectgroup = self.factory.makeProject()
         self.assertEqual(
-            projectgroup.name, self.getPickerEntry(projectgroup).alt_title)
+            projectgroup.name, self.getPickerEntry(projectgroup).alt_title
+        )
 
     def test_projectgroup_target_type(self):
         projectgroup = self.factory.makeProject()
         self.assertEqual(
-            'project group', self.getPickerEntry(projectgroup).target_type)
+            "project group", self.getPickerEntry(projectgroup).target_type
+        )
 
     def test_projectgroup_provides_details(self):
         projectgroup = self.factory.makeProject()
         self.assertEqual(
             "Maintainer: %s" % projectgroup.owner.displayname,
-            self.getPickerEntry(projectgroup).details[0])
+            self.getPickerEntry(projectgroup).details[0],
+        )
 
     def test_projectgroup_provides_summary(self):
         projectgroup = self.factory.makeProject()
         self.assertEqual(
-            projectgroup.summary,
-            self.getPickerEntry(projectgroup).description)
+            projectgroup.summary, self.getPickerEntry(projectgroup).description
+        )
 
     def test_projectgroup_truncates_summary(self):
-        summary = ("This is a deliberately, overly long summary. It goes on"
-                   "and on and on so as to break things up a good bit.")
+        summary = (
+            "This is a deliberately, overly long summary. It goes on"
+            "and on and on so as to break things up a good bit."
+        )
         projectgroup = self.factory.makeProject(summary=summary)
-        index = summary.rfind(' ', 0, 45)
-        expected_summary = summary[:index + 1]
+        index = summary.rfind(" ", 0, 45)
+        expected_summary = summary[: index + 1]
         expected_details = summary[index:]
         entry = self.getPickerEntry(projectgroup)
-        self.assertEqual(
-            expected_summary, entry.description)
-        self.assertEqual(
-            expected_details, entry.details[0])
+        self.assertEqual(expected_summary, entry.description)
+        self.assertEqual(expected_details, entry.details[0])
 
     def test_projectgroup_provides_alt_title_link(self):
-        projectgroup = self.factory.makeProject(name='fnord')
+        projectgroup = self.factory.makeProject(name="fnord")
         self.assertEqual(
-            'http://launchpad.test/fnord',
-            self.getPickerEntry(projectgroup).alt_title_link)
+            "http://launchpad.test/fnord";,
+            self.getPickerEntry(projectgroup).alt_title_link,
+        )
 
 
 class TestDistributionPickerEntrySourceAdapter(TestCaseWithFactory):
@@ -403,75 +423,85 @@ class TestDistributionPickerEntrySourceAdapter(TestCaseWithFactory):
     def test_distribution_provides_alt_title(self):
         distribution = self.factory.makeDistribution()
         self.assertEqual(
-            distribution.name, self.getPickerEntry(distribution).alt_title)
+            distribution.name, self.getPickerEntry(distribution).alt_title
+        )
 
     def test_distribution_provides_details(self):
         distribution = self.factory.makeDistribution()
         self.factory.makeDistroSeries(
-            distribution=distribution, status=SeriesStatus.CURRENT)
+            distribution=distribution, status=SeriesStatus.CURRENT
+        )
         self.assertEqual(
             "Maintainer: %s" % distribution.currentseries.owner.displayname,
-            self.getPickerEntry(distribution).details[0])
+            self.getPickerEntry(distribution).details[0],
+        )
 
     def test_distribution_provides_summary(self):
         distribution = self.factory.makeDistribution()
         self.assertEqual(
-            distribution.summary,
-            self.getPickerEntry(distribution).description)
+            distribution.summary, self.getPickerEntry(distribution).description
+        )
 
     def test_distribution_target_type(self):
         distribution = self.factory.makeDistribution()
         self.assertEqual(
-            'distribution', self.getPickerEntry(distribution).target_type)
+            "distribution", self.getPickerEntry(distribution).target_type
+        )
 
     def test_distribution_truncates_summary(self):
         summary = (
             "This is a deliberately, overly long summary. It goes on "
-            "and on and on so as to break things up a good bit.")
+            "and on and on so as to break things up a good bit."
+        )
         distribution = self.factory.makeDistribution(summary=summary)
-        index = summary.rfind(' ', 0, 45)
-        expected_summary = summary[:index + 1]
+        index = summary.rfind(" ", 0, 45)
+        expected_summary = summary[: index + 1]
         expected_details = summary[index:]
         entry = self.getPickerEntry(distribution)
-        self.assertEqual(
-            expected_summary, entry.description)
-        self.assertEqual(
-            expected_details, entry.details[0])
+        self.assertEqual(expected_summary, entry.description)
+        self.assertEqual(expected_details, entry.details[0])
 
     def test_distribution_provides_alt_title_link(self):
-        distribution = self.factory.makeDistribution(name='fnord')
+        distribution = self.factory.makeDistribution(name="fnord")
         self.assertEqual(
-            'http://launchpad.test/fnord',
-            self.getPickerEntry(distribution).alt_title_link)
+            "http://launchpad.test/fnord";,
+            self.getPickerEntry(distribution).alt_title_link,
+        )
 
     def test_provides_commercial_subscription_none(self):
         distribution = self.factory.makeDistribution()
         self.factory.makeDistroSeries(
-            distribution=distribution, status=SeriesStatus.CURRENT)
+            distribution=distribution, status=SeriesStatus.CURRENT
+        )
         self.assertEqual(
-            'Commercial Subscription: None',
-            self.getPickerEntry(distribution).details[1])
+            "Commercial Subscription: None",
+            self.getPickerEntry(distribution).details[1],
+        )
 
     def test_provides_commercial_subscription_active(self):
         distribution = self.factory.makeDistribution()
         self.factory.makeDistroSeries(
-            distribution=distribution, status=SeriesStatus.CURRENT)
+            distribution=distribution, status=SeriesStatus.CURRENT
+        )
         self.factory.makeCommercialSubscription(distribution)
         self.assertEqual(
-            'Commercial Subscription: Active',
-            self.getPickerEntry(distribution).details[1])
+            "Commercial Subscription: Active",
+            self.getPickerEntry(distribution).details[1],
+        )
 
     def test_provides_commercial_subscription_expired(self):
         distribution = self.factory.makeDistribution()
         self.factory.makeDistroSeries(
-            distribution=distribution, status=SeriesStatus.CURRENT)
+            distribution=distribution, status=SeriesStatus.CURRENT
+        )
         self.factory.makeCommercialSubscription(distribution)
         then = datetime(2005, 6, 15, 0, 0, 0, 0, pytz.UTC)
-        with celebrity_logged_in('admin'):
+        with celebrity_logged_in("admin"):
             distribution.commercial_subscription.date_expires = then
         self.assertEqual(
-            'Commercial Subscription: Expired',
-            self.getPickerEntry(distribution).details[1])
+            "Commercial Subscription: Expired",
+            self.getPickerEntry(distribution).details[1],
+        )
 
 
 @implementer(IHugeVocabulary)
@@ -490,12 +520,14 @@ class TestPersonVocabulary:
 
     def searchForTerms(self, query=None, vocab_filter=None):
         if vocab_filter is None:
-            filter_term = ''
+            filter_term = ""
         else:
             filter_term = vocab_filter.filter_terms[0]
         found = [
-            person for person in self.test_persons
-                if query in person.name and filter_term in person.name]
+            person
+            for person in self.test_persons
+            if query in person.name and filter_term in person.name
+        ]
         return CountableIterator(len(found), found, self.toTerm)
 
 
@@ -503,11 +535,11 @@ class TestVocabularyFilter(VocabularyFilter):
     # A filter returning all objects.
 
     def __new__(cls):
-        return super().__new__(cls, 'FILTER', 'Test Filter', 'Test')
+        return super().__new__(cls, "FILTER", "Test Filter", "Test")
 
     @property
     def filter_terms(self):
-        return ['xpting-person']
+        return ["xpting-person"]
 
 
 class HugeVocabularyJSONViewTestCase(TestCaseWithFactory):
@@ -519,13 +551,18 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory):
         test_persons = []
         for name in range(1, 7):
             test_persons.append(
-                self.factory.makePerson(name='pting-%s' % name))
+                self.factory.makePerson(name="pting-%s" % name)
+            )
         TestPersonVocabulary.setTestData(test_persons)
         getSiteManager().registerUtility(
-            TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
+            TestPersonVocabulary, IVocabularyFactory, "TestPerson"
+        )
         self.addCleanup(
             getSiteManager().unregisterUtility,
-            TestPersonVocabulary, IVocabularyFactory, 'TestPerson')
+            TestPersonVocabulary,
+            IVocabularyFactory,
+            "TestPerson",
+        )
         self.addCleanup(TestPersonVocabulary.setTestData, [])
 
     @staticmethod
@@ -534,136 +571,151 @@ class HugeVocabularyJSONViewTestCase(TestCaseWithFactory):
             context = getUtility(ILaunchpadRoot)
         query_string = urlencode(form)
         return create_view(
-            context, '+huge-vocabulary', form=form, query_string=query_string)
+            context, "+huge-vocabulary", form=form, query_string=query_string
+        )
 
     def test_name_field_missing_error(self):
         view = self.create_vocabulary_view({})
         self.assertRaisesWithContent(
-            MissingInputError, "('name', '', None)", view.__call__)
+            MissingInputError, "('name', '', None)", view.__call__
+        )
 
     def test_search_text_field_missing_error(self):
-        view = self.create_vocabulary_view({'name': 'TestPerson'})
+        view = self.create_vocabulary_view({"name": "TestPerson"})
         self.assertRaisesWithContent(
-            MissingInputError, "('search_text', '', None)", view.__call__)
+            MissingInputError, "('search_text', '', None)", view.__call__
+        )
 
     def test_vocabulary_name_unknown_error(self):
-        form = dict(name='snarf', search_text='pting')
+        form = dict(name="snarf", search_text="pting")
         view = self.create_vocabulary_view(form)
         self.assertRaisesWithContent(
-            UnexpectedFormData, "Unknown vocabulary 'snarf'", view.__call__)
+            UnexpectedFormData, "Unknown vocabulary 'snarf'", view.__call__
+        )
 
     def test_json_entries(self):
         # The results are JSON encoded.
         team = self.factory.makeTeam(
-            name='xpting-team',
-            membership_policy=TeamMembershipPolicy.RESTRICTED)
-        person = self.factory.makePerson(name='xpting-person')
-        creation_date = datetime(2005, 1, 30, 0, 0, 0, 0, pytz.timezone('UTC'))
+            name="xpting-team",
+            membership_policy=TeamMembershipPolicy.RESTRICTED,
+        )
+        person = self.factory.makePerson(name="xpting-person")
+        creation_date = datetime(2005, 1, 30, 0, 0, 0, 0, pytz.timezone("UTC"))
         removeSecurityProxy(person).datecreated = creation_date
         TestPersonVocabulary.test_persons.extend([team, person])
         product = self.factory.makeProduct(owner=team)
         bugtask = self.factory.makeBugTask(target=product)
-        form = dict(name='TestPerson', search_text='xpting')
+        form = dict(name="TestPerson", search_text="xpting")
         view = self.create_vocabulary_view(form, context=bugtask)
         result = simplejson.loads(view())
-        expected = [{
-            "alt_title": team.name,
-            "alt_title_link": "http://launchpad.test/~%s"; % team.name,
-            "api_uri": "/~%s" % team.name,
-            "badges":
-                [{"label": product.displayname,
-                  "role": "maintainer",
-                  "url": "/@@/product-badge"},
-                {"label": product.displayname,
-                 "role": "driver",
-                  "url": "/@@/product-badge"}],
-            "css": "sprite team",
-            "details": ['Team members: 1'],
-            "link_css": "sprite new-window",
-            "metadata": "team",
-            "title": team.displayname,
-            "value": team.name
+        expected = [
+            {
+                "alt_title": team.name,
+                "alt_title_link": "http://launchpad.test/~%s"; % team.name,
+                "api_uri": "/~%s" % team.name,
+                "badges": [
+                    {
+                        "label": product.displayname,
+                        "role": "maintainer",
+                        "url": "/@@/product-badge",
+                    },
+                    {
+                        "label": product.displayname,
+                        "role": "driver",
+                        "url": "/@@/product-badge",
+                    },
+                ],
+                "css": "sprite team",
+                "details": ["Team members: 1"],
+                "link_css": "sprite new-window",
+                "metadata": "team",
+                "title": team.displayname,
+                "value": team.name,
             },
             {
-            "alt_title": person.name,
-            "alt_title_link": "http://launchpad.test/~%s"; % person.name,
-            "api_uri": "/~%s" % person.name,
-            "css": "sprite person",
-            "description": "<email address hidden>",
-            "details": ['Member since 2005-01-30'],
-            "link_css": "sprite new-window",
-            "metadata": "person",
-            "title": person.displayname,
-            "value": person.name
-            }]
-        self.assertTrue('entries' in result)
+                "alt_title": person.name,
+                "alt_title_link": "http://launchpad.test/~%s"; % person.name,
+                "api_uri": "/~%s" % person.name,
+                "css": "sprite person",
+                "description": "<email address hidden>",
+                "details": ["Member since 2005-01-30"],
+                "link_css": "sprite new-window",
+                "metadata": "person",
+                "title": person.displayname,
+                "value": person.name,
+            },
+        ]
+        self.assertTrue("entries" in result)
         self.assertContentEqual(
-            expected[0].items(), result['entries'][0].items())
+            expected[0].items(), result["entries"][0].items()
+        )
         self.assertContentEqual(
-            expected[1].items(), result['entries'][1].items())
+            expected[1].items(), result["entries"][1].items()
+        )
 
     def test_vocab_filter(self):
         # The vocab filter is used to filter results.
         team = self.factory.makeTeam(
-            name='xpting-team',
-            membership_policy=TeamMembershipPolicy.RESTRICTED)
-        person = self.factory.makePerson(name='xpting-person')
+            name="xpting-team",
+            membership_policy=TeamMembershipPolicy.RESTRICTED,
+        )
+        person = self.factory.makePerson(name="xpting-person")
         TestPersonVocabulary.test_persons.extend([team, person])
         product = self.factory.makeProduct(owner=team)
         vocab_filter = TestVocabularyFilter()
-        form = dict(name='TestPerson',
-                    search_text='xpting', search_filter=vocab_filter)
+        form = dict(
+            name="TestPerson", search_text="xpting", search_filter=vocab_filter
+        )
         view = self.create_vocabulary_view(form, context=product)
         result = simplejson.loads(view())
-        entries = result['entries']
+        entries = result["entries"]
         self.assertEqual(1, len(entries))
-        self.assertEqual('xpting-person', entries[0]['value'])
+        self.assertEqual("xpting-person", entries[0]["value"])
 
     def test_max_description_size(self):
         # Descriptions over 120 characters are truncated and ellipsised.
-        email = 'pting-' * 19 + '@example.dom'
-        person = self.factory.makePerson(name='pting-n', email=email)
+        email = "pting-" * 19 + "@example.dom"
+        person = self.factory.makePerson(name="pting-n", email=email)
         TestPersonVocabulary.test_persons.append(person)
         # Login to gain permission to know the email address that used
         # for the description
         login_person(person)
-        form = dict(name='TestPerson', search_text='pting-n')
+        form = dict(name="TestPerson", search_text="pting-n")
         view = self.create_vocabulary_view(form)
         result = simplejson.loads(view())
-        expected = (email[:MAX_DESCRIPTION_LENGTH - 3] + '...')
-        self.assertEqual(
-            'pting-n', result['entries'][0]['value'])
-        self.assertEqual(
-            expected, result['entries'][0]['description'])
+        expected = email[: MAX_DESCRIPTION_LENGTH - 3] + "..."
+        self.assertEqual("pting-n", result["entries"][0]["value"])
+        self.assertEqual(expected, result["entries"][0]["description"])
 
     def test_default_batch_size(self):
         # The results are batched.
-        form = dict(name='TestPerson', search_text='pting')
+        form = dict(name="TestPerson", search_text="pting")
         view = self.create_vocabulary_view(form)
         result = simplejson.loads(view())
-        total_size = result['total_size']
-        entries = len(result['entries'])
+        total_size = result["total_size"]
+        entries = len(result["entries"])
         self.assertTrue(
             total_size > entries,
-            'total_size: %d is less than entries: %d' % (total_size, entries))
+            "total_size: %d is less than entries: %d" % (total_size, entries),
+        )
 
     def test_batch_size(self):
         # A The batch size can be specified with the batch param.
         form = dict(
-            name='TestPerson', search_text='pting',
-            start='0', batch='1')
+            name="TestPerson", search_text="pting", start="0", batch="1"
+        )
         view = self.create_vocabulary_view(form)
         result = simplejson.loads(view())
-        self.assertEqual(6, result['total_size'])
-        self.assertEqual(1, len(result['entries']))
+        self.assertEqual(6, result["total_size"])
+        self.assertEqual(1, len(result["entries"]))
 
     def test_start_offset(self):
         # The offset of the batch is specified with the start param.
         form = dict(
-            name='TestPerson', search_text='pting',
-            start='1', batch='1')
+            name="TestPerson", search_text="pting", start="1", batch="1"
+        )
         view = self.create_vocabulary_view(form)
         result = simplejson.loads(view())
-        self.assertEqual(6, result['total_size'])
-        self.assertEqual(1, len(result['entries']))
-        self.assertEqual('pting-2', result['entries'][0]['value'])
+        self.assertEqual(6, result["total_size"])
+        self.assertEqual(1, len(result["entries"]))
+        self.assertEqual("pting-2", result["entries"][0]["value"])
diff --git a/lib/lp/app/browser/tests/test_webservice.py b/lib/lp/app/browser/tests/test_webservice.py
index 7af01a7..2f3850d 100644
--- a/lib/lp/app/browser/tests/test_webservice.py
+++ b/lib/lp/app/browser/tests/test_webservice.py
@@ -23,28 +23,31 @@ class TestXHTMLRepresentations(TestCaseWithFactory):
         eric = self.factory.makePerson()
         # We need something that has an IPersonChoice, a project will do.
         product = self.factory.makeProduct(owner=eric)
-        field = IProduct['owner']
+        field = IProduct["owner"]
         request = get_current_web_service_request()
         renderer = getMultiAdapter(
-            (product, field, request), IFieldHTMLRenderer)
+            (product, field, request), IFieldHTMLRenderer
+        )
         # The representation of a person is the same as a tales
         # PersonFormatter.
         self.assertEqual(format_link(eric), renderer(eric))
 
     def test_text(self):
         # Test the XHTML representation of a text field.
-        text = '\N{SNOWMAN} snowman@xxxxxxxxxxx bug 1'
+        text = "\N{SNOWMAN} snowman@xxxxxxxxxxx bug 1"
         # We need something that has an IPersonChoice, a project will do.
         product = self.factory.makeProduct()
-        field = IProduct['description']
+        field = IProduct["description"]
         request = get_current_web_service_request()
         renderer = getMultiAdapter(
-            (product, field, request), IFieldHTMLRenderer)
+            (product, field, request), IFieldHTMLRenderer
+        )
         # The representation is linkified html.
         self.assertEqual(
-            '<p>\N{SNOWMAN} snowman@xxxxxxxxxxx '
+            "<p>\N{SNOWMAN} snowman@xxxxxxxxxxx "
             '<a href="/bugs/1" class="bug-link">bug 1</a></p>',
-            renderer(text))
+            renderer(text),
+        )
 
 
 class BaseMissingObjectWebService:
@@ -56,123 +59,126 @@ class BaseMissingObjectWebService:
     def test_object_not_found(self):
         """Missing top-level objects generate 404s but not OOPS."""
         webservice = LaunchpadWebServiceCaller(
-            'launchpad-library', 'salgado-change-anything')
-        response = webservice.get('/%s/123456789' % self.object_type)
+            "launchpad-library", "salgado-change-anything"
+        )
+        response = webservice.get("/%s/123456789" % self.object_type)
         self.assertEqual(response.status, 404)
-        self.assertEqual(response.getheader('x-lazr-oopsid'), None)
+        self.assertEqual(response.getheader("x-lazr-oopsid"), None)
 
 
 class TestMissingBranches(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice branches requests."""
 
-    object_type = 'branches'
+    object_type = "branches"
 
 
-class TestMissingBugTrackers(
-    BaseMissingObjectWebService, TestCaseWithFactory):
+class TestMissingBugTrackers(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice bug_trackers requests."""
 
-    object_type = 'bug_trackers'
+    object_type = "bug_trackers"
 
 
 class TestMissingBugs(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice bugs requests."""
 
-    object_type = 'bugs'
+    object_type = "bugs"
 
 
 class TestMissingBuilders(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice builders requests."""
 
-    object_type = 'builders'
+    object_type = "builders"
 
 
 class TestMissingCountries(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice countries requests."""
 
-    object_type = 'countries'
+    object_type = "countries"
 
 
 class TestMissingCves(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice cves requests."""
 
-    object_type = 'cves'
+    object_type = "cves"
 
 
 class TestMissingDistributions(
-    BaseMissingObjectWebService, TestCaseWithFactory):
+    BaseMissingObjectWebService, TestCaseWithFactory
+):
     """Test NotFound for webservice distributions requests."""
 
-    object_type = 'distributions'
+    object_type = "distributions"
 
 
 class TestMissingLanguages(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice launguages requests."""
 
-    object_type = 'languages'
+    object_type = "languages"
 
 
 class TestMissingLiveFSes(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice livefses requests."""
 
-    object_type = 'livefses'
+    object_type = "livefses"
 
 
-class TestMissingPackagesets(
-    BaseMissingObjectWebService, TestCaseWithFactory):
+class TestMissingPackagesets(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice packagesets requests."""
 
-    object_type = 'packagesets'
+    object_type = "packagesets"
 
 
 class TestMissingPeople(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice branches requests."""
 
-    object_type = 'people'
+    object_type = "people"
 
 
 class TestMissingProjectGroups(
-    BaseMissingObjectWebService, TestCaseWithFactory):
+    BaseMissingObjectWebService, TestCaseWithFactory
+):
     """Test NotFound for webservice project_groups requests."""
 
-    object_type = 'project_groups'
+    object_type = "project_groups"
 
 
 class TestMissingProjects(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice projects requests."""
 
-    object_type = 'projects'
+    object_type = "projects"
 
 
 class TestMissingQuestions(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice questions requests."""
 
-    object_type = 'questions'
+    object_type = "questions"
 
 
 class TestMissingSnaps(BaseMissingObjectWebService, TestCaseWithFactory):
     """Test NotFound for webservice snaps requests."""
 
-    object_type = '+snaps'
+    object_type = "+snaps"
 
 
 class TestMissingTemporaryBlobs(
-    BaseMissingObjectWebService, TestCaseWithFactory):
+    BaseMissingObjectWebService, TestCaseWithFactory
+):
     """Test NotFound for webservice temporary_blobs requests."""
 
-    object_type = 'temporary_blobs'
+    object_type = "temporary_blobs"
 
 
 class TestMissingTranslationGroups(
-    BaseMissingObjectWebService, TestCaseWithFactory):
+    BaseMissingObjectWebService, TestCaseWithFactory
+):
     """Test NotFound for webservice translation_groups requests."""
 
-    object_type = 'translation_groups'
+    object_type = "translation_groups"
 
 
 class TestMissingTranslationImportQueueEntries(
-    BaseMissingObjectWebService, TestCaseWithFactory):
-    """Test NotFound for webservice translation_import_queue_entries requests.
-    """
+    BaseMissingObjectWebService, TestCaseWithFactory
+):
+    """Test NotFound for webservice translation_import_queue_entries."""
 
-    object_type = 'translation_import_queue_entries'
+    object_type = "translation_import_queue_entries"
diff --git a/lib/lp/app/browser/vocabulary.py b/lib/lp/app/browser/vocabulary.py
index bf4cdd6..6d11b8a 100644
--- a/lib/lp/app/browser/vocabulary.py
+++ b/lib/lp/app/browser/vocabulary.py
@@ -4,41 +4,35 @@
 """Views which export vocabularies as JSON for widgets."""
 
 __all__ = [
-    'HugeVocabularyJSONView',
-    'IPickerEntrySource',
-    'get_person_picker_entry_metadata',
-    'vocabulary_filters',
-    ]
+    "HugeVocabularyJSONView",
+    "IPickerEntrySource",
+    "get_person_picker_entry_metadata",
+    "vocabulary_filters",
+]
+
+# This registers the registry.
+import zope.vocabularyregistry.registry  # noqa: F401  # isort: split
 
-from lazr.restful.interfaces import IWebServiceClientRequest
 import simplejson
-from zope.component import (
-    adapter,
-    getUtility,
-    )
+from lazr.restful.interfaces import IWebServiceClientRequest
+from zope.component import adapter, getUtility
 from zope.formlib.interfaces import MissingInputError
-from zope.interface import (
-    Attribute,
-    implementer,
-    Interface,
-    )
+from zope.interface import Attribute, Interface, implementer
 from zope.interface.interfaces import ComponentLookupError
 from zope.schema.interfaces import IVocabularyFactory
 from zope.security.interfaces import Unauthorized
-# This registers the registry.
-import zope.vocabularyregistry.registry  # noqa: F401
 
 from lp.app.browser.tales import (
     DateTimeFormatterAPI,
     IRCNicknameFormatterAPI,
     ObjectImageDisplayAPI,
-    )
+)
 from lp.app.errors import UnexpectedFormData
 from lp.code.interfaces.branch import IBranch
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
-    )
+)
 from lp.registry.interfaces.person import IPerson
 from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.projectgroup import IProjectGroup
@@ -51,7 +45,6 @@ from lp.services.webapp.publisher import canonical_url
 from lp.services.webapp.vocabulary import IHugeVocabulary
 from lp.soyuz.interfaces.archive import IArchive
 
-
 # XXX: EdwinGrubbs 2009-07-27 bug=405476
 # This limits the output to one line of text, since the sprite class
 # cannot clip the background image effectively for vocabulary items
@@ -63,27 +56,38 @@ class IPickerEntry(Interface):
     """Additional fields that the vocabulary doesn't provide.
 
     These fields are needed by the Picker Ajax widget."""
-    description = Attribute('Description')
-    image = Attribute('Image URL')
-    css = Attribute('CSS Class')
-    alt_title = Attribute('Alternative title')
-    title_link = Attribute('URL used for anchor on title')
-    details = Attribute('An optional list of information about the entry')
-    alt_title_link = Attribute('URL used for anchor on alt title')
-    link_css = Attribute('CSS Class for links')
-    badges = Attribute('List of badge img attributes')
-    metadata = Attribute('Metadata about the entry')
-    target_type = Attribute('Target data for target picker entries.')
+
+    description = Attribute("Description")
+    image = Attribute("Image URL")
+    css = Attribute("CSS Class")
+    alt_title = Attribute("Alternative title")
+    title_link = Attribute("URL used for anchor on title")
+    details = Attribute("An optional list of information about the entry")
+    alt_title_link = Attribute("URL used for anchor on alt title")
+    link_css = Attribute("CSS Class for links")
+    badges = Attribute("List of badge img attributes")
+    metadata = Attribute("Metadata about the entry")
+    target_type = Attribute("Target data for target picker entries.")
 
 
 @implementer(IPickerEntry)
 class PickerEntry:
     """See `IPickerEntry`."""
 
-    def __init__(self, description=None, image=None, css=None, alt_title=None,
-                 title_link=None, details=None, alt_title_link=None,
-                 link_css='sprite new-window', badges=None, metadata=None,
-                 target_type=None):
+    def __init__(
+        self,
+        description=None,
+        image=None,
+        css=None,
+        alt_title=None,
+        title_link=None,
+        details=None,
+        alt_title_link=None,
+        link_css="sprite new-window",
+        badges=None,
+        metadata=None,
+        target_type=None,
+    ):
         self.description = description
         self.image = image
         self.css = css
@@ -124,11 +128,11 @@ class DefaultPickerEntrySourceAdapter:
         entries = []
         for term_value in term_values:
             extra = PickerEntry()
-            if hasattr(term_value, 'summary'):
+            if hasattr(term_value, "summary"):
                 extra.description = term_value.summary
             display_api = ObjectImageDisplayAPI(term_value)
             image_url = display_api.custom_icon_url() or None
-            css = display_api.sprite_css() or 'sprite bullet'
+            css = display_api.sprite_css() or "sprite bullet"
             if image_url is not None:
                 extra.image = image_url
             else:
@@ -161,21 +165,24 @@ class PersonPickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
                 picker_entry.badges = []
                 for badge_info in badges:
                     picker_entry.badges.append(
-                        dict(url=badge_info.url,
-                             label=badge_info.label,
-                             role=badge_info.role))
+                        dict(
+                            url=badge_info.url,
+                            label=badge_info.label,
+                            role=badge_info.role,
+                        )
+                    )
 
         for person, picker_entry in zip(term_values, picker_entries):
             picker_entry.details = []
 
             if person.preferredemail is not None:
                 if person.hide_email_addresses:
-                    picker_entry.description = '<email address hidden>'
+                    picker_entry.description = "<email address hidden>"
                 else:
                     try:
                         picker_entry.description = person.preferredemail.email
                     except Unauthorized:
-                        picker_entry.description = '<email address hidden>'
+                        picker_entry.description = "<email address hidden>"
 
             picker_entry.metadata = get_person_picker_entry_metadata(person)
             # We will display the person's name (launchpad id) after their
@@ -184,23 +191,29 @@ class PersonPickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
             # We will linkify the person's name so it can be clicked to
             # open the page for that person.
             picker_entry.alt_title_link = canonical_url(
-                                            person, rootsite='mainsite')
+                person, rootsite="mainsite"
+            )
             # We will display the person's irc nick(s) after their email
             # address in the description text.
             irc_nicks = None
             if person.ircnicknames:
                 irc_nicks = ", ".join(
-                    [IRCNicknameFormatterAPI(ircid).displayname()
-                    for ircid in person.ircnicknames])
+                    [
+                        IRCNicknameFormatterAPI(ircid).displayname()
+                        for ircid in person.ircnicknames
+                    ]
+                )
             if irc_nicks:
                 picker_entry.details.append(irc_nicks)
             if person.is_team:
                 picker_entry.details.append(
-                    'Team members: %s' % person.all_member_count)
+                    "Team members: %s" % person.all_member_count
+                )
             else:
                 picker_entry.details.append(
-                    'Member since %s' % DateTimeFormatterAPI(
-                        person.datecreated).date())
+                    "Member since %s"
+                    % DateTimeFormatterAPI(person.datecreated).date()
+                )
         return picker_entries
 
 
@@ -210,8 +223,9 @@ class BranchPickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
 
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
-        entries = (
-            super().getPickerEntries(term_values, context_object, **kwarg))
+        entries = super().getPickerEntries(
+            term_values, context_object, **kwarg
+        )
         for branch, picker_entry in zip(term_values, entries):
             picker_entry.description = branch.bzr_identity
         return entries
@@ -236,60 +250,66 @@ class TargetPickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
 
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
-        entries = (
-            super().getPickerEntries(term_values, context_object, **kwarg))
+        entries = super().getPickerEntries(
+            term_values, context_object, **kwarg
+        )
         for target, picker_entry in zip(term_values, entries):
             picker_entry.description = self.getDescription(target)
             picker_entry.details = []
             summary = picker_entry.description
             if len(summary) > 45:
-                index = summary.rfind(' ', 0, 45)
-                first_line = summary[0:index + 1]
+                index = summary.rfind(" ", 0, 45)
+                first_line = summary[0 : index + 1]
                 second_line = summary[index:]
             else:
                 first_line = summary
-                second_line = ''
+                second_line = ""
 
             if len(second_line) > 90:
-                index = second_line.rfind(' ', 0, 90)
-                second_line = second_line[0:index + 1]
+                index = second_line.rfind(" ", 0, 90)
+                second_line = second_line[0 : index + 1]
             picker_entry.description = first_line
             if second_line:
                 picker_entry.details.append(second_line)
             picker_entry.alt_title = target.name
             picker_entry.alt_title_link = canonical_url(
-                target, rootsite='mainsite')
+                target, rootsite="mainsite"
+            )
             picker_entry.target_type = self.target_type
             maintainer = self.getMaintainer(target)
             if maintainer is not None:
-                picker_entry.details.append(
-                    'Maintainer: %s' % maintainer)
+                picker_entry.details.append("Maintainer: %s" % maintainer)
             commercial_subscription = self.getCommercialSubscription(target)
             if commercial_subscription is not None:
                 picker_entry.details.append(
-                    'Commercial Subscription: %s' % commercial_subscription)
+                    "Commercial Subscription: %s" % commercial_subscription
+                )
         return entries
 
 
 @adapter(ISourcePackageName)
 class SourcePackageNamePickerEntrySourceAdapter(
-                                            DefaultPickerEntrySourceAdapter):
+    DefaultPickerEntrySourceAdapter
+):
     """Adapts ISourcePackageName to IPickerEntrySource."""
 
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
-        entries = (
-            super().getPickerEntries(term_values, context_object, **kwarg))
+        entries = super().getPickerEntries(
+            term_values, context_object, **kwarg
+        )
         for sourcepackagename, picker_entry in zip(term_values, entries):
             descriptions = getSourcePackageDescriptions([sourcepackagename])
             picker_entry.description = descriptions.get(
-                sourcepackagename.name, "Not yet built")
+                sourcepackagename.name, "Not yet built"
+            )
         return entries
 
 
 @adapter(IDistributionSourcePackage)
 class DistributionSourcePackagePickerEntrySourceAdapter(
-    TargetPickerEntrySourceAdapter):
+    TargetPickerEntrySourceAdapter
+):
     """Adapts IDistributionSourcePackage to IPickerEntrySource."""
 
     target_type = "package"
@@ -301,9 +321,9 @@ class DistributionSourcePackagePickerEntrySourceAdapter(
     def getDescription(self, target):
         """See `TargetPickerEntrySource`"""
         if target.binary_names:
-            description = ', '.join(target.binary_names)
+            description = ", ".join(target.binary_names)
         else:
-            description = 'Not yet built.'
+            description = "Not yet built."
         return description
 
     def getPickerEntries(self, term_values, context_object, **kwarg):
@@ -347,11 +367,11 @@ class ProductPickerEntrySourceAdapter(TargetPickerEntrySourceAdapter):
         """See `TargetPickerEntrySource`"""
         if target.commercial_subscription:
             if target.has_current_commercial_subscription:
-                return 'Active'
+                return "Active"
             else:
-                return 'Expired'
+                return "Expired"
         else:
-            return 'None'
+            return "None"
 
 
 @adapter(IDistribution)
@@ -374,11 +394,11 @@ class DistributionPickerEntrySourceAdapter(TargetPickerEntrySourceAdapter):
         """See `TargetPickerEntrySource`"""
         if target.commercial_subscription:
             if target.has_current_commercial_subscription:
-                return 'Active'
+                return "Active"
             else:
-                return 'Expired'
+                return "Expired"
         else:
-            return 'None'
+            return "None"
 
 
 @adapter(IArchive)
@@ -387,8 +407,9 @@ class ArchivePickerEntrySourceAdapter(DefaultPickerEntrySourceAdapter):
 
     def getPickerEntries(self, term_values, context_object, **kwarg):
         """See `IPickerEntrySource`"""
-        entries = (
-            super().getPickerEntries(term_values, context_object, **kwarg))
+        entries = super().getPickerEntries(
+            term_values, context_object, **kwarg
+        )
         for archive, picker_entry in zip(term_values, entries):
             picker_entry.description = archive.reference
         return entries
@@ -400,6 +421,7 @@ class HugeVocabularyJSONView:
     This was needed by the Picker widget, but could be
     useful for other AJAX widgets.
     """
+
     DEFAULT_BATCH_SIZE = 10
 
     def __init__(self, context, request):
@@ -407,20 +429,19 @@ class HugeVocabularyJSONView:
         self.request = request
 
     def __call__(self):
-        name = self.request.form.get('name')
+        name = self.request.form.get("name")
         if name is None:
-            raise MissingInputError('name', '')
+            raise MissingInputError("name", "")
 
-        search_text = self.request.form.get('search_text')
+        search_text = self.request.form.get("search_text")
         if search_text is None:
-            raise MissingInputError('search_text', '')
-        search_filter = self.request.form.get('search_filter')
+            raise MissingInputError("search_text", "")
+        search_filter = self.request.form.get("search_filter")
 
         try:
             factory = getUtility(IVocabularyFactory, name)
         except ComponentLookupError:
-            raise UnexpectedFormData(
-                'Unknown vocabulary %r' % name)
+            raise UnexpectedFormData("Unknown vocabulary %r" % name)
 
         vocabulary = factory(self.context)
 
@@ -460,8 +481,8 @@ class HugeVocabularyJSONView:
         # corresponding picker entries by calling the adapter.
         for adapter_class, term_values in picker_entry_terms.items():
             picker_entries = adapter_cache[adapter_class].getPickerEntries(
-                term_values,
-                self.context)
+                term_values, self.context
+            )
             for term_value, picker_entry in zip(term_values, picker_entries):
                 picker_term_entries[term_value] = picker_entry
 
@@ -472,45 +493,46 @@ class HugeVocabularyJSONView:
             # be passed directly into the REST PATCH call.
             api_request = IWebServiceClientRequest(self.request)
             try:
-                entry['api_uri'] = canonical_url(
-                    term.value, request=api_request,
-                    path_only_if_possible=True)
+                entry["api_uri"] = canonical_url(
+                    term.value, request=api_request, path_only_if_possible=True
+                )
             except NoCanonicalUrl:
                 # The exception is caught, because the api_url is only
                 # needed for inplace editing via a REST call. The
                 # form picker doesn't need the api_url.
-                entry['api_uri'] = 'Could not find canonical url.'
+                entry["api_uri"] = "Could not find canonical url."
             picker_entry = picker_term_entries[term.value]
             if picker_entry.description is not None:
                 if len(picker_entry.description) > MAX_DESCRIPTION_LENGTH:
-                    entry['description'] = (
-                        picker_entry.description[:MAX_DESCRIPTION_LENGTH - 3]
-                        + '...')
+                    entry["description"] = (
+                        picker_entry.description[: MAX_DESCRIPTION_LENGTH - 3]
+                        + "..."
+                    )
                 else:
-                    entry['description'] = picker_entry.description
+                    entry["description"] = picker_entry.description
             if picker_entry.image is not None:
-                entry['image'] = picker_entry.image
+                entry["image"] = picker_entry.image
             if picker_entry.css is not None:
-                entry['css'] = picker_entry.css
+                entry["css"] = picker_entry.css
             if picker_entry.alt_title is not None:
-                entry['alt_title'] = picker_entry.alt_title
+                entry["alt_title"] = picker_entry.alt_title
             if picker_entry.title_link is not None:
-                entry['title_link'] = picker_entry.title_link
+                entry["title_link"] = picker_entry.title_link
             if picker_entry.details is not None:
-                entry['details'] = picker_entry.details
+                entry["details"] = picker_entry.details
             if picker_entry.alt_title_link is not None:
-                entry['alt_title_link'] = picker_entry.alt_title_link
+                entry["alt_title_link"] = picker_entry.alt_title_link
             if picker_entry.link_css is not None:
-                entry['link_css'] = picker_entry.link_css
+                entry["link_css"] = picker_entry.link_css
             if picker_entry.badges:
-                entry['badges'] = picker_entry.badges
+                entry["badges"] = picker_entry.badges
             if picker_entry.metadata is not None:
-                entry['metadata'] = picker_entry.metadata
+                entry["metadata"] = picker_entry.metadata
             if picker_entry.target_type is not None:
-                entry['target_type'] = picker_entry.target_type
+                entry["target_type"] = picker_entry.target_type
             result.append(entry)
 
-        self.request.response.setHeader('Content-type', 'application/json')
+        self.request.response.setHeader("Content-type", "application/json")
         return simplejson.dumps(dict(total_size=total_size, entries=result))
 
 
@@ -522,14 +544,16 @@ def vocabulary_filters(vocabulary):
     # If we have no filters or just the ALL filter, then no filtering
     # support is required.
     filters = []
-    if (len(supported_filters) == 0 or
-       (len(supported_filters) == 1
-        and supported_filters[0].name == 'ALL')):
+    if len(supported_filters) == 0 or (
+        len(supported_filters) == 1 and supported_filters[0].name == "ALL"
+    ):
         return filters
     for filter in supported_filters:
-        filters.append({
-            'name': filter.name,
-            'title': filter.title,
-            'description': filter.description,
-            })
+        filters.append(
+            {
+                "name": filter.name,
+                "title": filter.title,
+                "description": filter.description,
+            }
+        )
     return filters
diff --git a/lib/lp/app/browser/webservice.py b/lib/lp/app/browser/webservice.py
index a0cb712..8439f7e 100644
--- a/lib/lp/app/browser/webservice.py
+++ b/lib/lp/app/browser/webservice.py
@@ -9,12 +9,9 @@ from lazr.restful.interfaces import (
     IFieldHTMLRenderer,
     IReference,
     IWebServiceClientRequest,
-    )
+)
 from zope import component
-from zope.interface import (
-    implementer,
-    Interface,
-    )
+from zope.interface import Interface, implementer
 from zope.schema.interfaces import IText
 
 from lp.app.browser.stringformatter import FormattersAPI
@@ -30,9 +27,10 @@ def reference_xhtml_representation(context, field, request):
         # The value is a webservice link to the object, we want field value.
         obj = getattr(context, field.__name__, None)
         try:
-            return format_link(obj, empty_value='')
+            return format_link(obj, empty_value="")
         except NotImplementedError:
             return value
+
     return render
 
 
@@ -41,5 +39,7 @@ def reference_xhtml_representation(context, field, request):
 def text_xhtml_representation(context, field, request):
     """Render text as XHTML using the webservice."""
     return lambda text: (
-        '' if text is None
-        else FormattersAPI(text).text_to_html(linkify_text=True))
+        ""
+        if text is None
+        else FormattersAPI(text).text_to_html(linkify_text=True)
+    )
diff --git a/lib/lp/app/enums.py b/lib/lp/app/enums.py
index c9ebe8f..32cf371 100644
--- a/lib/lp/app/enums.py
+++ b/lib/lp/app/enums.py
@@ -4,24 +4,21 @@
 """Enumerations and related utilities used in the lp/app modules."""
 
 __all__ = [
-    'FREE_INFORMATION_TYPES',
-    'FREE_PRIVATE_INFORMATION_TYPES',
-    'InformationType',
-    'NON_EMBARGOED_INFORMATION_TYPES',
-    'PRIVATE_INFORMATION_TYPES',
-    'PROPRIETARY_INFORMATION_TYPES',
-    'PILLAR_INFORMATION_TYPES',
-    'PUBLIC_INFORMATION_TYPES',
-    'PUBLIC_PROPRIETARY_INFORMATION_TYPES',
-    'SECURITY_INFORMATION_TYPES',
-    'ServiceUsage',
-    'service_uses_launchpad',
-    ]
-
-from lazr.enum import (
-    DBEnumeratedType,
-    DBItem,
-    )
+    "FREE_INFORMATION_TYPES",
+    "FREE_PRIVATE_INFORMATION_TYPES",
+    "InformationType",
+    "NON_EMBARGOED_INFORMATION_TYPES",
+    "PRIVATE_INFORMATION_TYPES",
+    "PROPRIETARY_INFORMATION_TYPES",
+    "PILLAR_INFORMATION_TYPES",
+    "PUBLIC_INFORMATION_TYPES",
+    "PUBLIC_PROPRIETARY_INFORMATION_TYPES",
+    "SECURITY_INFORMATION_TYPES",
+    "ServiceUsage",
+    "service_uses_launchpad",
+]
+
+from lazr.enum import DBEnumeratedType, DBItem
 
 
 class InformationType(DBEnumeratedType):
@@ -31,73 +28,107 @@ class InformationType(DBEnumeratedType):
     Launchpad artifacts, including bugs and branches.
     """
 
-    PUBLIC = DBItem(1, """
+    PUBLIC = DBItem(
+        1,
+        """
         Public
 
         Everyone can see this information.
-        """)
+        """,
+    )
 
-    PUBLICSECURITY = DBItem(2, """
+    PUBLICSECURITY = DBItem(
+        2,
+        """
         Public Security
 
         Everyone can see this security related information.
-        """)
+        """,
+    )
 
-    PRIVATESECURITY = DBItem(3, """
+    PRIVATESECURITY = DBItem(
+        3,
+        """
         Private Security
 
        Only the security group can see this information.
-        """)
+        """,
+    )
 
-    USERDATA = DBItem(4, """
+    USERDATA = DBItem(
+        4,
+        """
         Private
 
         Only shared with users permitted to see private user information.
-        """)
+        """,
+    )
 
-    PROPRIETARY = DBItem(5, """
+    PROPRIETARY = DBItem(
+        5,
+        """
         Proprietary
 
         Only shared with users permitted to see proprietary information.
-        """)
+        """,
+    )
 
-    EMBARGOED = DBItem(6, """
+    EMBARGOED = DBItem(
+        6,
+        """
         Embargoed
 
         Only shared with users permitted to see embargoed information.
-        """)
+        """,
+    )
+
 
 PUBLIC_INFORMATION_TYPES = (
-    InformationType.PUBLIC, InformationType.PUBLICSECURITY)
+    InformationType.PUBLIC,
+    InformationType.PUBLICSECURITY,
+)
 
 PRIVATE_INFORMATION_TYPES = (
-    InformationType.PRIVATESECURITY, InformationType.USERDATA,
-    InformationType.PROPRIETARY, InformationType.EMBARGOED)
+    InformationType.PRIVATESECURITY,
+    InformationType.USERDATA,
+    InformationType.PROPRIETARY,
+    InformationType.EMBARGOED,
+)
 
-NON_EMBARGOED_INFORMATION_TYPES = (
-    PUBLIC_INFORMATION_TYPES +
-    (InformationType.PRIVATESECURITY, InformationType.USERDATA,
-     InformationType.PROPRIETARY))
+NON_EMBARGOED_INFORMATION_TYPES = PUBLIC_INFORMATION_TYPES + (
+    InformationType.PRIVATESECURITY,
+    InformationType.USERDATA,
+    InformationType.PROPRIETARY,
+)
 
 SECURITY_INFORMATION_TYPES = (
-    InformationType.PUBLICSECURITY, InformationType.PRIVATESECURITY)
+    InformationType.PUBLICSECURITY,
+    InformationType.PRIVATESECURITY,
+)
 
 FREE_PRIVATE_INFORMATION_TYPES = (
-    InformationType.PRIVATESECURITY, InformationType.USERDATA)
+    InformationType.PRIVATESECURITY,
+    InformationType.USERDATA,
+)
 
 FREE_INFORMATION_TYPES = (
-    PUBLIC_INFORMATION_TYPES + FREE_PRIVATE_INFORMATION_TYPES)
+    PUBLIC_INFORMATION_TYPES + FREE_PRIVATE_INFORMATION_TYPES
+)
 
 PROPRIETARY_INFORMATION_TYPES = (
-    InformationType.PROPRIETARY, InformationType.EMBARGOED)
+    InformationType.PROPRIETARY,
+    InformationType.EMBARGOED,
+)
 
 # The information types unrelated to user data or security
 PUBLIC_PROPRIETARY_INFORMATION_TYPES = (
-    (InformationType.PUBLIC,) + PROPRIETARY_INFORMATION_TYPES
-)
+    InformationType.PUBLIC,
+) + PROPRIETARY_INFORMATION_TYPES
 
 PILLAR_INFORMATION_TYPES = (
-    InformationType.PUBLIC, InformationType.PROPRIETARY)
+    InformationType.PUBLIC,
+    InformationType.PROPRIETARY,
+)
 
 
 class ServiceUsage(DBEnumeratedType):
@@ -107,30 +138,42 @@ class ServiceUsage(DBEnumeratedType):
     bug tracking, translations, code hosting, blueprint, and answers.
     """
 
-    UNKNOWN = DBItem(10, """
+    UNKNOWN = DBItem(
+        10,
+        """
     Unknown
 
     The maintainers have not indicated usage.  This value is the default for
     new pillars.
-    """)
+    """,
+    )
 
-    LAUNCHPAD = DBItem(20, """
+    LAUNCHPAD = DBItem(
+        20,
+        """
     Launchpad
 
     Launchpad is used to provide this service.
-    """)
+    """,
+    )
 
-    EXTERNAL = DBItem(30, """
+    EXTERNAL = DBItem(
+        30,
+        """
     External
 
     The service is provided external to Launchpad.
-    """)
+    """,
+    )
 
-    NOT_APPLICABLE = DBItem(40, """
+    NOT_APPLICABLE = DBItem(
+        40,
+        """
     Not Applicable
 
     The pillar does not use this type of service in Launchpad or externally.
-    """)
+    """,
+    )
 
 
 def service_uses_launchpad(usage_enum):
diff --git a/lib/lp/app/errors.py b/lib/lp/app/errors.py
index 9c16fa0..f3e5c91 100644
--- a/lib/lp/app/errors.py
+++ b/lib/lp/app/errors.py
@@ -4,26 +4,23 @@
 """Cross application type errors for launchpad."""
 
 __all__ = [
-    'GoneError',
-    'IncompatibleArguments',
-    'NameLookupFailed',
-    'NotFoundError',
-    'POSTToNonCanonicalURL',
-    'ServiceUsageForbidden',
-    'SubscriptionPrivacyViolation',
-    'TeamAccountNotClosable',
-    'TranslationUnavailable',
-    'UnexpectedFormData',
-    'UserCannotUnsubscribePerson',
-    ]
+    "GoneError",
+    "IncompatibleArguments",
+    "NameLookupFailed",
+    "NotFoundError",
+    "POSTToNonCanonicalURL",
+    "ServiceUsageForbidden",
+    "SubscriptionPrivacyViolation",
+    "TeamAccountNotClosable",
+    "TranslationUnavailable",
+    "UnexpectedFormData",
+    "UserCannotUnsubscribePerson",
+]
 
 import http.client
 
 from lazr.restful.declarations import error_status
-from zope.security.interfaces import (
-    ForbiddenAttribute,
-    Unauthorized,
-    )
+from zope.security.interfaces import ForbiddenAttribute, Unauthorized
 
 
 class TranslationUnavailable(Exception):
diff --git a/lib/lp/app/interfaces/headings.py b/lib/lp/app/interfaces/headings.py
index 5c50232..e3ca547 100644
--- a/lib/lp/app/interfaces/headings.py
+++ b/lib/lp/app/interfaces/headings.py
@@ -4,9 +4,9 @@
 """Marker interfaces that define how to generate headings for a view."""
 
 __all__ = [
-    'IHeadingBreadcrumb',
-    'IMajorHeadingView',
-    ]
+    "IHeadingBreadcrumb",
+    "IMajorHeadingView",
+]
 
 
 from zope.interface import Interface
diff --git a/lib/lp/app/interfaces/informationtype.py b/lib/lp/app/interfaces/informationtype.py
index dc100fd..5ea95df 100644
--- a/lib/lp/app/interfaces/informationtype.py
+++ b/lib/lp/app/interfaces/informationtype.py
@@ -2,8 +2,8 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'IInformationType',
-    ]
+    "IInformationType",
+]
 
 from lazr.restful.declarations import exported
 from zope.schema import Choice
@@ -15,9 +15,11 @@ from lp.app.interfaces.launchpad import IPrivacy
 
 class IInformationType(IPrivacy):
 
-    information_type = exported(Choice(
-        title=_('Information Type'),
-        vocabulary=InformationType,
-        required=True,
-        description=_('The type of data contained in this item.')
-        ))
+    information_type = exported(
+        Choice(
+            title=_("Information Type"),
+            vocabulary=InformationType,
+            required=True,
+            description=_("The type of data contained in this item."),
+        )
+    )
diff --git a/lib/lp/app/interfaces/launchpad.py b/lib/lp/app/interfaces/launchpad.py
index b140b6b..767f45a 100644
--- a/lib/lp/app/interfaces/launchpad.py
+++ b/lib/lp/app/interfaces/launchpad.py
@@ -7,25 +7,19 @@ Note that these are not interfaces to application content objects.
 """
 
 __all__ = [
-    'IHasIcon',
-    'IHasLogo',
-    'IHasMugshot',
-    'IHeadingContext',
-    'ILaunchpadCelebrities',
-    'ILaunchpadUsage',
-    'IPrivacy',
-    'IServiceUsage',
-    ]
+    "IHasIcon",
+    "IHasLogo",
+    "IHasMugshot",
+    "IHeadingContext",
+    "ILaunchpadCelebrities",
+    "ILaunchpadUsage",
+    "IPrivacy",
+    "IServiceUsage",
+]
 
 from lazr.restful.declarations import exported
-from zope.interface import (
-    Attribute,
-    Interface,
-    )
-from zope.schema import (
-    Bool,
-    Choice,
-    )
+from zope.interface import Attribute, Interface
+from zope.schema import Bool, Choice
 
 from lp import _
 from lp.app.enums import ServiceUsage
@@ -36,6 +30,7 @@ class ILaunchpadCelebrities(Interface):
 
     Celebrities are SQLBase instances that have a well known name.
     """
+
     admin = Attribute("The 'admins' team.")
     software_center_agent = Attribute("The Software Center Agent.")
     bug_importer = Attribute("The bug importer.")
@@ -67,8 +62,7 @@ class ILaunchpadCelebrities(Interface):
     vcs_imports = Attribute("The 'vcs-imports' team.")
 
     def isCelebrityPerson(name):
-        """Return true if there is an IPerson celebrity with the given name.
-        """
+        """Return true if there is an IPerson celebrity with the given name."""
 
     def clearCache():
         """Clear any cached celebrities."""
@@ -82,57 +76,74 @@ class IServiceUsage(Interface):
     # implies an actual location not an answer of "Launchpad, externally, or
     # neither."
     answers_usage = Choice(
-        title=_('Type of service for answers application'),
+        title=_("Type of service for answers application"),
         description=_("Where does this pillar have an Answers forum?"),
         default=ServiceUsage.UNKNOWN,
-        vocabulary=ServiceUsage)
+        vocabulary=ServiceUsage,
+    )
     blueprints_usage = Choice(
-        title=_('Type of service for blueprints application'),
+        title=_("Type of service for blueprints application"),
         description=_("Where does this pillar host blueprints?"),
         default=ServiceUsage.UNKNOWN,
-