← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
lp.bugs: Apply black

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/425872
-- 
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-bugs into launchpad:master.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 5030b87..accdfac 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -64,3 +64,5 @@ c606443bdb2f342593c9a7c9437cb70c01f85f29
 01c7f7112b20dab5c48373339d530a39f0dc859b
 # apply black to lp.blueprints
 6178b1869f90f9bf1eb9ed050154888b523c53c5
+# apply black to lp.bugs
+2edd6d52841e03ba11ad431e40828f2f0b6a7e17
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index dbcf289..a1c7bb0 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -46,6 +46,7 @@ repos:
             |archivepublisher
             |archiveuploader
             |blueprints
+            |bugs
           )/
 -   repo: https://github.com/PyCQA/isort
     rev: 5.9.2
@@ -68,6 +69,7 @@ repos:
             |archivepublisher
             |archiveuploader
             |blueprints
+            |bugs
           )/
     -   id: isort
         alias: isort-black
@@ -80,6 +82,7 @@ repos:
             |archivepublisher
             |archiveuploader
             |blueprints
+            |bugs
           )/
 -   repo: https://github.com/PyCQA/flake8
     rev: 3.9.2
diff --git a/lib/lp/bugs/adapters/bug.py b/lib/lp/bugs/adapters/bug.py
index 831f312..ce91bd9 100644
--- a/lib/lp/bugs/adapters/bug.py
+++ b/lib/lp/bugs/adapters/bug.py
@@ -4,10 +4,10 @@
 """Resources having to do with Launchpad bugs."""
 
 __all__ = [
-    'bugcomment_to_entry',
-    'bugtask_to_privacy',
-    'convert_to_information_type',
-    ]
+    "bugcomment_to_entry",
+    "bugtask_to_privacy",
+    "convert_to_information_type",
+]
 
 from lazr.restful.interfaces import IEntry
 from zope.component import getMultiAdapter
@@ -22,7 +22,8 @@ def bugcomment_to_entry(comment, version):
     real IMessage instances but IBugComment.
     """
     return getMultiAdapter(
-        (comment.bugtask.bug.messages[comment.index], version), IEntry)
+        (comment.bugtask.bug.messages[comment.index], version), IEntry
+    )
 
 
 def bugtask_to_privacy(bugtask):
diff --git a/lib/lp/bugs/adapters/bugchange.py b/lib/lp/bugs/adapters/bugchange.py
index 714353b..c7babdc 100644
--- a/lib/lp/bugs/adapters/bugchange.py
+++ b/lib/lp/bugs/adapters/bugchange.py
@@ -4,89 +4,85 @@
 """Implementations for bug changes."""
 
 __all__ = [
-    'ATTACHMENT_ADDED',
-    'ATTACHMENT_REMOVED',
-    'BRANCH_LINKED',
-    'BRANCH_UNLINKED',
-    'BranchLinkedToBug',
-    'BranchUnlinkedFromBug',
-    'BUG_WATCH_ADDED',
-    'BUG_WATCH_REMOVED',
-    'BugAttachmentChange',
-    'BugConvertedToQuestion',
-    'BugDescriptionChange',
-    'BugDuplicateChange',
-    'BugInformationTypeChange',
-    'BugLocked',
-    'BugLockReasonSet',
-    'BugTagsChange',
-    'BugTaskAdded',
-    'BugTaskAssigneeChange',
-    'BugTaskBugWatchChange',
-    'BugTaskDeleted',
-    'BugTaskImportanceChange',
-    'BugTaskMilestoneChange',
-    'BugTaskStatusChange',
-    'BugTaskTargetChange',
-    'BugTitleChange',
-    'BugUnlocked',
-    'BugWatchAdded',
-    'BugWatchRemoved',
-    'CHANGED_DUPLICATE_MARKER',
-    'CVE_LINKED',
-    'CVE_UNLINKED',
-    'CveLinkedToBug',
-    'CveUnlinkedFromBug',
-    'get_bug_change_class',
-    'get_bug_changes',
-    'MARKED_AS_DUPLICATE',
-    'MERGE_PROPOSAL_LINKED',
-    'MERGE_PROPOSAL_UNLINKED',
-    'MergeProposalLinkedToBug',
-    'MergeProposalUnlinkedFromBug',
-    'REMOVED_DUPLICATE_MARKER',
-    'REMOVED_SUBSCRIBER',
-    'SeriesNominated',
-    'UnsubscribedFromBug',
-    ]
+    "ATTACHMENT_ADDED",
+    "ATTACHMENT_REMOVED",
+    "BRANCH_LINKED",
+    "BRANCH_UNLINKED",
+    "BranchLinkedToBug",
+    "BranchUnlinkedFromBug",
+    "BUG_WATCH_ADDED",
+    "BUG_WATCH_REMOVED",
+    "BugAttachmentChange",
+    "BugConvertedToQuestion",
+    "BugDescriptionChange",
+    "BugDuplicateChange",
+    "BugInformationTypeChange",
+    "BugLocked",
+    "BugLockReasonSet",
+    "BugTagsChange",
+    "BugTaskAdded",
+    "BugTaskAssigneeChange",
+    "BugTaskBugWatchChange",
+    "BugTaskDeleted",
+    "BugTaskImportanceChange",
+    "BugTaskMilestoneChange",
+    "BugTaskStatusChange",
+    "BugTaskTargetChange",
+    "BugTitleChange",
+    "BugUnlocked",
+    "BugWatchAdded",
+    "BugWatchRemoved",
+    "CHANGED_DUPLICATE_MARKER",
+    "CVE_LINKED",
+    "CVE_UNLINKED",
+    "CveLinkedToBug",
+    "CveUnlinkedFromBug",
+    "get_bug_change_class",
+    "get_bug_changes",
+    "MARKED_AS_DUPLICATE",
+    "MERGE_PROPOSAL_LINKED",
+    "MERGE_PROPOSAL_UNLINKED",
+    "MergeProposalLinkedToBug",
+    "MergeProposalUnlinkedFromBug",
+    "REMOVED_DUPLICATE_MARKER",
+    "REMOVED_SUBSCRIBER",
+    "SeriesNominated",
+    "UnsubscribedFromBug",
+]
 
 from textwrap import dedent
 
 from zope.interface import implementer
 from zope.security.proxy import isinstance as zope_isinstance
 
-from lp.bugs.enums import (
-    BugLockStatus,
-    BugNotificationLevel,
-    )
+from lp.bugs.enums import BugLockStatus, BugNotificationLevel
 from lp.bugs.interfaces.bugchange import IBugChange
 from lp.bugs.interfaces.bugtask import (
-    IBugTask,
     RESOLVED_BUGTASK_STATUSES,
     UNRESOLVED_BUGTASK_STATUSES,
-    )
+    IBugTask,
+)
 from lp.registry.interfaces.product import IProduct
 from lp.services.librarian.browser import ProxiedLibraryFileAlias
 from lp.services.webapp.publisher import canonical_url
 
-
 # These are used lp.bugs.model.bugactivity.BugActivity.attribute to normalize
 # the output from these change objects into the attribute that actually
 # changed.  It is fragile, but a reasonable incremental step.
 ATTACHMENT_ADDED = "attachment added"
 ATTACHMENT_REMOVED = "attachment removed"
-BRANCH_LINKED = 'branch linked'
-BRANCH_UNLINKED = 'branch unlinked'
-BUG_WATCH_ADDED = 'bug watch added'
-BUG_WATCH_REMOVED = 'bug watch removed'
-CHANGED_DUPLICATE_MARKER = 'changed duplicate marker'
-CVE_LINKED = 'cve linked'
-CVE_UNLINKED = 'cve unlinked'
-MARKED_AS_DUPLICATE = 'marked as duplicate'
-MERGE_PROPOSAL_LINKED = 'merge proposal linked'
-MERGE_PROPOSAL_UNLINKED = 'merge proposal unlinked'
-REMOVED_DUPLICATE_MARKER = 'removed duplicate marker'
-REMOVED_SUBSCRIBER = 'removed subscriber'
+BRANCH_LINKED = "branch linked"
+BRANCH_UNLINKED = "branch unlinked"
+BUG_WATCH_ADDED = "bug watch added"
+BUG_WATCH_REMOVED = "bug watch removed"
+CHANGED_DUPLICATE_MARKER = "changed duplicate marker"
+CVE_LINKED = "cve linked"
+CVE_UNLINKED = "cve unlinked"
+MARKED_AS_DUPLICATE = "marked as duplicate"
+MERGE_PROPOSAL_LINKED = "merge proposal linked"
+MERGE_PROPOSAL_UNLINKED = "merge proposal unlinked"
+REMOVED_DUPLICATE_MARKER = "removed duplicate marker"
+REMOVED_SUBSCRIBER = "removed subscriber"
 
 
 class NoBugChangeFoundError(Exception):
@@ -106,7 +102,8 @@ def get_bug_change_class(obj, field_name):
     except KeyError:
         raise NoBugChangeFoundError(
             "Unable to find a suitable BugChange for field '%s' on object "
-            "%s" % (field_name, obj))
+            "%s" % (field_name, obj)
+        )
 
 
 def get_bug_changes(bug_delta):
@@ -114,15 +111,25 @@ def get_bug_changes(bug_delta):
     # The order of the field names in this list is important; this is
     # the order in which changes will appear both in the bug activity
     # log and in notification emails.
-    bug_change_field_names = ['duplicateof', 'title', 'description',
-        'information_type', 'tags', 'attachment']
+    bug_change_field_names = [
+        "duplicateof",
+        "title",
+        "description",
+        "information_type",
+        "tags",
+        "attachment",
+    ]
     for field_name in bug_change_field_names:
         field_delta = getattr(bug_delta, field_name)
         if field_delta is not None:
             bug_change_class = get_bug_change_class(bug_delta.bug, field_name)
             yield bug_change_class(
-                when=None, person=bug_delta.user, what_changed=field_name,
-                old_value=field_delta['old'], new_value=field_delta['new'])
+                when=None,
+                person=bug_delta.user,
+                what_changed=field_name,
+                old_value=field_delta["old"],
+                new_value=field_delta["new"],
+            )
 
     if bug_delta.bugtask_deltas is not None:
         bugtask_deltas = bug_delta.bugtask_deltas
@@ -133,21 +140,30 @@ def get_bug_changes(bug_delta):
 
         # The order here is important; see bug_change_field_names.
         bugtask_change_field_names = [
-            'target', 'importance', 'status', 'milestone', 'bugwatch',
-            'assignee', 'importance_explanation', 'status_explanation',
-            ]
+            "target",
+            "importance",
+            "status",
+            "milestone",
+            "bugwatch",
+            "assignee",
+            "importance_explanation",
+            "status_explanation",
+        ]
         for bugtask_delta in bugtask_deltas:
             for field_name in bugtask_change_field_names:
                 field_delta = getattr(bugtask_delta, field_name)
                 if field_delta is not None:
                     bug_change_class = get_bug_change_class(
-                        bugtask_delta.bugtask, field_name)
+                        bugtask_delta.bugtask, field_name
+                    )
                     yield bug_change_class(
                         bug_task=bugtask_delta.bugtask,
-                        when=None, person=bug_delta.user,
+                        when=None,
+                        person=bug_delta.user,
                         what_changed=field_name,
-                        old_value=field_delta['old'],
-                        new_value=field_delta['new'])
+                        old_value=field_delta["old"],
+                        new_value=field_delta["new"],
+                    )
 
 
 @implementer(IBugChange)
@@ -182,10 +198,10 @@ class AttributeChange(BugChangeBase):
     def getBugActivity(self):
         """Return the BugActivity data for the textual change."""
         return {
-            'newvalue': self.new_value,
-            'oldvalue': self.old_value,
-            'whatchanged': self.what_changed,
-            }
+            "newvalue": self.new_value,
+            "oldvalue": self.old_value,
+            "whatchanged": self.what_changed,
+        }
 
 
 class UnsubscribedFromBug(BugChangeBase):
@@ -194,20 +210,20 @@ class UnsubscribedFromBug(BugChangeBase):
     def __init__(self, when, person, unsubscribed_user, **kwargs):
         super().__init__(when, person)
         self.unsubscribed_user = unsubscribed_user
-        self.send_notification = kwargs.get('send_notification', False)
-        self.notification_text = kwargs.get('notification_text')
+        self.send_notification = kwargs.get("send_notification", False)
+        self.notification_text = kwargs.get("notification_text")
 
     def getBugActivity(self):
         """See `IBugChange`."""
         return dict(
-            whatchanged='%s %s' % (
-                REMOVED_SUBSCRIBER,
-                self.unsubscribed_user.displayname))
+            whatchanged="%s %s"
+            % (REMOVED_SUBSCRIBER, self.unsubscribed_user.displayname)
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
         if self.send_notification and self.notification_text:
-            return {'text': '** %s' % self.notification_text}
+            return {"text": "** %s" % self.notification_text}
         else:
             return None
 
@@ -222,16 +238,17 @@ class BugConvertedToQuestion(BugChangeBase):
     def getBugActivity(self):
         """See `IBugChange`."""
         return dict(
-            whatchanged='converted to question',
-            newvalue=str(self.question.id))
+            whatchanged="converted to question", newvalue=str(self.question.id)
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
         return {
-            'text': (
-                '** Converted to question:\n'
-                '   %s' % canonical_url(self.question)),
-            }
+            "text": (
+                "** Converted to question:\n"
+                "   %s" % canonical_url(self.question)
+            ),
+        }
 
 
 class BugTaskAdded(BugChangeBase):
@@ -246,30 +263,31 @@ class BugTaskAdded(BugChangeBase):
     def getBugActivity(self):
         """See `IBugChange`."""
         return dict(
-            whatchanged='bug task added',
-            newvalue=self.bug_task.bugtargetname)
+            whatchanged="bug task added", newvalue=self.bug_task.bugtargetname
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
         lines = []
         if self.bug_task.bugwatch:
-            lines.append("** Also affects: %s via" % (
-                self.bug_task.bugtargetname))
+            lines.append(
+                "** Also affects: %s via" % (self.bug_task.bugtargetname)
+            )
             lines.append("   %s" % self.bug_task.bugwatch.url)
         else:
-            lines.append("** Also affects: %s" % (
-                self.bug_task.bugtargetname))
-        lines.append("%13s: %s" % (
-            "Importance", self.bug_task.importance.title))
+            lines.append("** Also affects: %s" % (self.bug_task.bugtargetname))
+        lines.append(
+            "%13s: %s" % ("Importance", self.bug_task.importance.title)
+        )
         if self.bug_task.assignee:
             assignee = self.bug_task.assignee
-            lines.append("%13s: %s" % (
-                "Assignee", assignee.unique_displayname))
-        lines.append("%13s: %s" % (
-            "Status", self.bug_task.status.title))
+            lines.append(
+                "%13s: %s" % ("Assignee", assignee.unique_displayname)
+            )
+        lines.append("%13s: %s" % ("Status", self.bug_task.status.title))
         return {
-            'text': '\n'.join(lines),
-            }
+            "text": "\n".join(lines),
+        }
 
 
 class BugTaskDeleted(BugChangeBase):
@@ -283,16 +301,13 @@ class BugTaskDeleted(BugChangeBase):
 
     def getBugActivity(self):
         """See `IBugChange`."""
-        return dict(
-            whatchanged='bug task deleted',
-            oldvalue=self.targetname)
+        return dict(whatchanged="bug task deleted", oldvalue=self.targetname)
 
     def getBugNotification(self):
         """See `IBugChange`."""
         return {
-            'text': (
-                "** No longer affects: %s" % self.targetname),
-            }
+            "text": ("** No longer affects: %s" % self.targetname),
+        }
 
 
 class SeriesNominated(BugChangeBase):
@@ -305,8 +320,9 @@ class SeriesNominated(BugChangeBase):
     def getBugActivity(self):
         """See `IBugChange`."""
         return dict(
-            whatchanged='nominated for series',
-            newvalue=self.series.bugtargetname)
+            whatchanged="nominated for series",
+            newvalue=self.series.bugtargetname,
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
@@ -322,19 +338,21 @@ class BugWatchAdded(BugChangeBase):
 
     def getBugActivity(self):
         """See `IBugChange`."""
-        return dict(
-            whatchanged=BUG_WATCH_ADDED,
-            newvalue=self.bug_watch.url)
+        return dict(whatchanged=BUG_WATCH_ADDED, newvalue=self.bug_watch.url)
 
     def getBugNotification(self):
         """See `IBugChange`."""
         return {
-            'text': (
+            "text": (
                 "** Bug watch added: %s #%s\n"
-                "   %s" % (
-                    self.bug_watch.bugtracker.title, self.bug_watch.remotebug,
-                    self.bug_watch.url)),
-            }
+                "   %s"
+                % (
+                    self.bug_watch.bugtracker.title,
+                    self.bug_watch.remotebug,
+                    self.bug_watch.url,
+                )
+            ),
+        }
 
 
 class BugWatchRemoved(BugChangeBase):
@@ -346,19 +364,21 @@ class BugWatchRemoved(BugChangeBase):
 
     def getBugActivity(self):
         """See `IBugChange`."""
-        return dict(
-            whatchanged=BUG_WATCH_REMOVED,
-            oldvalue=self.bug_watch.url)
+        return dict(whatchanged=BUG_WATCH_REMOVED, oldvalue=self.bug_watch.url)
 
     def getBugNotification(self):
         """See `IBugChange`."""
         return {
-            'text': (
+            "text": (
                 "** Bug watch removed: %s #%s\n"
-                "   %s" % (
-                    self.bug_watch.bugtracker.title, self.bug_watch.remotebug,
-                    self.bug_watch.url)),
-            }
+                "   %s"
+                % (
+                    self.bug_watch.bugtracker.title,
+                    self.bug_watch.remotebug,
+                    self.bug_watch.url,
+                )
+            ),
+        }
 
 
 class BranchLinkedToBug(BugChangeBase):
@@ -374,14 +394,14 @@ class BranchLinkedToBug(BugChangeBase):
         if self.branch.private:
             return None
         return dict(
-            whatchanged=BRANCH_LINKED,
-            newvalue=self.branch.bzr_identity)
+            whatchanged=BRANCH_LINKED, newvalue=self.branch.bzr_identity
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
         if self.branch.private or self.bug.is_complete:
             return None
-        return {'text': '** Branch linked: %s' % self.branch.bzr_identity}
+        return {"text": "** Branch linked: %s" % self.branch.bzr_identity}
 
 
 class BranchUnlinkedFromBug(BugChangeBase):
@@ -397,14 +417,14 @@ class BranchUnlinkedFromBug(BugChangeBase):
         if self.branch.private:
             return None
         return dict(
-            whatchanged=BRANCH_UNLINKED,
-            oldvalue=self.branch.bzr_identity)
+            whatchanged=BRANCH_UNLINKED, oldvalue=self.branch.bzr_identity
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
         if self.branch.private or self.bug.is_complete:
             return None
-        return {'text': '** Branch unlinked: %s' % self.branch.bzr_identity}
+        return {"text": "** Branch unlinked: %s" % self.branch.bzr_identity}
 
 
 class MergeProposalLinkedToBug(BugChangeBase):
@@ -421,17 +441,19 @@ class MergeProposalLinkedToBug(BugChangeBase):
             return None
         return dict(
             whatchanged=MERGE_PROPOSAL_LINKED,
-            newvalue=canonical_url(self.merge_proposal))
+            newvalue=canonical_url(self.merge_proposal),
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
         if self.merge_proposal.private or self.bug.is_complete:
             return None
         return {
-            'text': (
-                '** Merge proposal linked:\n'
-                '   %s' % canonical_url(self.merge_proposal)),
-            }
+            "text": (
+                "** Merge proposal linked:\n"
+                "   %s" % canonical_url(self.merge_proposal)
+            ),
+        }
 
 
 class MergeProposalUnlinkedFromBug(BugChangeBase):
@@ -448,17 +470,19 @@ class MergeProposalUnlinkedFromBug(BugChangeBase):
             return None
         return dict(
             whatchanged=MERGE_PROPOSAL_UNLINKED,
-            oldvalue=canonical_url(self.merge_proposal))
+            oldvalue=canonical_url(self.merge_proposal),
+        )
 
     def getBugNotification(self):
         """See `IBugChange`."""
         if self.merge_proposal.private or self.bug.is_complete:
             return None
         return {
-            'text': (
-                '** Merge proposal unlinked:\n'
-                '   %s' % canonical_url(self.merge_proposal)),
-            }
+            "text": (
+                "** Merge proposal unlinked:\n"
+                "   %s" % canonical_url(self.merge_proposal)
+            ),
+        }
 
 
 class BugDescriptionChange(AttributeChange):
@@ -466,23 +490,26 @@ class BugDescriptionChange(AttributeChange):
 
     def getBugNotification(self):
         from lp.services.mail.notification import get_unified_diff
-        description_diff = get_unified_diff(
-            self.old_value, self.new_value, 72)
-        notification_text = (
-            "** Description changed:\n\n%s" % description_diff)
-        return {'text': notification_text}
+
+        description_diff = get_unified_diff(self.old_value, self.new_value, 72)
+        notification_text = "** Description changed:\n\n%s" % description_diff
+        return {"text": notification_text}
 
 
 def _is_status_change_lifecycle_change(old_status, new_status):
     """Is a status change a lifecycle change?"""
     # Bug is moving from one of unresolved bug statuses (like
     # 'in progress') to one of resolved ('fix released').
-    bug_is_closed = (old_status in UNRESOLVED_BUGTASK_STATUSES and
-                     new_status in RESOLVED_BUGTASK_STATUSES)
+    bug_is_closed = (
+        old_status in UNRESOLVED_BUGTASK_STATUSES
+        and new_status in RESOLVED_BUGTASK_STATUSES
+    )
 
     # Bug is moving back from one of resolved bug statuses (reopening).
-    bug_is_reopened = (old_status in RESOLVED_BUGTASK_STATUSES and
-                       new_status in UNRESOLVED_BUGTASK_STATUSES)
+    bug_is_reopened = (
+        old_status in RESOLVED_BUGTASK_STATUSES
+        and new_status in UNRESOLVED_BUGTASK_STATUSES
+    )
     return bug_is_closed or bug_is_reopened
 
 
@@ -498,17 +525,19 @@ class BugDuplicateChange(AttributeChange):
             # Bug was already a duplicate of one bug,
             # and we are changing it to be a duplicate of another bug.
             lifecycle = _is_status_change_lifecycle_change(
-                old_bug.default_bugtask.status,
-                new_bug.default_bugtask.status)
+                old_bug.default_bugtask.status, new_bug.default_bugtask.status
+            )
         elif new_bug is not None:
             # old_bug is None here, so we are just adding a duplicate marker.
-            lifecycle = (new_bug.default_bugtask.status in
-                         RESOLVED_BUGTASK_STATUSES)
+            lifecycle = (
+                new_bug.default_bugtask.status in RESOLVED_BUGTASK_STATUSES
+            )
         elif old_bug is not None:
             # Unmarking a bug as duplicate.  This is lifecycle change
             # only if bug has been reopened as a result.
-            lifecycle = (old_bug.default_bugtask.status in
-                         RESOLVED_BUGTASK_STATUSES)
+            lifecycle = (
+                old_bug.default_bugtask.status in RESOLVED_BUGTASK_STATUSES
+            )
         else:
             pass
 
@@ -520,42 +549,47 @@ class BugDuplicateChange(AttributeChange):
     def getBugActivity(self):
         if self.old_value is not None and self.new_value is not None:
             return {
-                'whatchanged': CHANGED_DUPLICATE_MARKER,
-                'oldvalue': str(self.old_value.id),
-                'newvalue': str(self.new_value.id),
-                }
+                "whatchanged": CHANGED_DUPLICATE_MARKER,
+                "oldvalue": str(self.old_value.id),
+                "newvalue": str(self.new_value.id),
+            }
         elif self.old_value is None:
             return {
-                'whatchanged': MARKED_AS_DUPLICATE,
-                'newvalue': str(self.new_value.id),
-                }
+                "whatchanged": MARKED_AS_DUPLICATE,
+                "newvalue": str(self.new_value.id),
+            }
         elif self.new_value is None:
             return {
-                'whatchanged': REMOVED_DUPLICATE_MARKER,
-                'oldvalue': str(self.old_value.id),
-                }
+                "whatchanged": REMOVED_DUPLICATE_MARKER,
+                "oldvalue": str(self.old_value.id),
+            }
         else:
             raise AssertionError(
-                "There is no change: both the old bug and new bug are None.")
+                "There is no change: both the old bug and new bug are None."
+            )
 
     def getBugNotification(self):
         if self.old_value is not None and self.new_value is not None:
             if self.old_value.private:
                 old_value_text = (
                     "** This bug is no longer a duplicate of private bug "
-                    "%d" % self.old_value.id)
+                    "%d" % self.old_value.id
+                )
             else:
                 old_value_text = (
                     "** This bug is no longer a duplicate of bug %d\n"
-                    "   %s" % (self.old_value.id, self.old_value.title))
+                    "   %s" % (self.old_value.id, self.old_value.title)
+                )
             if self.new_value.private:
                 new_value_text = (
                     "** This bug has been marked a duplicate of private bug "
-                    "%d" % self.new_value.id)
+                    "%d" % self.new_value.id
+                )
             else:
                 new_value_text = (
                     "** This bug has been marked a duplicate of bug %d\n"
-                    "   %s" % (self.new_value.id, self.new_value.title))
+                    "   %s" % (self.new_value.id, self.new_value.title)
+                )
 
             text = "\n".join((old_value_text, new_value_text))
 
@@ -563,27 +597,32 @@ class BugDuplicateChange(AttributeChange):
             if self.new_value.private:
                 text = (
                     "** This bug has been marked a duplicate of private bug "
-                    "%d" % self.new_value.id)
+                    "%d" % self.new_value.id
+                )
             else:
                 text = (
                     "** This bug has been marked a duplicate of bug %d\n"
-                    "   %s" % (self.new_value.id, self.new_value.title))
+                    "   %s" % (self.new_value.id, self.new_value.title)
+                )
 
         elif self.new_value is None:
             if self.old_value.private:
                 text = (
                     "** This bug is no longer a duplicate of private bug "
-                    "%d" % self.old_value.id)
+                    "%d" % self.old_value.id
+                )
             else:
                 text = (
                     "** This bug is no longer a duplicate of bug %d\n"
-                    "   %s" % (self.old_value.id, self.old_value.title))
+                    "   %s" % (self.old_value.id, self.old_value.title)
+                )
 
         else:
             raise AssertionError(
-                "There is no change: both the old bug and new bug are None.")
+                "There is no change: both the old bug and new bug are None."
+            )
 
-        return {'text': text}
+        return {"text": text}
 
 
 class BugTitleChange(AttributeChange):
@@ -595,16 +634,19 @@ class BugTitleChange(AttributeChange):
         # We return 'summary' instead of 'title' for title changes
         # because the bug's title is referred to as its summary in the
         # UI.
-        activity['whatchanged'] = 'summary'
+        activity["whatchanged"] = "summary"
         return activity
 
     def getBugNotification(self):
-        notification_text = dedent("""\
+        notification_text = dedent(
+            """\
             ** Summary changed:
 
             - %s
-            + %s""" % (self.old_value, self.new_value))
-        return {'text': notification_text}
+            + %s"""
+            % (self.old_value, self.new_value)
+        )
+        return {"text": notification_text}
 
 
 class BugInformationTypeChange(AttributeChange):
@@ -612,15 +654,16 @@ class BugInformationTypeChange(AttributeChange):
 
     def getBugActivity(self):
         return {
-            'newvalue': self.new_value.title,
-            'oldvalue': self.old_value.title,
-            'whatchanged': 'information type'
-             }
+            "newvalue": self.new_value.title,
+            "oldvalue": self.old_value.title,
+            "whatchanged": "information type",
+        }
 
     def getBugNotification(self):
         return {
-            'text': "** Information type changed from %s to %s" % (
-                self.old_value.title, self.new_value.title)}
+            "text": "** Information type changed from %s to %s"
+            % (self.old_value.title, self.new_value.title)
+        }
 
 
 class BugTagsChange(AttributeChange):
@@ -633,10 +676,10 @@ class BugTagsChange(AttributeChange):
         old_value = " ".join(sorted(set(self.old_value)))
 
         return {
-            'newvalue': new_value,
-            'oldvalue': old_value,
-            'whatchanged': self.what_changed,
-            }
+            "newvalue": new_value,
+            "oldvalue": old_value,
+            "whatchanged": self.what_changed,
+        }
 
     def getBugNotification(self):
         new_tags = set(self.new_value)
@@ -647,18 +690,17 @@ class BugTagsChange(AttributeChange):
         messages = []
         if len(removed_tags) > 0:
             messages.append(
-                "** Tags removed: %s" % " ".join(sorted(removed_tags)))
+                "** Tags removed: %s" % " ".join(sorted(removed_tags))
+            )
         if len(added_tags) > 0:
-            messages.append(
-                "** Tags added: %s" % " ".join(sorted(added_tags)))
+            messages.append("** Tags added: %s" % " ".join(sorted(added_tags)))
 
-        return {'text': "\n".join(messages)}
+        return {"text": "\n".join(messages)}
 
 
 def download_url_of_bugattachment(attachment):
     """Return the URL of the ProxiedLibraryFileAlias for the attachment."""
-    return ProxiedLibraryFileAlias(
-        attachment.libraryfile, attachment).http_url
+    return ProxiedLibraryFileAlias(attachment.libraryfile, attachment).http_url
 
 
 class BugAttachmentChange(AttributeChange):
@@ -670,39 +712,45 @@ class BugAttachmentChange(AttributeChange):
             old_value = None
             new_value = "%s %s" % (
                 self.new_value.title,
-                download_url_of_bugattachment(self.new_value))
+                download_url_of_bugattachment(self.new_value),
+            )
         else:
             what_changed = ATTACHMENT_REMOVED
             old_value = "%s %s" % (
                 self.old_value.title,
-                download_url_of_bugattachment(self.old_value))
+                download_url_of_bugattachment(self.old_value),
+            )
             new_value = None
 
         return {
-            'newvalue': new_value,
-            'oldvalue': old_value,
-            'whatchanged': what_changed,
-            }
+            "newvalue": new_value,
+            "oldvalue": old_value,
+            "whatchanged": what_changed,
+        }
 
     def getBugNotification(self):
         if self.old_value is None:
             if self.new_value.is_patch:
-                attachment_str = 'Patch'
+                attachment_str = "Patch"
             else:
-                attachment_str = 'Attachment'
+                attachment_str = "Attachment"
             message = '** %s added: "%s"\n   %s' % (
-                attachment_str, self.new_value.title,
-                download_url_of_bugattachment(self.new_value))
+                attachment_str,
+                self.new_value.title,
+                download_url_of_bugattachment(self.new_value),
+            )
         else:
             if self.old_value.is_patch:
-                attachment_str = 'Patch'
+                attachment_str = "Patch"
             else:
-                attachment_str = 'Attachment'
+                attachment_str = "Attachment"
             message = '** %s removed: "%s"\n   %s' % (
-                attachment_str, self.old_value.title,
-                download_url_of_bugattachment(self.old_value))
+                attachment_str,
+                self.old_value.title,
+                download_url_of_bugattachment(self.old_value),
+            )
 
-        return {'text': message}
+        return {"text": message}
 
 
 class CveLinkedToBug(BugChangeBase):
@@ -714,13 +762,11 @@ class CveLinkedToBug(BugChangeBase):
 
     def getBugActivity(self):
         """See `IBugChange`."""
-        return dict(
-            newvalue=self.cve.sequence,
-            whatchanged=CVE_LINKED)
+        return dict(newvalue=self.cve.sequence, whatchanged=CVE_LINKED)
 
     def getBugNotification(self):
         """See `IBugChange`."""
-        return {'text': "** CVE added: %s" % self.cve.url}
+        return {"text": "** CVE added: %s" % self.cve.url}
 
 
 class CveUnlinkedFromBug(BugChangeBase):
@@ -732,20 +778,17 @@ class CveUnlinkedFromBug(BugChangeBase):
 
     def getBugActivity(self):
         """See `IBugChange`."""
-        return dict(
-            oldvalue=self.cve.sequence,
-            whatchanged=CVE_UNLINKED)
+        return dict(oldvalue=self.cve.sequence, whatchanged=CVE_UNLINKED)
 
     def getBugNotification(self):
         """See `IBugChange`."""
-        return {'text': "** CVE removed: %s" % self.cve.url}
+        return {"text": "** CVE removed: %s" % self.cve.url}
 
 
 class BugLocked(BugChangeBase):
     """Used to represent the locking of a bug."""
 
-    def __init__(self, when, person, old_status,
-                 new_status, reason, **kwargs):
+    def __init__(self, when, person, old_status, new_status, reason, **kwargs):
         super().__init__(when, person)
         self.old_status = old_status
         self.new_status = new_status
@@ -754,12 +797,12 @@ class BugLocked(BugChangeBase):
     def getBugActivity(self):
         """See `IBugChange'."""
         activity = dict(
-            whatchanged='lock status',
+            whatchanged="lock status",
             oldvalue=str(self.old_status),
             newvalue=str(self.new_status),
         )
         if self.reason:
-            activity['message'] = self.reason
+            activity["message"] = self.reason
 
         return activity
 
@@ -769,9 +812,8 @@ class BugLocked(BugChangeBase):
         if self.reason:
             text = "{}: {}".format(text, self.reason)
 
-        return {
-            'text': text
-        }
+        return {"text": text}
+
 
 class BugUnlocked(BugChangeBase):
     """Used to represent the unlocking of a bug."""
@@ -783,16 +825,14 @@ class BugUnlocked(BugChangeBase):
     def getBugActivity(self):
         """See `IBugChange'."""
         return dict(
-            whatchanged='lock status',
+            whatchanged="lock status",
             newvalue=str(BugLockStatus.UNLOCKED),
             oldvalue=str(self.old_status),
         )
 
     def getBugNotification(self):
         """See `IBugChange`."""
-        return {
-            'text': "** Bug unlocked"
-        }
+        return {"text": "** Bug unlocked"}
 
 
 class BugLockReasonSet(BugChangeBase):
@@ -806,9 +846,9 @@ class BugLockReasonSet(BugChangeBase):
     def getBugActivity(self):
         """See `IBugChange`."""
         return dict(
-            whatchanged='lock reason',
-            oldvalue=self.old_reason if self.old_reason else 'unset',
-            newvalue=self.new_reason if self.new_reason else 'unset',
+            whatchanged="lock reason",
+            oldvalue=self.old_reason if self.old_reason else "unset",
+            newvalue=self.new_reason if self.new_reason else "unset",
         )
 
     def getBugNotification(self):
@@ -816,11 +856,9 @@ class BugLockReasonSet(BugChangeBase):
         if self.new_reason is None:
             text = "Bug lock reason unset"
         else:
-            text = '** Bug lock reason changed: {}'.format(self.new_reason)
+            text = "** Bug lock reason changed: {}".format(self.new_reason)
 
-        return {
-            'text': text
-        }
+        return {"text": text}
 
 
 class BugTaskAttributeChange(AttributeChange):
@@ -835,8 +873,9 @@ class BugTaskAttributeChange(AttributeChange):
     sending notifications.
     """
 
-    def __init__(self, bug_task, when, person, what_changed, old_value,
-                 new_value):
+    def __init__(
+        self, bug_task, when, person, what_changed, old_value, new_value
+    ):
         super().__init__(when, person, what_changed, old_value, new_value)
         self.bug_task = bug_task
 
@@ -844,13 +883,15 @@ class BugTaskAttributeChange(AttributeChange):
             self.display_old_value = None
         else:
             self.display_old_value = getattr(
-                self.old_value, self.display_attribute)
+                self.old_value, self.display_attribute
+            )
 
         if self.new_value is None:
             self.display_new_value = None
         else:
             self.display_new_value = getattr(
-                self.new_value, self.display_attribute)
+                self.new_value, self.display_attribute
+            )
 
     @property
     def display_activity_label(self):
@@ -876,11 +917,11 @@ class BugTaskAttributeChange(AttributeChange):
         target so as to make it clear in which task the change was made.
         """
         return {
-            'whatchanged': '%s: %s' % (
-                self.bug_task.bugtargetname, self.display_activity_label),
-            'oldvalue': self.display_old_value,
-            'newvalue': self.display_new_value,
-            }
+            "whatchanged": "%s: %s"
+            % (self.bug_task.bugtargetname, self.display_activity_label),
+            "oldvalue": self.display_old_value,
+            "newvalue": self.display_new_value,
+        }
 
     def getBugNotification(self):
         """Return the bug notification text for this change.
@@ -890,35 +931,38 @@ class BugTaskAttributeChange(AttributeChange):
         """
         text = (
             "** Changed in: %(bug_target_name)s\n"
-            "%(label)13s: %(oldval)s => %(newval)s\n" % {
-                'bug_target_name': self.bug_task.bugtargetname,
-                'label': self.display_notification_label,
-                'oldval': self.display_old_value,
-                'newval': self.display_new_value,
-            })
+            "%(label)13s: %(oldval)s => %(newval)s\n"
+            % {
+                "bug_target_name": self.bug_task.bugtargetname,
+                "label": self.display_notification_label,
+                "oldval": self.display_old_value,
+                "newval": self.display_new_value,
+            }
+        )
 
-        return {'text': text.rstrip()}
+        return {"text": text.rstrip()}
 
 
 class BugTaskImportanceChange(BugTaskAttributeChange):
     """Represents a change in BugTask.importance."""
 
     # Use `importance.title` in activity records and notifications.
-    display_attribute = 'title'
+    display_attribute = "title"
 
 
 class BugTaskStatusChange(BugTaskAttributeChange):
     """Represents a change in BugTask.status."""
 
     # Use `status.title` in activity records and notifications.
-    display_attribute = 'title'
+    display_attribute = "title"
 
     @property
     def change_level(self):
         """See `IBugChange`."""
         # Is bug being closed or reopened?
         lifecycle_change = _is_status_change_lifecycle_change(
-            self.old_value, self.new_value)
+            self.old_value, self.new_value
+        )
 
         if lifecycle_change:
             return BugNotificationLevel.LIFECYCLE
@@ -930,25 +974,26 @@ class BugTaskMilestoneChange(BugTaskAttributeChange):
     """Represents a change in BugTask.milestone."""
 
     # Use `milestone.name` in activity records and notifications.
-    display_attribute = 'name'
+    display_attribute = "name"
 
 
 class BugTaskBugWatchChange(BugTaskAttributeChange):
     """Represents a change in BugTask.bugwatch."""
 
     # Use the term "remote watch" as this is used in the UI.
-    display_activity_label = 'remote watch'
-    display_notification_label = 'Remote watch'
+    display_activity_label = "remote watch"
+    display_notification_label = "Remote watch"
 
     # Use `bugwatch.title` in activity records and notifications.
-    display_attribute = 'title'
+    display_attribute = "title"
 
 
 class BugTaskAssigneeChange(AttributeChange):
     """Represents a change in BugTask.assignee."""
 
-    def __init__(self, bug_task, when, person,
-                 what_changed, old_value, new_value):
+    def __init__(
+        self, bug_task, when, person, what_changed, old_value, new_value
+    ):
         super().__init__(when, person, what_changed, old_value, new_value)
         self.bug_task = bug_task
 
@@ -962,10 +1007,10 @@ class BugTaskAssigneeChange(AttributeChange):
                 return assignee.unique_displayname
 
         return {
-            'whatchanged': '%s: assignee' % self.bug_task.bugtargetname,
-            'oldvalue': assignee_for_display(self.old_value),
-            'newvalue': assignee_for_display(self.new_value),
-            }
+            "whatchanged": "%s: assignee" % self.bug_task.bugtargetname,
+            "oldvalue": assignee_for_display(self.old_value),
+            "newvalue": assignee_for_display(self.new_value),
+        }
 
     def getBugNotification(self):
         """See `IBugChange`."""
@@ -977,13 +1022,16 @@ class BugTaskAssigneeChange(AttributeChange):
                 return assignee.unique_displayname
 
         return {
-            'text': (
+            "text": (
                 "** Changed in: %s\n"
-                "     Assignee: %s => %s" % (
+                "     Assignee: %s => %s"
+                % (
                     self.bug_task.bugtargetname,
                     assignee_for_display(self.old_value),
-                    assignee_for_display(self.new_value))),
-            }
+                    assignee_for_display(self.new_value),
+                )
+            ),
+        }
 
 
 class BugTaskTargetChange(AttributeChange):
@@ -991,18 +1039,19 @@ class BugTaskTargetChange(AttributeChange):
 
     change_level = BugNotificationLevel.LIFECYCLE
 
-    def __init__(self, bug_task, when, person,
-                 what_changed, old_value, new_value):
+    def __init__(
+        self, bug_task, when, person, what_changed, old_value, new_value
+    ):
         super().__init__(when, person, what_changed, old_value, new_value)
         self.bug_task = bug_task
 
     def getBugActivity(self):
         """See `IBugChange`."""
         return {
-            'whatchanged': 'affects',
-            'oldvalue': self.old_value.bugtargetname,
-            'newvalue': self.new_value.bugtargetname,
-            }
+            "whatchanged": "affects",
+            "oldvalue": self.old_value.bugtargetname,
+            "newvalue": self.new_value.bugtargetname,
+        }
 
     def getBugNotification(self):
         """See `IBugChange`."""
@@ -1012,15 +1061,17 @@ class BugTaskTargetChange(AttributeChange):
             template = "** Package changed: %s => %s"
         text = template % (
             self.old_value.bugtargetname,
-            self.new_value.bugtargetname)
-        return {'text': text}
+            self.new_value.bugtargetname,
+        )
+        return {"text": text}
 
 
 class BugTaskImportanceExplanationChange(AttributeChange):
     """Represents a change in BugTask.importance_explanation."""
 
-    def __init__(self, bug_task, when, person,
-                 what_changed, old_value, new_value):
+    def __init__(
+        self, bug_task, when, person, what_changed, old_value, new_value
+    ):
         # XXX: lgp171188 2022-04-12: This is a sub-class of
         # the `AttributeChange` class instead of
         # the `BugTaskAttributeChange` class because the latter
@@ -1037,23 +1088,22 @@ class BugTaskImportanceExplanationChange(AttributeChange):
         super().__init__(when, person, what_changed, old_value, new_value)
         self.bug_task = bug_task
 
-
     def getBugActivity(self):
         """See `IBugChange`."""
         return {
-            'whatchanged': '%s: importance explanation' % (
-                self.bug_task.bugtargetname,
-            ),
-            'oldvalue': self.old_value or 'unset',
-            'newvalue': self.new_value or 'unset',
+            "whatchanged": "%s: importance explanation"
+            % (self.bug_task.bugtargetname,),
+            "oldvalue": self.old_value or "unset",
+            "newvalue": self.new_value or "unset",
         }
 
     def getBugNotification(self):
         """See `IBugChange`."""
         return {
-            'text': (
+            "text": (
                 "** Changed in %s\n"
-                "     Importance explanation: %s => %s" % (
+                "     Importance explanation: %s => %s"
+                % (
                     self.bug_task.bugtargetname,
                     self.old_value or "unset",
                     self.new_value or "unset",
@@ -1065,8 +1115,9 @@ class BugTaskImportanceExplanationChange(AttributeChange):
 class BugTaskStatusExplanationChange(AttributeChange):
     """Represents a change in BugTask.status_explanation."""
 
-    def __init__(self, bug_task, when, person,
-                 what_changed, old_value, new_value):
+    def __init__(
+        self, bug_task, when, person, what_changed, old_value, new_value
+    ):
         # XXX: lgp171188 2022-04-12: This is a sub-class of
         # the `AttributeChange` class instead of
         # the `BugTaskAttributeChange` class because the latter
@@ -1086,44 +1137,44 @@ class BugTaskStatusExplanationChange(AttributeChange):
     def getBugActivity(self):
         """See `IBugChange`."""
         return {
-            'whatchanged': '%s: status explanation' % (
-                self.bug_task.bugtargetname,
-            ),
-            'oldvalue': self.old_value or 'unset',
-            'newvalue': self.new_value or 'unset',
+            "whatchanged": "%s: status explanation"
+            % (self.bug_task.bugtargetname,),
+            "oldvalue": self.old_value or "unset",
+            "newvalue": self.new_value or "unset",
         }
 
     def getBugNotification(self):
         """See `IBugChange`."""
         return {
-            'text': (
-                '** Changed in %s\n'
-                '     Status explanation: %s => %s' % (
+            "text": (
+                "** Changed in %s\n"
+                "     Status explanation: %s => %s"
+                % (
                     self.bug_task.bugtargetname,
-                    self.old_value or 'unset',
-                    self.new_value or 'unset',
+                    self.old_value or "unset",
+                    self.new_value or "unset",
                 )
             )
         }
 
 
 BUG_CHANGE_LOOKUP = {
-    'description': BugDescriptionChange,
-    'information_type': BugInformationTypeChange,
-    'tags': BugTagsChange,
-    'title': BugTitleChange,
-    'attachment': BugAttachmentChange,
-    'duplicateof': BugDuplicateChange,
-    }
+    "description": BugDescriptionChange,
+    "information_type": BugInformationTypeChange,
+    "tags": BugTagsChange,
+    "title": BugTitleChange,
+    "attachment": BugAttachmentChange,
+    "duplicateof": BugDuplicateChange,
+}
 
 
 BUGTASK_CHANGE_LOOKUP = {
-    'importance': BugTaskImportanceChange,
-    'status': BugTaskStatusChange,
-    'target': BugTaskTargetChange,
-    'milestone': BugTaskMilestoneChange,
-    'bugwatch': BugTaskBugWatchChange,
-    'assignee': BugTaskAssigneeChange,
-    'importance_explanation': BugTaskImportanceExplanationChange,
-    'status_explanation': BugTaskStatusExplanationChange,
-    }
+    "importance": BugTaskImportanceChange,
+    "status": BugTaskStatusChange,
+    "target": BugTaskTargetChange,
+    "milestone": BugTaskMilestoneChange,
+    "bugwatch": BugTaskBugWatchChange,
+    "assignee": BugTaskAssigneeChange,
+    "importance_explanation": BugTaskImportanceExplanationChange,
+    "status_explanation": BugTaskStatusExplanationChange,
+}
diff --git a/lib/lp/bugs/adapters/bugdelta.py b/lib/lp/bugs/adapters/bugdelta.py
index 8d8268c..f0f31a0 100644
--- a/lib/lp/bugs/adapters/bugdelta.py
+++ b/lib/lp/bugs/adapters/bugdelta.py
@@ -12,13 +12,27 @@ from lp.bugs.interfaces.bug import IBugDelta
 class BugDelta:
     """See `IBugDelta`."""
 
-    def __init__(self, bug, bugurl, user,
-                 title=None, description=None, name=None,
-                 private=None, security_related=None, information_type=None,
-                 duplicateof=None, external_reference=None, bugwatch=None,
-                 cve=None, attachment=None, tags=None,
-                 added_bugtasks=None, bugtask_deltas=None,
-                 bug_before_modification=None):
+    def __init__(
+        self,
+        bug,
+        bugurl,
+        user,
+        title=None,
+        description=None,
+        name=None,
+        private=None,
+        security_related=None,
+        information_type=None,
+        duplicateof=None,
+        external_reference=None,
+        bugwatch=None,
+        cve=None,
+        attachment=None,
+        tags=None,
+        added_bugtasks=None,
+        bugtask_deltas=None,
+        bug_before_modification=None,
+    ):
         self.bug = bug
         self.bug_before_modification = bug_before_modification
         self.bugurl = bugurl
diff --git a/lib/lp/bugs/adapters/bugsubscriptionfilter.py b/lib/lp/bugs/adapters/bugsubscriptionfilter.py
index ba6f3c4..17b6238 100644
--- a/lib/lp/bugs/adapters/bugsubscriptionfilter.py
+++ b/lib/lp/bugs/adapters/bugsubscriptionfilter.py
@@ -4,9 +4,9 @@
 """Adapt IStructuralSubscription to other types."""
 
 __all__ = [
-    'bugsubscriptionfilter_to_distribution',
-    'bugsubscriptionfilter_to_product',
-    ]
+    "bugsubscriptionfilter_to_distribution",
+    "bugsubscriptionfilter_to_product",
+]
 
 
 def bugsubscriptionfilter_to_distribution(bug_subscription_filter):
diff --git a/lib/lp/bugs/adapters/tests/test_bugchange.py b/lib/lp/bugs/adapters/tests/test_bugchange.py
index e2af8df..6a8c3b9 100644
--- a/lib/lp/bugs/adapters/tests/test_bugchange.py
+++ b/lib/lp/bugs/adapters/tests/test_bugchange.py
@@ -8,7 +8,7 @@ from lp.bugs.adapters.bugchange import (
     BugDescriptionChange,
     get_bug_change_class,
     get_bug_changes,
-    )
+)
 from lp.bugs.adapters.bugdelta import BugDelta
 from lp.bugs.enums import BugNotificationLevel
 from lp.bugs.interfaces.bugtask import BugTaskStatus
@@ -34,9 +34,11 @@ class BugChangeTestCase(TestCaseWithFactory):
             expected = BUG_CHANGE_LOOKUP[field_name]
             received = get_bug_change_class(bug, field_name)
             self.assertEqual(
-                expected, received,
+                expected,
+                received,
                 "Expected %s from get_bug_change_class() for field name %s. "
-                "Got %s." % (expected, field_name, received))
+                "Got %s." % (expected, field_name, received),
+            )
 
 
 class BugChangeLevelTestCase(TestCaseWithFactory):
@@ -53,19 +55,18 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         if user is None:
             user = self.user
         return BugDelta(
-            bug=self.bug,
-            bugurl=canonical_url(self.bug),
-            user=user,
-            **kwargs)
+            bug=self.bug, bugurl=canonical_url(self.bug), user=user, **kwargs
+        )
 
     def test_change_level_metadata_description(self):
         # Changing a bug description is considered to have change_level
         # of BugNotificationLevel.METADATA.
         bug_delta = self.createDelta(
             description={
-                'new': 'new description',
-                'old': self.bug.description,
-                })
+                "new": "new description",
+                "old": self.bug.description,
+            }
+        )
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertTrue(isinstance(change, BugDescriptionChange))
@@ -77,11 +78,11 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         bugtask_delta = BugTaskDelta(
             bugtask=self.bugtask,
             status={
-                'old': BugTaskStatus.NEW,
-                'new': BugTaskStatus.FIXRELEASED,
-                })
-        bug_delta = self.createDelta(
-            bugtask_deltas=bugtask_delta)
+                "old": BugTaskStatus.NEW,
+                "new": BugTaskStatus.FIXRELEASED,
+            },
+        )
+        bug_delta = self.createDelta(bugtask_deltas=bugtask_delta)
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.LIFECYCLE, change.change_level)
@@ -92,11 +93,11 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         bugtask_delta = BugTaskDelta(
             bugtask=self.bugtask,
             status={
-                'old': BugTaskStatus.FIXRELEASED,
-                'new': BugTaskStatus.TRIAGED,
-                })
-        bug_delta = self.createDelta(
-            bugtask_deltas=bugtask_delta)
+                "old": BugTaskStatus.FIXRELEASED,
+                "new": BugTaskStatus.TRIAGED,
+            },
+        )
+        bug_delta = self.createDelta(bugtask_deltas=bugtask_delta)
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.LIFECYCLE, change.change_level)
@@ -107,11 +108,11 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         bugtask_delta = BugTaskDelta(
             bugtask=self.bugtask,
             status={
-                'old': BugTaskStatus.TRIAGED,
-                'new': BugTaskStatus.FIXCOMMITTED,
-                })
-        bug_delta = self.createDelta(
-            bugtask_deltas=bugtask_delta)
+                "old": BugTaskStatus.TRIAGED,
+                "new": BugTaskStatus.FIXCOMMITTED,
+            },
+        )
+        bug_delta = self.createDelta(bugtask_deltas=bugtask_delta)
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.METADATA, change.change_level)
@@ -122,11 +123,11 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         bugtask_delta = BugTaskDelta(
             bugtask=self.bugtask,
             status={
-                'old': BugTaskStatus.OPINION,
-                'new': BugTaskStatus.WONTFIX,
-                })
-        bug_delta = self.createDelta(
-            bugtask_deltas=bugtask_delta)
+                "old": BugTaskStatus.OPINION,
+                "new": BugTaskStatus.WONTFIX,
+            },
+        )
+        bug_delta = self.createDelta(bugtask_deltas=bugtask_delta)
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.METADATA, change.change_level)
@@ -136,13 +137,15 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         # simple BugNotificationLevel.METADATA change.
         duplicate_of = self.factory.makeBug()
         duplicate_of.default_bugtask.transitionToStatus(
-            BugTaskStatus.NEW, self.user)
+            BugTaskStatus.NEW, self.user
+        )
         bug_delta = self.createDelta(
             user=self.bug.owner,
             duplicateof={
-                'old': None,
-                'new': duplicate_of,
-                })
+                "old": None,
+                "new": duplicate_of,
+            },
+        )
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.METADATA, change.change_level)
@@ -152,13 +155,15 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         # a BugNotificationLevel.LIFECYCLE change.
         duplicate_of = self.factory.makeBug()
         duplicate_of.default_bugtask.transitionToStatus(
-            BugTaskStatus.FIXRELEASED, self.user)
+            BugTaskStatus.FIXRELEASED, self.user
+        )
         bug_delta = self.createDelta(
             user=self.bug.owner,
             duplicateof={
-                'old': None,
-                'new': duplicate_of,
-                })
+                "old": None,
+                "new": duplicate_of,
+            },
+        )
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.LIFECYCLE, change.change_level)
@@ -168,13 +173,15 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         # simple BugNotificationLevel.METADATA change.
         duplicate_of = self.factory.makeBug()
         duplicate_of.default_bugtask.transitionToStatus(
-            BugTaskStatus.NEW, self.user)
+            BugTaskStatus.NEW, self.user
+        )
         bug_delta = self.createDelta(
             user=self.bug.owner,
             duplicateof={
-                'new': None,
-                'old': duplicate_of,
-                })
+                "new": None,
+                "old": duplicate_of,
+            },
+        )
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.METADATA, change.change_level)
@@ -184,12 +191,11 @@ class BugChangeLevelTestCase(TestCaseWithFactory):
         # a BugNotificationLevel.LIFECYCLE change.
         duplicate_of = self.factory.makeBug()
         duplicate_of.default_bugtask.transitionToStatus(
-            BugTaskStatus.FIXRELEASED, self.user)
+            BugTaskStatus.FIXRELEASED, self.user
+        )
         bug_delta = self.createDelta(
-            user=self.bug.owner,
-            duplicateof={
-                'new': None,
-                'old': duplicate_of})
+            user=self.bug.owner, duplicateof={"new": None, "old": duplicate_of}
+        )
 
         change = list(get_bug_changes(bug_delta))[0]
         self.assertEqual(BugNotificationLevel.LIFECYCLE, change.change_level)
diff --git a/lib/lp/bugs/adapters/tests/test_bugsubscriptionfilter.py b/lib/lp/bugs/adapters/tests/test_bugsubscriptionfilter.py
index 3a4f5fc..b17527f 100644
--- a/lib/lp/bugs/adapters/tests/test_bugsubscriptionfilter.py
+++ b/lib/lp/bugs/adapters/tests/test_bugsubscriptionfilter.py
@@ -6,7 +6,7 @@
 from lp.bugs.adapters.bugsubscriptionfilter import (
     bugsubscriptionfilter_to_distribution,
     bugsubscriptionfilter_to_product,
-    )
+)
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.product import IProduct
 from lp.testing import TestCaseWithFactory
@@ -20,35 +20,43 @@ class BugSubscriptionFilterTestCase(TestCaseWithFactory):
     def test_bugsubscriptionfilter_to_product_with_product(self):
         product = self.factory.makeProduct()
         subscription_filter = self.factory.makeBugSubscriptionFilter(
-            target=product)
+            target=product
+        )
         self.assertEqual(
-            product, bugsubscriptionfilter_to_product(subscription_filter))
+            product, bugsubscriptionfilter_to_product(subscription_filter)
+        )
         self.assertEqual(product, IProduct(subscription_filter))
 
     def test_bugsubscriptionfilter_to_product_with_productseries(self):
         product = self.factory.makeProduct()
         series = product.development_focus
         subscription_filter = self.factory.makeBugSubscriptionFilter(
-            target=series)
+            target=series
+        )
         self.assertEqual(
-            product, bugsubscriptionfilter_to_product(subscription_filter))
+            product, bugsubscriptionfilter_to_product(subscription_filter)
+        )
         self.assertEqual(product, IProduct(subscription_filter))
 
     def test_bugsubscriptionfilter_to_distribution_with_distribution(self):
         distribution = self.factory.makeDistribution()
         subscription_filter = self.factory.makeBugSubscriptionFilter(
-            distribution)
+            distribution
+        )
         self.assertEqual(
             distribution,
-            bugsubscriptionfilter_to_distribution(subscription_filter))
+            bugsubscriptionfilter_to_distribution(subscription_filter),
+        )
         self.assertEqual(distribution, IDistribution(subscription_filter))
 
     def test_bugsubscriptionfilter_to_distroseries_with_distribution(self):
         distribution = self.factory.makeDistribution()
         series = self.factory.makeDistroSeries(distribution=distribution)
         subscription_filter = self.factory.makeBugSubscriptionFilter(
-            target=series)
+            target=series
+        )
         self.assertEqual(
             distribution,
-            bugsubscriptionfilter_to_distribution(subscription_filter))
+            bugsubscriptionfilter_to_distribution(subscription_filter),
+        )
         self.assertEqual(distribution, IDistribution(subscription_filter))
diff --git a/lib/lp/bugs/adapters/treelookup.py b/lib/lp/bugs/adapters/treelookup.py
index eebb7e2..796fe94 100644
--- a/lib/lp/bugs/adapters/treelookup.py
+++ b/lib/lp/bugs/adapters/treelookup.py
@@ -31,9 +31,9 @@ on the eye, makes debugging easier, and means they can be customised.
 """
 
 __all__ = [
-    'LookupBranch',
-    'LookupTree',
-    ]
+    "LookupBranch",
+    "LookupTree",
+]
 
 import copy
 import string
@@ -98,18 +98,19 @@ class LookupBranch:
 
         This is mainly intended as an aid to development.
         """
-        format = 'branch(%s => %%s)'
+        format = "branch(%s => %%s)"
         if self.is_default:
-            format = format % '*'
+            format = format % "*"
         else:
-            format = format % ', '.join(
-                self._describe_key(key) for key in self.keys)
+            format = format % ", ".join(
+                self._describe_key(key) for key in self.keys
+            )
         if self.is_leaf:
             return format % self._describe_result(self.result)
         else:
             return format % self.result.describe(level)
 
-    _describe_key_chars = set(string.ascii_letters + string.digits + '-_+=*')
+    _describe_key_chars = set(string.ascii_letters + string.digits + "-_+=*")
 
     def _describe_key(self, key):
         """Return a pretty representation of a simple key.
@@ -133,9 +134,10 @@ class LookupBranch:
 
     def __repr__(self):
         """A machine-readable representation of this branch."""
-        return '%s(%s)' % (
+        return "%s(%s)" % (
             self.__class__.__name__,
-            ', '.join(repr(item) for item in (self.keys + (self.result,))))
+            ", ".join(repr(item) for item in (self.keys + (self.result,))),
+        )
 
 
 class LookupTree:
@@ -182,8 +184,8 @@ class LookupTree:
                     continue
                 branch = copy.copy(branch)
                 branch.keys = tuple(
-                    key for key in branch.keys
-                    if key not in prune)
+                    key for key in branch.keys if key not in prune
+                )
             pruned_branches.append(branch)
             seen_keys.update(branch.keys)
 
@@ -202,9 +204,9 @@ class LookupTree:
         default = False
         for branch in self.branches:
             if not isinstance(branch, LookupBranch):
-                raise TypeError('Not a LookupBranch: %r' % (branch,))
+                raise TypeError("Not a LookupBranch: %r" % (branch,))
             if default:
-                raise TypeError('Default branch must be last.')
+                raise TypeError("Default branch must be last.")
             default = branch.is_default
 
     def find(self, key, *more):
@@ -271,15 +273,18 @@ class LookupTree:
 
         This is mainly intended as an aid to development.
         """
-        indent = '    ' * level
-        format = indent + '%s'
-        return 'tree(\n%s\n%s)' % (
-            '\n'.join(format % branch.describe(level + 1)
-                      for branch in self.branches),
-            indent)
+        indent = "    " * level
+        format = indent + "%s"
+        return "tree(\n%s\n%s)" % (
+            "\n".join(
+                format % branch.describe(level + 1) for branch in self.branches
+            ),
+            indent,
+        )
 
     def __repr__(self):
         """A machine-readable representation of this tree."""
-        return '%s(%s)' % (
+        return "%s(%s)" % (
             self.__class__.__name__,
-            ', '.join(repr(branch) for branch in self.branches))
+            ", ".join(repr(branch) for branch in self.branches),
+        )
diff --git a/lib/lp/bugs/browser/bug.py b/lib/lp/bugs/browser/bug.py
index 1759a8e..e3f3361 100644
--- a/lib/lp/bugs/browser/bug.py
+++ b/lib/lp/bugs/browser/bug.py
@@ -4,70 +4,54 @@
 """IBug related view classes."""
 
 __all__ = [
-    'BugActivity',
-    'BugContextMenu',
-    'BugEditView',
-    'BugInformationTypePortletView',
-    'BugLockStatusEditView',
-    'BugMarkAsAffectingUserView',
-    'BugMarkAsDuplicateView',
-    'BugNavigation',
-    'BugSecrecyEditView',
-    'BugSetNavigation',
-    'BugSubscriptionPortletDetails',
-    'BugTextView',
-    'BugURL',
-    'BugView',
-    'BugViewMixin',
-    'BugWithoutContextView',
-    'DeprecatedAssignedBugsView',
-    'MaloneView',
-    ]
+    "BugActivity",
+    "BugContextMenu",
+    "BugEditView",
+    "BugInformationTypePortletView",
+    "BugLockStatusEditView",
+    "BugMarkAsAffectingUserView",
+    "BugMarkAsDuplicateView",
+    "BugNavigation",
+    "BugSecrecyEditView",
+    "BugSetNavigation",
+    "BugSubscriptionPortletDetails",
+    "BugTextView",
+    "BugURL",
+    "BugView",
+    "BugViewMixin",
+    "BugWithoutContextView",
+    "DeprecatedAssignedBugsView",
+    "MaloneView",
+]
 
-from email.mime.multipart import MIMEMultipart
-from email.mime.text import MIMEText
 import json
 import re
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
 
-from lazr.enum import (
-    EnumeratedType,
-    Item,
-    )
+from lazr.enum import EnumeratedType, Item
 from lazr.lifecycle.event import ObjectModifiedEvent
 from lazr.lifecycle.snapshot import Snapshot
-from lazr.restful import (
-    EntryResource,
-    ResourceJSONEncoder,
-    )
+from lazr.restful import EntryResource, ResourceJSONEncoder
 from lazr.restful.interface import copy_field
 from lazr.restful.interfaces import IJSONRequestCache
 from zope import formlib
-from zope.component import (
-    getMultiAdapter,
-    getUtility,
-    )
+from zope.component import getMultiAdapter, getUtility
 from zope.event import notify
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.widgets import TextWidget
-from zope.interface import (
-    implementer,
-    Interface,
-    providedBy,
-    )
+from zope.interface import Interface, implementer, providedBy
 from zope.publisher.defaultview import getDefaultViewName
-from zope.schema import (
-    Bool,
-    Choice,
-    )
+from zope.schema import Bool, Choice
 from zope.security.proxy import removeSecurityProxy
 
 from lp import _
 from lp.app.browser.informationtype import InformationTypePortletMixin
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
-    )
+    action,
+)
 from lp.app.enums import PRIVATE_INFORMATION_TYPES
 from lp.app.errors import NotFoundError
 from lp.app.interfaces.services import IService
@@ -77,34 +61,25 @@ from lp.app.widgets.product import GhostCheckBoxWidget
 from lp.app.widgets.project import ProjectScopeWidget
 from lp.bugs.browser.bugsubscription import BugPortletSubscribersWithDetails
 from lp.bugs.browser.widgets.bug import BugTagsWidget
-from lp.bugs.enums import (
-    BugLockStatus,
-    BugNotificationLevel,
-    )
-from lp.bugs.interfaces.bug import (
-    IBug,
-    IBugSet,
-    )
+from lp.bugs.enums import BugLockStatus, BugNotificationLevel
+from lp.bugs.interfaces.bug import IBug, IBugSet
 from lp.bugs.interfaces.bugattachment import (
     BugAttachmentType,
     IBugAttachmentSet,
-    )
+)
 from lp.bugs.interfaces.bugnomination import IBugNominationSet
-from lp.bugs.interfaces.bugtask import (
-    BugTaskStatus,
-    IBugTask,
-    )
+from lp.bugs.interfaces.bugtask import BugTaskStatus, IBugTask
 from lp.bugs.interfaces.bugtasksearch import (
     BugTaskSearchParams,
     IFrontPageBugTaskSearch,
-    )
+)
 from lp.bugs.interfaces.bugwatch import IBugWatchSet
 from lp.bugs.interfaces.cve import ICveSet
 from lp.bugs.mail.bugnotificationbuilder import format_rfc2822_date
 from lp.bugs.model.personsubscriptioninfo import PersonSubscriptions
 from lp.bugs.model.structuralsubscription import (
     get_structural_subscriptions_for_bug,
-    )
+)
 from lp.registry.interfaces.person import IPersonSet
 from lp.services.compat import message_as_bytes
 from lp.services.features import getFeatureFlag
@@ -112,34 +87,29 @@ from lp.services.fields import DuplicateBug
 from lp.services.librarian.browser import ProxiedLibraryFileAlias
 from lp.services.mail.mailwrapper import MailWrapper
 from lp.services.propertycache import cachedproperty
-from lp.services.searchbuilder import (
-    any,
-    not_equals,
-    )
+from lp.services.searchbuilder import any, not_equals
 from lp.services.webapp import (
-    canonical_url,
     ContextMenu,
-    enabled_with_permission,
     LaunchpadView,
     Link,
     Navigation,
+    canonical_url,
+    enabled_with_permission,
     stepthrough,
     structured,
-    )
+)
 from lp.services.webapp.authorization import (
     check_permission,
     precache_permission_for_objects,
-    )
-from lp.services.webapp.interfaces import (
-    ICanonicalUrlData,
-    ILaunchBag,
-    )
+)
+from lp.services.webapp.interfaces import ICanonicalUrlData, ILaunchBag
 from lp.services.webapp.publisher import RedirectionView
 from lp.services.webapp.snapshot import notify_modified
 
 
 class BugNavigation(Navigation):
     """Navigation for the `IBug`."""
+
     # It would be easier, since there is no per-bug sequence for a BugWatch
     # and we have to leak the BugWatch.id anyway, to hang bugwatches off a
     # global /bugwatchs/nnnn
@@ -153,21 +123,21 @@ class BugNavigation(Navigation):
 
     usedfor = IBug
 
-    @stepthrough('+watch')
+    @stepthrough("+watch")
     def traverse_watches(self, name):
         """Retrieve a BugWatch by name."""
         if name.isdigit():
             # in future this should look up by (bug.id, watch.seqnum)
             return getUtility(IBugWatchSet).get(int(name))
 
-    @stepthrough('+subscription')
+    @stepthrough("+subscription")
     def traverse_subscriptions(self, person_name):
         """Retrieve a BugSubscription by person name."""
         for subscription in self.context.subscriptions:
             if subscription.person.name == person_name:
                 return subscription
 
-    @stepthrough('attachments')
+    @stepthrough("attachments")
     def traverse_attachments(self, name):
         """Retrieve a BugAttachment by ID.
 
@@ -177,10 +147,10 @@ class BugNavigation(Navigation):
             attachment = getUtility(IBugAttachmentSet)[name]
             if attachment is not None and attachment.bug == self.context:
                 return self.redirectSubTree(
-                    canonical_url(attachment, request=self.request),
-                    status=301)
+                    canonical_url(attachment, request=self.request), status=301
+                )
 
-    @stepthrough('+attachment')
+    @stepthrough("+attachment")
     def traverse_attachment(self, name):
         """Retrieve a BugAttachment by ID.
 
@@ -191,7 +161,7 @@ class BugNavigation(Navigation):
             if attachment is not None and attachment.bug == self.context:
                 return attachment
 
-    @stepthrough('nominations')
+    @stepthrough("nominations")
     def traverse_nominations(self, nomination_id):
         """Traverse to a nomination by id."""
         if nomination_id.isdigit():
@@ -203,9 +173,10 @@ class BugNavigation(Navigation):
 
 class BugSetNavigation(Navigation):
     """Navigation for the IBugSet."""
+
     usedfor = IBugSet
 
-    @stepthrough('+text')
+    @stepthrough("+text")
     def text(self, name):
         """Retrieve a bug by name."""
         try:
@@ -216,13 +187,29 @@ class BugSetNavigation(Navigation):
 
 class BugContextMenu(ContextMenu):
     """Context menu of actions that can be performed upon a Bug."""
+
     usedfor = IBug
     links = [
-        'editdescription', 'markduplicate', 'visibility', 'addupstream',
-        'adddistro', 'subscription', 'addsubscriber', 'editsubscriptions',
-        'addcomment', 'nominate', 'addbranch', 'linktocve', 'unlinkcve',
-        'createquestion', 'mute_subscription', 'removequestion',
-        'activitylog', 'affectsmetoo', 'change_lock_status']
+        "editdescription",
+        "markduplicate",
+        "visibility",
+        "addupstream",
+        "adddistro",
+        "subscription",
+        "addsubscriber",
+        "editsubscriptions",
+        "addcomment",
+        "nominate",
+        "addbranch",
+        "linktocve",
+        "unlinkcve",
+        "createquestion",
+        "mute_subscription",
+        "removequestion",
+        "activitylog",
+        "affectsmetoo",
+        "change_lock_status",
+    ]
 
     def __init__(self, context):
         # Always force the context to be the current bugtask, so that we don't
@@ -230,166 +217,187 @@ class BugContextMenu(ContextMenu):
         ContextMenu.__init__(self, getUtility(ILaunchBag).bugtask)
 
     @cachedproperty
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def editdescription(self):
         """Return the 'Edit description/tags' Link."""
-        text = 'Update description / tags'
-        return Link('+edit', text, icon='edit')
+        text = "Update description / tags"
+        return Link("+edit", text, icon="edit")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def visibility(self):
         """Return the 'Set privacy/security' Link."""
-        text = 'Change privacy/security'
-        return Link('+secrecy', text)
+        text = "Change privacy/security"
+        return Link("+secrecy", text)
 
-    @enabled_with_permission('launchpad.Moderate')
+    @enabled_with_permission("launchpad.Moderate")
     def change_lock_status(self):
         """Return the 'Change lock status' Link."""
-        text = 'Change lock status'
-        return Link('+lock-status', text, icon='edit')
+        text = "Change lock status"
+        return Link("+lock-status", text, icon="edit")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def markduplicate(self):
         """Return the 'Mark as duplicate' Link."""
-        return Link('+duplicate', 'Mark as duplicate')
+        return Link("+duplicate", "Mark as duplicate")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def addupstream(self):
         """Return the 'Also affects project' Link."""
-        text = 'Also affects project'
-        return Link('+choose-affected-product', text, icon='add')
+        text = "Also affects project"
+        return Link("+choose-affected-product", text, icon="add")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def adddistro(self):
         """Return the 'Also affects distribution/package' Link."""
-        text = 'Also affects distribution/package'
-        return Link('+distrotask', text, icon='add')
+        text = "Also affects distribution/package"
+        return Link("+distrotask", text, icon="add")
 
     def subscription(self):
         """Return the 'Subscribe/Unsubscribe' Link."""
         user = getUtility(ILaunchBag).user
         if user is None:
-            text = 'Subscribe/Unsubscribe'
-            icon = 'edit'
+            text = "Subscribe/Unsubscribe"
+            icon = "edit"
         elif user is not None and (
-            self.context.bug.isSubscribed(user) or
-            self.context.bug.isSubscribedToDupes(user)):
+            self.context.bug.isSubscribed(user)
+            or self.context.bug.isSubscribedToDupes(user)
+        ):
             if self.context.bug.isMuted(user):
-                text = 'Subscribe'
-                icon = 'add'
+                text = "Subscribe"
+                icon = "add"
             else:
-                text = 'Edit subscription'
-                icon = 'edit'
+                text = "Edit subscription"
+                icon = "edit"
         else:
-            text = 'Subscribe'
-            icon = 'add'
-        return Link('+subscribe', text, icon=icon, summary=(
-                'When you are subscribed, Launchpad will email you each time '
-                'this bug changes'))
+            text = "Subscribe"
+            icon = "add"
+        return Link(
+            "+subscribe",
+            text,
+            icon=icon,
+            summary=(
+                "When you are subscribed, Launchpad will email you each time "
+                "this bug changes"
+            ),
+        )
 
     def addsubscriber(self):
         """Return the 'Subscribe someone else' Link."""
-        text = 'Subscribe someone else'
+        text = "Subscribe someone else"
         return Link(
-            '+addsubscriber', text, icon='add', summary=(
-                'Launchpad will email that person whenever this bugs '
-                'changes'))
+            "+addsubscriber",
+            text,
+            icon="add",
+            summary=(
+                "Launchpad will email that person whenever this bugs "
+                "changes"
+            ),
+        )
 
     def editsubscriptions(self):
         """Return the 'Edit subscriptions' Link."""
-        text = 'Edit bug mail'
+        text = "Edit bug mail"
         return Link(
-            '+subscriptions', text, icon='edit', summary=(
-                'View and change your subscriptions to this bug'))
+            "+subscriptions",
+            text,
+            icon="edit",
+            summary=("View and change your subscriptions to this bug"),
+        )
 
     def mute_subscription(self):
         """Return the 'Mute subscription' Link."""
         user = getUtility(ILaunchBag).user
         if self.context.bug.isMuted(user):
             text = "Unmute bug mail"
-            icon = 'unmute'
+            icon = "unmute"
             summary = (
-                'Unmute this bug so that you will receive emails about it.')
+                "Unmute this bug so that you will receive emails about it."
+            )
         else:
             text = "Mute bug mail"
-            icon = 'mute'
+            icon = "mute"
             summary = (
-                'Mute this bug so that you will not receive emails about it.')
+                "Mute this bug so that you will not receive emails about it."
+            )
 
-        return Link('+mute', text, icon=icon, summary=summary)
+        return Link("+mute", text, icon=icon, summary=summary)
 
     def nominate(self):
         """Return the 'Target/Nominate for series' Link."""
         launchbag = getUtility(ILaunchBag)
         target = launchbag.product or launchbag.distribution
         if check_permission("launchpad.Driver", target) or (
-                getFeatureFlag('bugs.nominations.bug_supervisors_can_target')
-                and check_permission("launchpad.BugSupervisor", target)):
+            getFeatureFlag("bugs.nominations.bug_supervisors_can_target")
+            and check_permission("launchpad.BugSupervisor", target)
+        ):
             text = "Target to series"
-            return Link('+nominate', text, icon='milestone')
-        elif (check_permission("launchpad.BugSupervisor", target) or
-            self.user is None):
-            text = 'Nominate for series'
-            return Link('+nominate', text, icon='milestone')
+            return Link("+nominate", text, icon="milestone")
+        elif (
+            check_permission("launchpad.BugSupervisor", target)
+            or self.user is None
+        ):
+            text = "Nominate for series"
+            return Link("+nominate", text, icon="milestone")
         else:
-            return Link('+nominate', '', enabled=False, icon='milestone')
+            return Link("+nominate", "", enabled=False, icon="milestone")
 
-    @enabled_with_permission('launchpad.Append')
+    @enabled_with_permission("launchpad.Append")
     def addcomment(self):
         """Return the 'Comment or attach file' Link."""
-        text = 'Add attachment or patch'
-        return Link('+addcomment', text, icon='add')
+        text = "Add attachment or patch"
+        return Link("+addcomment", text, icon="add")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def addbranch(self):
         """Return the 'Add branch' Link."""
-        text = 'Link a related branch'
-        return Link('+addbranch', text, icon='add')
+        text = "Link a related branch"
+        return Link("+addbranch", text, icon="add")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def linktocve(self):
         """Return the 'Link to CVE' Link."""
         text = structured(
-            'Link to '
+            "Link to "
             '<abbr title="Common Vulnerabilities and Exposures Index">'
-            'CVE'
-            '</abbr>')
-        return Link('+linkcve', text, icon='add')
+            "CVE"
+            "</abbr>"
+        )
+        return Link("+linkcve", text, icon="add")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def unlinkcve(self):
         """Return 'Remove CVE link' Link."""
         enabled = self.context.bug.has_cves
-        text = 'Remove CVE link'
-        return Link('+unlinkcve', text, icon='remove', enabled=enabled)
+        text = "Remove CVE link"
+        return Link("+unlinkcve", text, icon="remove", enabled=enabled)
 
     @property
     def _bug_question(self):
         return self.context.bug.getQuestionCreatedFromBug()
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def createquestion(self):
         """Create a question from this bug."""
-        text = 'Convert to a question'
+        text = "Convert to a question"
         enabled = self._bug_question is None
-        return Link('+create-question', text, enabled=enabled, icon='add')
+        return Link("+create-question", text, enabled=enabled, icon="add")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def removequestion(self):
         """Remove the created question from this bug."""
-        text = 'Convert back to a bug'
+        text = "Convert back to a bug"
         enabled = self._bug_question is not None
-        return Link('+remove-question', text, enabled=enabled, icon='remove')
+        return Link("+remove-question", text, enabled=enabled, icon="remove")
 
     def activitylog(self):
         """Return the 'Activity log' Link."""
-        text = 'See full activity log'
-        return Link('+activity', text)
+        text = "See full activity log"
+        return Link("+activity", text)
 
     def affectsmetoo(self):
         """Return the 'This bug affects me too' link."""
         enabled = getUtility(ILaunchBag).user is not None
-        return Link('+affectsmetoo', 'change', enabled=enabled)
+        return Link("+affectsmetoo", "change", enabled=enabled)
 
 
 class MaloneView(LaunchpadFormView):
@@ -398,25 +406,25 @@ class MaloneView(LaunchpadFormView):
     custom_widget_searchtext = CustomWidgetFactory(TextWidget, displayWidth=50)
     custom_widget_scope = ProjectScopeWidget
     schema = IFrontPageBugTaskSearch
-    field_names = ['searchtext', 'scope']
+    field_names = ["searchtext", "scope"]
 
     # Test: standalone/xx-slash-malone-slash-bugs.rst
     error_message = None
 
-    page_title = 'Launchpad Bugs'
+    page_title = "Launchpad Bugs"
 
     @property
     def target_css_class(self):
         """The CSS class for used in the target widget."""
         if self.target_error:
-            return 'error'
+            return "error"
         else:
             return None
 
     @property
     def target_error(self):
         """The error message for the target widget."""
-        return self.getFieldError('scope')
+        return self.getFieldError("scope")
 
     def initialize(self):
         """Initialize the view to handle the request."""
@@ -424,7 +432,7 @@ class MaloneView(LaunchpadFormView):
         bug_id = self.request.form.get("id")
         if bug_id:
             self._redirectToBug(bug_id)
-        elif self.widgets['scope'].hasInput():
+        elif self.widgets["scope"].hasInput():
             self._validate(action=None, data={})
 
     def _redirectToBug(self, bug_id):
@@ -446,17 +454,22 @@ class MaloneView(LaunchpadFormView):
     def most_recently_fixed_bugs(self):
         """Return the five most recently fixed bugs."""
         params = BugTaskSearchParams(
-            self.user, status=BugTaskStatus.FIXRELEASED,
-            date_closed=not_equals(None), orderby='-date_closed')
+            self.user,
+            status=BugTaskStatus.FIXRELEASED,
+            date_closed=not_equals(None),
+            orderby="-date_closed",
+        )
         return getUtility(IBugSet).getDistinctBugsForBugTasks(
-            self.context.searchTasks(params), self.user, limit=5)
+            self.context.searchTasks(params), self.user, limit=5
+        )
 
     @property
     def most_recently_reported_bugs(self):
         """Return the five most recently reported bugs."""
-        params = BugTaskSearchParams(self.user, orderby='-datecreated')
+        params = BugTaskSearchParams(self.user, orderby="-datecreated")
         return getUtility(IBugSet).getDistinctBugsForBugTasks(
-            self.context.searchTasks(params), self.user, limit=5)
+            self.context.searchTasks(params), self.user, limit=5
+        )
 
     def getCveBugLinkCount(self):
         """Return the number of links between bugs and CVEs there are."""
@@ -472,7 +485,8 @@ class BugViewMixin:
         if self.context.duplicateof is not None:
             naked_duplicate = removeSecurityProxy(self.context.duplicateof)
             active = getattr(
-                naked_duplicate.default_bugtask.target, 'active', True)
+                naked_duplicate.default_bugtask.target, "active", True
+            )
         return active
 
     @cachedproperty
@@ -499,14 +513,14 @@ class BugViewMixin:
         For example, "subscribed-false dup-subscribed-true".
         """
         if subscribed_person in self.duplicate_subscribers:
-            dup_class = 'dup-subscribed-true'
+            dup_class = "dup-subscribed-true"
         else:
-            dup_class = 'dup-subscribed-false'
+            dup_class = "dup-subscribed-false"
 
         if subscribed_person in self.direct_subscribers:
-            return 'subscribed-true %s' % dup_class
+            return "subscribed-true %s" % dup_class
         else:
-            return 'subscribed-false %s' % dup_class
+            return "subscribed-false %s" % dup_class
 
     @cachedproperty
     def _bug_attachments(self):
@@ -515,25 +529,26 @@ class BugViewMixin:
         # if you are looking to consolidate things.
         result = {
             BugAttachmentType.PATCH: [],
-            'other': [],
-            }
+            "other": [],
+        }
         for attachment in self.context.attachments_unpopulated:
             info = {
-                'attachment': attachment,
-                'file': ProxiedLibraryFileAlias(
-                    attachment.libraryfile, attachment),
-                }
+                "attachment": attachment,
+                "file": ProxiedLibraryFileAlias(
+                    attachment.libraryfile, attachment
+                ),
+            }
             if attachment.type == BugAttachmentType.PATCH:
                 key = attachment.type
             else:
-                key = 'other'
+                key = "other"
             result[key].append(info)
         return result
 
     @property
     def regular_attachments(self):
         """The list of bug attachments that are not patches."""
-        return self._bug_attachments['other']
+        return self._bug_attachments["other"]
 
     @property
     def patches(self):
@@ -553,8 +568,9 @@ class BugViewMixin:
         return self.context.getSpecifications(self.user)
 
 
-class BugInformationTypePortletView(InformationTypePortletMixin,
-                                    LaunchpadView):
+class BugInformationTypePortletView(
+    InformationTypePortletMixin, LaunchpadView
+):
     """View class for the information type portlet."""
 
 
@@ -594,24 +610,26 @@ class BugView(LaunchpadView, BugViewMixin):
         dupes_in_current_context = {
             bugtask.bug: bugtask
             for bugtask in current_task.target.searchTasks(
-                BugTaskSearchParams(self.user, bug=any(*duplicate_bugs)))}
+                BugTaskSearchParams(self.user, bug=any(*duplicate_bugs))
+            )
+        }
         dupes = []
         for bug in duplicate_bugs:
             # Don't disclose even the ID of a private bug. The link will
             # just 404.
-            if not check_permission('launchpad.View', bug):
+            if not check_permission("launchpad.View", bug):
                 continue
             dupe = {
-                'title': bug.title,
-                'id': bug.id,
-                'bug': bug,
-                }
+                "title": bug.title,
+                "id": bug.id,
+                "bug": bug,
+            }
             # If the dupe has the same context as the one we're in, link
             # to that bug task directly.
             if bug in dupes_in_current_context:
-                dupe['url'] = canonical_url(dupes_in_current_context[bug])
+                dupe["url"] = canonical_url(dupes_in_current_context[bug])
             else:
-                dupe['url'] = canonical_url(bug)
+                dupe["url"] = canonical_url(bug)
             dupes.append(dupe)
 
         return dupes
@@ -619,18 +637,22 @@ class BugView(LaunchpadView, BugViewMixin):
     def proxiedUrlForLibraryFile(self, attachment):
         """Return the proxied download URL for a Librarian file."""
         return ProxiedLibraryFileAlias(
-            attachment.libraryfile, attachment).http_url
+            attachment.libraryfile, attachment
+        ).http_url
 
 
 class BugActivity(BugView):
 
-    page_title = 'Activity log'
+    page_title = "Activity log"
 
     @cachedproperty
     def activity(self):
         activity = list(IBug(self.context).activity)
-        list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-            [a.person_id for a in activity], need_validity=True))
+        list(
+            getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                [a.person_id for a in activity], need_validity=True
+            )
+        )
         return activity
 
 
@@ -650,13 +672,14 @@ class BugSubscriptionPortletDetails:
         self.muted = False
         if user is not None:
             has_structural_subscriptions = not (
-                get_structural_subscriptions_for_bug(bug, user).is_empty())
+                get_structural_subscriptions_for_bug(bug, user).is_empty()
+            )
             self.muted = bug.isMuted(user)
             psi = PersonSubscriptions(user, bug)
             if psi.direct.personal:
                 self.direct_notifications = True
                 direct = psi.direct.personal[0]
-                cache['subscription'] = direct.subscription
+                cache["subscription"] = direct.subscription
                 level = direct.subscription.bug_notification_level
                 if level == BugNotificationLevel.COMMENTS:
                     self.direct_all_notifications = True
@@ -666,55 +689,66 @@ class BugSubscriptionPortletDetails:
                     assert level == BugNotificationLevel.LIFECYCLE
                     self.direct_lifecycle_notifications = True
             self.other_subscription_notifications = bool(
-                has_structural_subscriptions or
-                psi.from_duplicate.count or
-                psi.as_owner.count or
-                psi.as_assignee.count or
-                psi.direct.as_team_member or
-                psi.direct.as_team_admin)
-            cache['other_subscription_notifications'] = bool(
-                self.other_subscription_notifications)
+                has_structural_subscriptions
+                or psi.from_duplicate.count
+                or psi.as_owner.count
+                or psi.as_assignee.count
+                or psi.direct.as_team_member
+                or psi.direct.as_team_admin
+            )
+            cache["other_subscription_notifications"] = bool(
+                self.other_subscription_notifications
+            )
             self.only_other_subscription_notifications = (
-                self.other_subscription_notifications and
-                not self.direct_notifications)
+                self.other_subscription_notifications
+                and not self.direct_notifications
+            )
             self.any_subscription_notifications = (
-                self.other_subscription_notifications or
-                self.direct_notifications)
+                self.other_subscription_notifications
+                or self.direct_notifications
+            )
         self.user_should_see_mute_link = (
-            self.any_subscription_notifications or self.muted)
+            self.any_subscription_notifications or self.muted
+        )
 
 
-class BugSubscriptionPortletView(LaunchpadView,
-                                 BugSubscriptionPortletDetails, BugViewMixin):
+class BugSubscriptionPortletView(
+    LaunchpadView, BugSubscriptionPortletDetails, BugViewMixin
+):
     """View class for the subscription portlet."""
 
     # We want these strings to be available for the template and for the
     # JavaScript.
     notifications_text = {
-        'not_only_other_subscription': _('You are'),
-        'only_other_subscription': _(
-            'You have subscriptions that may cause you to receive '
-            'notifications, but you are'),
-        'direct_all': _('subscribed to all notifications for this bug.'),
-        'direct_metadata': _(
-            'subscribed to all notifications except comments for this bug.'),
-        'direct_lifecycle': _(
-            'subscribed to notifications when this bug is closed or '
-            'reopened.'),
-        'not_direct': _(
-            "not directly subscribed to this bug's notifications."),
-        'muted': _(
-            'Your personal email notifications from this bug are muted.'),
-        }
+        "not_only_other_subscription": _("You are"),
+        "only_other_subscription": _(
+            "You have subscriptions that may cause you to receive "
+            "notifications, but you are"
+        ),
+        "direct_all": _("subscribed to all notifications for this bug."),
+        "direct_metadata": _(
+            "subscribed to all notifications except comments for this bug."
+        ),
+        "direct_lifecycle": _(
+            "subscribed to notifications when this bug is closed or "
+            "reopened."
+        ),
+        "not_direct": _(
+            "not directly subscribed to this bug's notifications."
+        ),
+        "muted": _(
+            "Your personal email notifications from this bug are muted."
+        ),
+    }
 
     def initialize(self):
         """Initialize the view to handle the request."""
         LaunchpadView.initialize(self)
         cache = IJSONRequestCache(self.request).objects
         self.extractBugSubscriptionDetails(self.user, self.context, cache)
-        cache['bug_is_private'] = self.context.private
+        cache["bug_is_private"] = self.context.private
         if self.user:
-            cache['notifications_text'] = self.notifications_text
+            cache["notifications_text"] = self.notifications_text
 
 
 class BugWithoutContextView(RedirectionView):
@@ -728,16 +762,18 @@ class BugWithoutContextView(RedirectionView):
         redirected_context = context.default_bugtask
         viewname = getDefaultViewName(redirected_context, request)
         cache_view = getMultiAdapter(
-            (redirected_context, request), name=viewname)
+            (redirected_context, request), name=viewname
+        )
         super().__init__(
-            canonical_url(redirected_context), request, cache_view=cache_view)
+            canonical_url(redirected_context), request, cache_view=cache_view
+        )
 
 
 class BugEditViewBase(LaunchpadEditFormView):
     """Base class for all bug edit pages."""
 
     schema = IBug
-    page_title = 'Edit'
+    page_title = "Edit"
 
     def setUpWidgets(self):
         """Set up the widgets using the bug as the context."""
@@ -746,7 +782,8 @@ class BugEditViewBase(LaunchpadEditFormView):
     def updateBugFromData(self, data):
         """Update the bug using the values in the data dictionary."""
         LaunchpadEditFormView.updateContextFromData(
-            self, data, context=self.context.bug)
+            self, data, context=self.context.bug
+        )
 
     @property
     def next_url(self):
@@ -759,18 +796,18 @@ class BugEditViewBase(LaunchpadEditFormView):
 class BugEditView(BugEditViewBase):
     """The view for the edit bug page."""
 
-    field_names = ['title', 'description', 'tags']
+    field_names = ["title", "description", "tags"]
     custom_widget_title = CustomWidgetFactory(TextWidget, displayWidth=30)
     custom_widget_tags = BugTagsWidget
 
     @property
     def label(self):
         """The form label."""
-        return 'Edit details for bug #%d' % self.context.bug.id
+        return "Edit details for bug #%d" % self.context.bug.id
 
     page_title = label
 
-    @action('Change', name='change')
+    @action("Change", name="change")
     def change_action(self, action, data):
         """Update the bug with submitted changes."""
         self.updateBugFromData(data)
@@ -782,25 +819,24 @@ class BugLockStatusEditView(LaunchpadEditFormView):
     class schema(Interface):
         # Duplicating the fields is necessary because these fields are
         # read-only in `IBug`.
-        lock_status = copy_field(IBug['lock_status'], readonly=False)
-        lock_reason = copy_field(IBug['lock_reason'], readonly=False)
+        lock_status = copy_field(IBug["lock_status"], readonly=False)
+        lock_reason = copy_field(IBug["lock_reason"], readonly=False)
 
     @property
     def adapters(self):
         """See `LaunchpadFormView`."""
         return {self.schema: self.context.bug}
 
-    field_names = ['lock_status', 'lock_reason']
+    field_names = ["lock_status", "lock_reason"]
 
     custom_widget_lock_status = LaunchpadRadioWidgetWithDescription
     custom_widget_lock_reason = CustomWidgetFactory(
-        TextWidget,
-        displayWidth=30
+        TextWidget, displayWidth=30
     )
 
     def initialize(self):
         super().initialize()
-        lock_status_widget = self.widgets['lock_status']
+        lock_status_widget = self.widgets["lock_status"]
         lock_status_widget._displayItemForMissingValue = False
 
     @property
@@ -812,26 +848,26 @@ class BugLockStatusEditView(LaunchpadEditFormView):
 
     page_title = label
 
-    @action('Change', name='change')
+    @action("Change", name="change")
     def change_action(self, action, data):
         """Update the bug lock status and reason with submitted changes."""
         bug = self.context.bug
-        if bug.lock_status != data['lock_status']:
-            locked_states = (
-                BugLockStatus.COMMENT_ONLY,
-            )
-            if data['lock_status'] in locked_states:
+        if bug.lock_status != data["lock_status"]:
+            locked_states = (BugLockStatus.COMMENT_ONLY,)
+            if data["lock_status"] in locked_states:
                 bug.lock(
-                    status=data['lock_status'],
-                    reason=data['lock_reason'],
-                    who=self.user
+                    status=data["lock_status"],
+                    reason=data["lock_reason"],
+                    who=self.user,
                 )
             else:
                 bug.unlock(who=self.user)
 
-        elif (bug.lock_status != BugLockStatus.UNLOCKED and
-              bug.lock_reason != data['lock_reason']):
-            bug.setLockReason(data['lock_reason'], who=self.user)
+        elif (
+            bug.lock_status != BugLockStatus.UNLOCKED
+            and bug.lock_reason != data["lock_reason"]
+        ):
+            bug.setLockReason(data["lock_reason"], who=self.user)
 
     @property
     def next_url(self):
@@ -846,7 +882,7 @@ class BugLockStatusEditView(LaunchpadEditFormView):
 class BugMarkAsDuplicateView(BugEditViewBase):
     """Page for marking a bug as a duplicate."""
 
-    field_names = ['duplicateof']
+    field_names = ["duplicateof"]
     label = "Mark bug report as a duplicate"
     page_title = label
 
@@ -855,15 +891,16 @@ class BugMarkAsDuplicateView(BugEditViewBase):
         super().setUpFields()
 
         duplicateof_field = DuplicateBug(
-            __name__='duplicateof', title=_('Duplicate Of'), required=True)
+            __name__="duplicateof", title=_("Duplicate Of"), required=True
+        )
 
-        self.form_fields = self.form_fields.omit('duplicateof')
+        self.form_fields = self.form_fields.omit("duplicateof")
         self.form_fields = formlib.form.Fields(duplicateof_field)
 
     @property
     def initial_values(self):
         """See `LaunchpadFormView.`"""
-        return {'duplicateof': self.context.bug.duplicateof}
+        return {"duplicateof": self.context.bug.duplicateof}
 
     @property
     def next_url(self):
@@ -873,20 +910,23 @@ class BugMarkAsDuplicateView(BugEditViewBase):
         return None
 
     def _validate(self, action, data):
-        if action.name != 'remove':
+        if action.name != "remove":
             return super()._validate(action, data)
         return []
 
-    @action('Set Duplicate', name='change',
-        failure=LaunchpadFormView.ajax_failure_handler)
+    @action(
+        "Set Duplicate",
+        name="change",
+        failure=LaunchpadFormView.ajax_failure_handler,
+    )
     def change_action(self, action, data):
         """Update the bug."""
         data = dict(data)
         # We handle duplicate changes by hand instead of leaving it to
         # the usual machinery because we must use bug.markAsDuplicate().
         bug = self.context.bug
-        with notify_modified(bug, ['duplicateof']):
-            duplicateof = data.pop('duplicateof')
+        with notify_modified(bug, ["duplicateof"]):
+            duplicateof = data.pop("duplicateof")
             bug.markAsDuplicate(duplicateof)
         # Apply other changes.
         self.updateBugFromData(data)
@@ -895,12 +935,13 @@ class BugMarkAsDuplicateView(BugEditViewBase):
     def shouldShowRemoveButton(self, action):
         return self.context.bug.duplicateof is not None
 
-    @action('Remove Duplicate', name='remove',
-        condition=shouldShowRemoveButton)
+    @action(
+        "Remove Duplicate", name="remove", condition=shouldShowRemoveButton
+    )
     def remove_action(self, action, data):
         """Update the bug."""
         bug = self.context.bug
-        with notify_modified(bug, ['duplicateof']):
+        with notify_modified(bug, ["duplicateof"]):
             bug.markAsDuplicate(None)
         return self._duplicate_action_result()
 
@@ -910,8 +951,8 @@ class BugMarkAsDuplicateView(BugEditViewBase):
             launchbag = getUtility(ILaunchBag)
             launchbag.add(bug.default_bugtask)
             view = getMultiAdapter(
-                (bug, self.request),
-                name='+bugtasks-and-nominations-table')
+                (bug, self.request), name="+bugtasks-and-nominations-table"
+            )
             view.initialize()
             return view.render()
         return None
@@ -922,11 +963,11 @@ class BugSecrecyEditView(LaunchpadFormView, BugSubscriptionPortletDetails):
 
     @property
     def label(self):
-        return 'Bug #%i - Set information type' % self.context.bug.id
+        return "Bug #%i - Set information type" % self.context.bug.id
 
     page_title = label
 
-    field_names = ['information_type', 'validate_change']
+    field_names = ["information_type", "validate_change"]
 
     custom_widget_information_type = LaunchpadRadioWidgetWithDescription
     custom_widget_validate_change = GhostCheckBoxWidget
@@ -938,13 +979,17 @@ class BugSecrecyEditView(LaunchpadFormView, BugSubscriptionPortletDetails):
 
         class information_type_schema(Interface):
             information_type_field = copy_field(
-                IBug['information_type'], readonly=False,
-                vocabulary=InformationTypeVocabulary(types=info_types))
+                IBug["information_type"],
+                readonly=False,
+                vocabulary=InformationTypeVocabulary(types=info_types),
+            )
             # A hidden field used to determine if the new information type
             # should be validated to ensure the bug does not become invisible
             # after the change.
             validate_change = Bool(
-                title="Validate change", required=False, default=False)
+                title="Validate change", required=False, default=False
+            )
+
         return information_type_schema
 
     @property
@@ -959,65 +1004,78 @@ class BugSecrecyEditView(LaunchpadFormView, BugSubscriptionPortletDetails):
     @property
     def initial_values(self):
         """See `LaunchpadFormView.`"""
-        return {'information_type': self.context.bug.information_type}
+        return {"information_type": self.context.bug.information_type}
 
-    @action('Change', name='change',
-        failure=LaunchpadFormView.ajax_failure_handler)
+    @action(
+        "Change", name="change", failure=LaunchpadFormView.ajax_failure_handler
+    )
     def change_action(self, action, data):
         """Update the bug."""
         data = dict(data)
         bug = self.context.bug
-        information_type = data.pop('information_type')
-        changed_fields = ['information_type']
+        information_type = data.pop("information_type")
+        changed_fields = ["information_type"]
         # When the user first submits the form, validate change is True and
         # so we check that the bug does not become invisible. If the user
         # confirms they really want to make the change, validate change is
         # False and we process the change as normal.
         if self.request.is_ajax:
-            validate_change = data.get('validate_change', False)
-            if (validate_change and
-                information_type in PRIVATE_INFORMATION_TYPES and
-                self._bug_will_be_invisible(information_type)):
+            validate_change = data.get("validate_change", False)
+            if (
+                validate_change
+                and information_type in PRIVATE_INFORMATION_TYPES
+                and self._bug_will_be_invisible(information_type)
+            ):
                 self.request.response.setStatus(400, "Bug Visibility")
-                return ''
+                return ""
 
         user_will_be_subscribed = (
-            information_type in PRIVATE_INFORMATION_TYPES and
-            bug.getSubscribersForPerson(self.user).is_empty())
+            information_type in PRIVATE_INFORMATION_TYPES
+            and bug.getSubscribersForPerson(self.user).is_empty()
+        )
         bug_before_modification = Snapshot(bug, providing=providedBy(bug))
-        changed = bug.transitionToInformationType(
-            information_type, self.user)
+        changed = bug.transitionToInformationType(information_type, self.user)
         if changed:
             self._handlePrivacyChanged(user_will_be_subscribed)
             notify(
                 ObjectModifiedEvent(
-                    bug, bug_before_modification, changed_fields,
-                    user=self.user))
+                    bug,
+                    bug_before_modification,
+                    changed_fields,
+                    user=self.user,
+                )
+            )
         if self.request.is_ajax:
             # Avoid circular imports
             from lp.bugs.browser.bugtask import (
                 can_add_package_task_to_bug,
                 can_add_project_task_to_bug,
-                )
+            )
+
             if changed:
                 result_data = self._getSubscriptionDetails()
-                result_data['can_add_project_task'] = (
-                    can_add_project_task_to_bug(bug))
-                result_data['can_add_package_task'] = (
-                    can_add_package_task_to_bug(bug))
+                result_data[
+                    "can_add_project_task"
+                ] = can_add_project_task_to_bug(bug)
+                result_data[
+                    "can_add_package_task"
+                ] = can_add_package_task_to_bug(bug)
                 self.request.response.setHeader(
-                    'content-type', 'application/json')
+                    "content-type", "application/json"
+                )
                 return json.dumps(
-                    result_data, cls=ResourceJSONEncoder,
-                    media_type=EntryResource.JSON_TYPE)
+                    result_data,
+                    cls=ResourceJSONEncoder,
+                    media_type=EntryResource.JSON_TYPE,
+                )
             else:
-                return ''
+                return ""
 
     def _bug_will_be_invisible(self, information_type):
         # Return true if this bug will be totally invisible if it were to be
         # change to the specified information type.
         pillars = self.context.bug.affected_pillars
-        service = getUtility(IService, 'sharing')
+        service = getUtility(IService, "sharing")
         for pillar in pillars:
             grant_counts = service.getAccessPolicyGrantCounts(pillar)
             for count_info in grant_counts:
@@ -1037,11 +1095,12 @@ class BugSecrecyEditView(LaunchpadFormView, BugSubscriptionPortletDetails):
         else:
             bug = self.context
         subscribers_portlet = BugPortletSubscribersWithDetails(
-            bug, self.request)
+            bug, self.request
+        )
         subscription_data = subscribers_portlet.subscriber_data
         result_data = dict(
-            cache_data=cache,
-            subscription_data=subscription_data)
+            cache_data=cache, subscription_data=subscription_data
+        )
         return result_data
 
     def _handlePrivacyChanged(self, user_will_be_subscribed):
@@ -1054,18 +1113,20 @@ class BugSecrecyEditView(LaunchpadFormView, BugSubscriptionPortletDetails):
         """
         if user_will_be_subscribed:
             notification_text = (
-                    "Since you marked this bug as private you have "
-                    "automatically been subscribed to it. "
-                    "If you don't want to receive email about "
-                    "this bug you can <a href=\"%s\">mute your "
-                    "subscription</a> or <a href=\"%s\">"
-                    "unsubscribe</a>." % (
-                    canonical_url(
-                        self.context, view_name='+mute'),
-                    canonical_url(
-                        self.context, view_name='+subscribe')))
+                "Since you marked this bug as private you have "
+                "automatically been subscribed to it. "
+                "If you don't want to receive email about "
+                'this bug you can <a href="%s">mute your '
+                'subscription</a> or <a href="%s">'
+                "unsubscribe</a>."
+                % (
+                    canonical_url(self.context, view_name="+mute"),
+                    canonical_url(self.context, view_name="+subscribe"),
+                )
+            )
             self.request.response.addInfoNotification(
-                structured(notification_text))
+                structured(notification_text)
+            )
 
 
 class DeprecatedAssignedBugsView(RedirectionView):
@@ -1084,7 +1145,8 @@ class DeprecatedAssignedBugsView(RedirectionView):
 
     def __call__(self):
         self.target = canonical_url(
-            getUtility(ILaunchBag).user, view_name='+assignedbugs')
+            getUtility(ILaunchBag).user, view_name="+assignedbugs"
+        )
         super().__call__()
 
     @property
@@ -1092,7 +1154,7 @@ class DeprecatedAssignedBugsView(RedirectionView):
         return self._context
 
 
-normalize_mime_type = re.compile(r'\s+')
+normalize_mime_type = re.compile(r"\s+")
 
 
 class BugTextView(LaunchpadView):
@@ -1109,7 +1171,8 @@ class BugTextView(LaunchpadView):
                 authorised_people.append(task.assignee)
         authorised_people.extend(self.subscribers)
         precache_permission_for_objects(
-            self.request, 'launchpad.LimitedView', authorised_people)
+            self.request, "launchpad.LimitedView", authorised_people
+        )
 
     @cachedproperty
     def bugtasks(self):
@@ -1119,8 +1182,11 @@ class BugTextView(LaunchpadView):
     @cachedproperty
     def subscribers(self):
         """Cache subscribers and avoid hitting the DB twice."""
-        return [sub.person for sub in self.context.subscriptions
-                if self.user or not sub.person.private]
+        return [
+            sub.person
+            for sub in self.context.subscriptions
+            if self.user or not sub.person.private
+        ]
 
     def bug_text(self):
 
@@ -1128,99 +1194,112 @@ class BugTextView(LaunchpadView):
         bug = self.context
 
         text = []
-        text.append('bug: %d' % bug.id)
-        text.append('title: %s' % bug.title)
-        text.append('date-reported: %s' %
-            format_rfc2822_date(bug.datecreated))
-        text.append('date-updated: %s' %
-            format_rfc2822_date(bug.date_last_updated))
-        text.append('reporter: %s' % bug.owner.unique_displayname)
+        text.append("bug: %d" % bug.id)
+        text.append("title: %s" % bug.title)
+        text.append("date-reported: %s" % format_rfc2822_date(bug.datecreated))
+        text.append(
+            "date-updated: %s" % format_rfc2822_date(bug.date_last_updated)
+        )
+        text.append("reporter: %s" % bug.owner.unique_displayname)
 
         if bug.duplicateof:
-            text.append('duplicate-of: %d' % bug.duplicateof.id)
+            text.append("duplicate-of: %d" % bug.duplicateof.id)
         else:
-            text.append('duplicate-of: ')
+            text.append("duplicate-of: ")
 
         if bug.duplicates:
-            dupes = ' '.join(str(dupe.id) for dupe in bug.duplicates)
-            text.append('duplicates: %s' % dupes)
+            dupes = " ".join(str(dupe.id) for dupe in bug.duplicates)
+            text.append("duplicates: %s" % dupes)
         else:
-            text.append('duplicates: ')
+            text.append("duplicates: ")
 
         if bug.private:
             # XXX kiko 2007-10-31: this could include date_made_private and
             # who_made_private but Bjorn doesn't let me.
-            text.append('private: yes')
+            text.append("private: yes")
 
         if bug.security_related:
-            text.append('security: yes')
+            text.append("security: yes")
 
         patches = []
-        text.append('attachments: ')
+        text.append("attachments: ")
         for attachment in bug.attachments_unpopulated:
             if attachment.type != BugAttachmentType.PATCH:
-                text.append(' %s' % self.attachment_text(attachment))
+                text.append(" %s" % self.attachment_text(attachment))
             else:
                 patches.append(attachment)
 
-        text.append('patches: ')
+        text.append("patches: ")
         for attachment in patches:
-            text.append(' %s' % self.attachment_text(attachment))
+            text.append(" %s" % self.attachment_text(attachment))
 
-        text.append('tags: %s' % ' '.join(bug.tags))
+        text.append("tags: %s" % " ".join(bug.tags))
 
-        text.append('subscribers: ')
+        text.append("subscribers: ")
         for subscriber in self.subscribers:
-            text.append(' %s' % subscriber.unique_displayname)
+            text.append(" %s" % subscriber.unique_displayname)
 
-        return ''.join(line + '\n' for line in text)
+        return "".join(line + "\n" for line in text)
 
     def bugtask_text(self, task):
         """Return a BugTask for text display."""
         text = []
-        text.append('task: %s' % task.bugtargetname)
-        text.append('status: %s' % task.status.title)
-        text.append('date-created: %s' %
-            format_rfc2822_date(task.datecreated))
-
-        for status in ["left_new", "confirmed", "triaged", "assigned",
-                       "inprogress", "closed", "incomplete",
-                       "fix_committed", "fix_released", "left_closed"]:
+        text.append("task: %s" % task.bugtargetname)
+        text.append("status: %s" % task.status.title)
+        text.append("date-created: %s" % format_rfc2822_date(task.datecreated))
+
+        for status in [
+            "left_new",
+            "confirmed",
+            "triaged",
+            "assigned",
+            "inprogress",
+            "closed",
+            "incomplete",
+            "fix_committed",
+            "fix_released",
+            "left_closed",
+        ]:
             date = getattr(task, "date_%s" % status)
             if date:
-                text.append("date-%s: %s" % (
-                    status.replace('_', '-'), format_rfc2822_date(date)))
+                text.append(
+                    "date-%s: %s"
+                    % (status.replace("_", "-"), format_rfc2822_date(date))
+                )
 
-        text.append('reporter: %s' % task.owner.unique_displayname)
+        text.append("reporter: %s" % task.owner.unique_displayname)
 
         if task.bugwatch:
-            text.append('watch: %s' % task.bugwatch.url)
+            text.append("watch: %s" % task.bugwatch.url)
 
-        text.append('importance: %s' % task.importance.title)
+        text.append("importance: %s" % task.importance.title)
 
         component = task.getPackageComponent()
         if component:
-            text.append('component: %s' % component.name)
+            text.append("component: %s" % component.name)
 
-        if (task.assignee
-            and check_permission('launchpad.LimitedView', task.assignee)):
-            text.append('assignee: %s' % task.assignee.unique_displayname)
+        if task.assignee and check_permission(
+            "launchpad.LimitedView", task.assignee
+        ):
+            text.append("assignee: %s" % task.assignee.unique_displayname)
         else:
-            text.append('assignee: ')
+            text.append("assignee: ")
 
         if task.milestone:
-            text.append('milestone: %s' % task.milestone.name)
+            text.append("milestone: %s" % task.milestone.name)
         else:
-            text.append('milestone: ')
+            text.append("milestone: ")
 
-        return ''.join(line + '\n' for line in text)
+        return "".join(line + "\n" for line in text)
 
     def attachment_text(self, attachment):
         """Return a text representation of a bug attachment."""
         mime_type = normalize_mime_type.sub(
-            ' ', attachment.libraryfile.mimetype)
+            " ", attachment.libraryfile.mimetype
+        )
         download_url = ProxiedLibraryFileAlias(
-            attachment.libraryfile, attachment).http_url
+            attachment.libraryfile, attachment
+        ).http_url
         return "%s %s" % (download_url, mime_type)
 
     def comment_text(self):
@@ -1229,16 +1308,15 @@ class BugTextView(LaunchpadView):
         def build_message(text):
             mailwrapper = MailWrapper(width=72)
             text = mailwrapper.format(text)
-            message = MIMEText(text.encode('utf-8'),
-                'plain', 'utf-8')
+            message = MIMEText(text.encode("utf-8"), "plain", "utf-8")
             # This is redundant and makes the template noisy
-            del message['MIME-Version']
+            del message["MIME-Version"]
             return message
 
         from lp.bugs.browser.bugtask import (
             get_comments_for_bugtask,
             get_visible_comments,
-            )
+        )
 
         # XXX: kiko 2007-10-31: for some reason, get_comments_for_bugtask
         # takes a task, not a bug. For now live with it.
@@ -1252,16 +1330,16 @@ class BugTextView(LaunchpadView):
 
         for comment in comments:
             message = build_message(comment.text_for_display)
-            message['Author'] = comment.owner.unique_displayname
-            message['Date'] = format_rfc2822_date(comment.datecreated)
-            message['Message-Id'] = comment.rfc822msgid
+            message["Author"] = comment.owner.unique_displayname
+            message["Date"] = format_rfc2822_date(comment.datecreated)
+            message["Message-Id"] = comment.rfc822msgid
             comment_mime.attach(message)
 
-        return message_as_bytes(comment_mime).decode('utf-8')
+        return message_as_bytes(comment_mime).decode("utf-8")
 
     def render(self):
         """Return a text representation of the bug."""
-        self.request.response.setHeader('Content-type', 'text/plain')
+        self.request.response.setHeader("Content-type", "text/plain")
         texts = [self.bug_text()]
         texts.extend(self.bugtask_text(task) for task in self.bugtasks)
         texts.append(self.comment_text())
@@ -1273,7 +1351,7 @@ class BugURL:
     """Bug URL creation rules."""
 
     inside = None
-    rootsite = 'bugs'
+    rootsite = "bugs"
 
     def __init__(self, context):
         self.context = context
@@ -1287,24 +1365,29 @@ class BugURL:
 class BugAffectingUserChoice(EnumeratedType):
     """The choices for a bug affecting a user."""
 
-    YES = Item("""
+    YES = Item(
+        """
         Yes
 
         This bug affects me.
-        """)
+        """
+    )
 
-    NO = Item("""
+    NO = Item(
+        """
         No
 
         This bug doesn't affect me.
-        """)
+        """
+    )
 
 
 class BugMarkAsAffectingUserForm(Interface):
     """Form schema for marking the bug as affecting the user."""
+
     affects = Choice(
-        title=_('Does this bug affect you?'),
-        vocabulary=BugAffectingUserChoice)
+        title=_("Does this bug affect you?"), vocabulary=BugAffectingUserChoice
+    )
 
 
 class BugMarkAsAffectingUserView(LaunchpadFormView):
@@ -1312,7 +1395,7 @@ class BugMarkAsAffectingUserView(LaunchpadFormView):
 
     schema = BugMarkAsAffectingUserForm
 
-    field_names = ['affects']
+    field_names = ["affects"]
     label = "Does this bug affect you?"
     page_title = label
 
@@ -1327,11 +1410,12 @@ class BugMarkAsAffectingUserView(LaunchpadFormView):
         else:
             affects = BugAffectingUserChoice.NO
 
-        return {'affects': affects}
+        return {"affects": affects}
 
-    @action('Change', name='change')
+    @action("Change", name="change")
     def change_action(self, action, data):
         """Mark the bug according to the selection."""
         self.context.bug.markUserAffected(
-            self.user, data['affects'] == BugAffectingUserChoice.YES)
+            self.user, data["affects"] == BugAffectingUserChoice.YES
+        )
         self.request.response.redirect(canonical_url(self.context.bug))
diff --git a/lib/lp/bugs/browser/bugalsoaffects.py b/lib/lp/bugs/browser/bugalsoaffects.py
index 7372c56..5525100 100644
--- a/lib/lp/bugs/browser/bugalsoaffects.py
+++ b/lib/lp/bugs/browser/bugalsoaffects.py
@@ -2,17 +2,14 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'BugAlsoAffectsProductMetaView',
-    'BugAlsoAffectsDistroMetaView',
-    'BugAlsoAffectsProductWithProductCreationView'
-    ]
+    "BugAlsoAffectsProductMetaView",
+    "BugAlsoAffectsDistroMetaView",
+    "BugAlsoAffectsProductWithProductCreationView",
+]
 
 from textwrap import dedent
 
-from lazr.enum import (
-    EnumeratedType,
-    Item,
-    )
+from lazr.enum import EnumeratedType, Item
 from lazr.lifecycle.event import ObjectCreatedEvent
 from zope.browserpage import ViewPageTemplateFile
 from zope.component import getUtility
@@ -22,20 +19,11 @@ from zope.formlib.interfaces import MissingInputError
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.widgets import DropdownWidget
 from zope.schema import Choice
-from zope.schema.vocabulary import (
-    SimpleTerm,
-    SimpleVocabulary,
-    )
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 
 from lp import _
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
-from lp.app.browser.multistep import (
-    MultiStepView,
-    StepView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
+from lp.app.browser.multistep import MultiStepView, StepView
 from lp.app.enums import ServiceUsage
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.app.validators.email import email_validator
@@ -44,7 +32,7 @@ from lp.app.widgets.popup import SearchForUpstreamPopupWidget
 from lp.app.widgets.textwidgets import StrippedTextWidget
 from lp.bugs.browser.widgets.bugtask import (
     BugTaskAlsoAffectsSourcePackageNameWidget,
-    )
+)
 from lp.bugs.interfaces.bug import IBug
 from lp.bugs.interfaces.bugtask import (
     BugTaskImportance,
@@ -53,31 +41,19 @@ from lp.bugs.interfaces.bugtask import (
     IAddBugTaskWithProductCreationForm,
     IllegalTarget,
     valid_remote_bug_url,
-    )
-from lp.bugs.interfaces.bugtracker import (
-    BugTrackerType,
-    IBugTrackerSet,
-    )
+)
+from lp.bugs.interfaces.bugtracker import BugTrackerType, IBugTrackerSet
 from lp.bugs.interfaces.bugwatch import (
     IBugWatchSet,
     NoBugTrackerFound,
     UnrecognizedBugTrackerURL,
-    )
-from lp.bugs.model.bugtask import (
-    validate_new_target,
-    validate_target,
-    )
+)
+from lp.bugs.model.bugtask import validate_new_target, validate_target
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
-    )
-from lp.registry.interfaces.packaging import (
-    IPackagingUtil,
-    PackagingType,
-    )
-from lp.registry.interfaces.product import (
-    IProductSet,
-    License,
-    )
+)
+from lp.registry.interfaces.packaging import IPackagingUtil, PackagingType
+from lp.registry.interfaces.product import IProductSet, License
 from lp.registry.model.product import Product
 from lp.services.features import getFeatureFlag
 from lp.services.fields import StrippedTextLine
@@ -88,7 +64,7 @@ from lp.services.webapp.interfaces import ILaunchBag
 
 
 class BugAlsoAffectsProductMetaView(MultiStepView):
-    page_title = 'Record as affecting another project'
+    page_title = "Record as affecting another project"
 
     @property
     def first_step(self):
@@ -96,7 +72,7 @@ class BugAlsoAffectsProductMetaView(MultiStepView):
 
 
 class BugAlsoAffectsDistroMetaView(MultiStepView):
-    page_title = 'Record as affecting another distribution/package'
+    page_title = "Record as affecting another distribution/package"
 
     @property
     def first_step(self):
@@ -104,17 +80,17 @@ class BugAlsoAffectsDistroMetaView(MultiStepView):
 
 
 class AlsoAffectsStep(StepView):
-    __launchpad_facetname__ = 'bugs'
+    __launchpad_facetname__ = "bugs"
     schema = IAddBugTaskForm
 
 
 class LinkPackgingMixin:
-
     @property
     def can_link_package(self):
         bugtask = self.context
         is_package_bugtask = IDistributionSourcePackage.providedBy(
-            bugtask.target)
+            bugtask.target
+        )
         return is_package_bugtask and bugtask.target.upstream_product is None
 
 
@@ -122,7 +98,8 @@ class ChooseProductStep(LinkPackgingMixin, AlsoAffectsStep):
     """View for choosing a product that is affected by a given bug."""
 
     template = ViewPageTemplateFile(
-        '../templates/bugtask-choose-affected-product.pt')
+        "../templates/bugtask-choose-affected-product.pt"
+    )
 
     custom_widget_product = SearchForUpstreamPopupWidget
     label = "Record as affecting another project"
@@ -131,15 +108,18 @@ class ChooseProductStep(LinkPackgingMixin, AlsoAffectsStep):
     @property
     def _field_names(self):
         """The fields needed to choose an existing project."""
-        names = ['product']
+        names = ["product"]
         if self.can_link_package:
-            names.append('add_packaging')
+            names.append("add_packaging")
         return names
 
     def initialize(self):
         super().initialize()
-        if (self.widgets['product'].hasInput() or
-            not IDistributionSourcePackage.providedBy(self.context.target)):
+        if self.widgets[
+            "product"
+        ].hasInput() or not IDistributionSourcePackage.providedBy(
+            self.context.target
+        ):
             return
 
         self.maybeAddNotificationOrTeleport()
@@ -164,20 +144,20 @@ class ChooseProductStep(LinkPackgingMixin, AlsoAffectsStep):
                 # We can infer the upstream and there's no bugtask for it,
                 # so we can go straight to the page asking for the remote
                 # bug URL.
-                self.request.form['field.product'] = upstream.name
-                self.request.form['field.add_packaging'] = 'off'
+                self.request.form["field.product"] = upstream.name
+                self.request.form["field.add_packaging"] = "off"
                 self.next_step = ProductBugTaskCreationStep
             return
 
     def validateStep(self, data):
-        if data.get('product'):
+        if data.get("product"):
             try:
-                validate_new_target(self.context.bug, data.get('product'))
+                validate_new_target(self.context.bug, data.get("product"))
             except IllegalTarget as e:
-                self.setFieldError('product', e.args[0])
+                self.setFieldError("product", e.args[0])
             return
 
-        entered_product = self.request.form.get(self.widgets['product'].name)
+        entered_product = self.request.form.get(self.widgets["product"].name)
         if not entered_product:
             return
 
@@ -185,10 +165,11 @@ class ChooseProductStep(LinkPackgingMixin, AlsoAffectsStep):
         # Tell the user to search for it using the popup widget as it'll allow
         # the user to register a new product if the one they are looking for
         # is not yet registered.
-        widget_link_id = self.widgets['product'].show_widget_id
+        widget_link_id = self.widgets["product"].show_widget_id
         self.setFieldError(
-            'product',
-            structured("""
+            "product",
+            structured(
+                """
                 There is no project in Launchpad named "%s". Please
                 <a href="/projects"
                 onclick="LPJS.use('event').Event.simulate(
@@ -196,17 +177,20 @@ class ChooseProductStep(LinkPackgingMixin, AlsoAffectsStep):
                          return false;"
                 >search for it</a> as it may be
                 registered with a different name.""",
-                entered_product, widget_link_id))
+                entered_product,
+                widget_link_id,
+            ),
+        )
 
     def main_action(self, data):
         """Perform the 'Continue' action."""
         # Inject the selected product into the form and set the next_step to
         # be used by our multistep controller.
-        self.request.form['field.product'] = data['product'].name
-        if data.get('add_packaging', False):
-            self.request.form['field.add_packaging'] = 'on'
+        self.request.form["field.product"] = data["product"].name
+        if data.get("add_packaging", False):
+            self.request.form["field.add_packaging"] = "on"
         else:
-            self.request.form['field.add_packaging'] = 'off'
+            self.request.form["field.add_packaging"] = "off"
         self.next_step = ProductBugTaskCreationStep
 
 
@@ -223,10 +207,11 @@ class BugTaskCreationStep(AlsoAffectsStep):
     """
 
     custom_widget_bug_url = CustomWidgetFactory(
-        StrippedTextWidget, displayWidth=62)
+        StrippedTextWidget, displayWidth=62
+    )
 
-    initial_focus_widget = 'bug_url'
-    step_name = 'specify_remote_bug_url'
+    initial_focus_widget = "bug_url"
+    step_name = "specify_remote_bug_url"
     target_field_names = ()
 
     # This is necessary so that other views which dispatch work to this one
@@ -236,15 +221,16 @@ class BugTaskCreationStep(AlsoAffectsStep):
     def __init__(self, context, request):
         super().__init__(context, request)
         self.notifications = []
-        self._field_names = ['bug_url'] + list(self.target_field_names)
+        self._field_names = ["bug_url"] + list(self.target_field_names)
 
     def setUpWidgets(self):
         super().setUpWidgets()
         self.target_widgets = [
             self.widgets[field_name]
             for field_name in self.field_names
-            if field_name in self.target_field_names]
-        self.bugwatch_widgets = [self.widgets['bug_url']]
+            if field_name in self.target_field_names
+        ]
+        self.bugwatch_widgets = [self.widgets["bug_url"]]
 
     def getTarget(self, data=None):
         """Return the fix target.
@@ -261,7 +247,7 @@ class BugTaskCreationStep(AlsoAffectsStep):
         that URL we create a bug watch and link it to the newly created bug
         task.
         """
-        bug_url = data.get('bug_url', '')
+        bug_url = data.get("bug_url", "")
         target = self.getTarget(data)
 
         extracted_bug = None
@@ -269,63 +255,75 @@ class BugTaskCreationStep(AlsoAffectsStep):
         if bug_url:
             try:
                 extracted_bugtracker, extracted_bug = getUtility(
-                    IBugWatchSet).extractBugTrackerAndBug(bug_url)
+                    IBugWatchSet
+                ).extractBugTrackerAndBug(bug_url)
             except NoBugTrackerFound:
                 # Delegate to another view which will ask the user if they
                 # wants to create the bugtracker now.
-                if 'product' in self.target_field_names:
+                if "product" in self.target_field_names:
                     self.next_step = UpstreamBugTrackerCreationStep
                 else:
-                    assert 'distribution' in self.target_field_names
+                    assert "distribution" in self.target_field_names
                     self.next_step = DistroBugTrackerCreationStep
                 return
 
-        if data.get('product') is not None:
-            task_target = data['product']
+        if data.get("product") is not None:
+            task_target = data["product"]
         else:
-            task_target = data['distribution']
-            if data.get('sourcepackagename') is not None:
-                task_target = data['sourcepackagename']
+            task_target = data["distribution"]
+            if data.get("sourcepackagename") is not None:
+                task_target = data["sourcepackagename"]
         # The new target has already been validated so don't do it again.
         self.task_added = self.context.bug.addTask(
-            getUtility(ILaunchBag).user, task_target, validate_target=False)
+            getUtility(ILaunchBag).user, task_target, validate_target=False
+        )
         task_added = self.task_added
 
         if extracted_bug is not None:
-            assert extracted_bugtracker is not None, (
-                "validate() should have ensured that bugtracker is not None.")
+            assert (
+                extracted_bugtracker is not None
+            ), "validate() should have ensured that bugtracker is not None."
             # Display a notification, if another bug is already linked
             # to the same external bug.
             other_bugs_already_watching = [
-                bug for bug in extracted_bugtracker.getBugsWatching(
-                    extracted_bug)
-                if bug != self.context.bug]
+                bug
+                for bug in extracted_bugtracker.getBugsWatching(extracted_bug)
+                if bug != self.context.bug
+            ]
             # Simply add one notification per bug to simplify the
             # implementation; most of the time it will be only one bug.
             for other_bug in other_bugs_already_watching:
                 self.request.response.addInfoNotification(
                     structured(
-                    '<a href="%(bug_url)s">Bug #%(bug_id)s</a> also links'
-                    ' to the added bug watch'
-                    ' (%(bugtracker_name)s #%(remote_bug)s).',
-                    bug_url=canonical_url(other_bug),
-                    bug_id=str(other_bug.id),
-                    bugtracker_name=extracted_bugtracker.name,
-                    remote_bug=extracted_bug))
+                        '<a href="%(bug_url)s">Bug #%(bug_id)s</a> also links'
+                        " to the added bug watch"
+                        " (%(bugtracker_name)s #%(remote_bug)s).",
+                        bug_url=canonical_url(other_bug),
+                        bug_id=str(other_bug.id),
+                        bugtracker_name=extracted_bugtracker.name,
+                        remote_bug=extracted_bug,
+                    )
+                )
 
             # Make sure that we don't add duplicate bug watches.
             bug_watch = task_added.bug.getBugWatch(
-                extracted_bugtracker, extracted_bug)
+                extracted_bugtracker, extracted_bug
+            )
             if bug_watch is None:
                 bug_watch = task_added.bug.addWatch(
-                    extracted_bugtracker, extracted_bug, self.user)
+                    extracted_bugtracker, extracted_bug, self.user
+                )
             if target.bug_tracking_usage != ServiceUsage.LAUNCHPAD:
                 task_added.bugwatch = bug_watch
 
-        if (target.bug_tracking_usage != ServiceUsage.LAUNCHPAD
+        if (
+            target.bug_tracking_usage != ServiceUsage.LAUNCHPAD
             and task_added.bugwatch is not None
-            and (task_added.bugwatch.bugtracker.bugtrackertype !=
-                 BugTrackerType.EMAILADDRESS)):
+            and (
+                task_added.bugwatch.bugtracker.bugtrackertype
+                != BugTrackerType.EMAILADDRESS
+            )
+        ):
             # A remote bug task gets its status from a bug watch, so
             # we want its status/importance to be UNKNOWN when
             # created. Status updates cannot be fetched from Email
@@ -333,10 +331,10 @@ class BugTaskCreationStep(AlsoAffectsStep):
             # importance to be updated manually, so we do not reset
             # the status and importance here.
             bug_importer = getUtility(ILaunchpadCelebrities).bug_importer
-            task_added.transitionToStatus(
-                BugTaskStatus.UNKNOWN, bug_importer)
+            task_added.transitionToStatus(BugTaskStatus.UNKNOWN, bug_importer)
             task_added.transitionToImportance(
-                BugTaskImportance.UNKNOWN, bug_importer)
+                BugTaskImportance.UNKNOWN, bug_importer
+            )
 
         notify(ObjectCreatedEvent(task_added))
         self.next_url = canonical_url(task_added)
@@ -345,49 +343,54 @@ class BugTaskCreationStep(AlsoAffectsStep):
 class IAddDistroBugTaskForm(IAddBugTaskForm):
 
     sourcepackagename = Choice(
-        title=_("Source Package Name"), required=False,
-        description=_("The source package in which the bug occurs. "
-                      "Leave blank if you are not sure."),
-        vocabulary='DistributionSourcePackage')
+        title=_("Source Package Name"),
+        required=False,
+        description=_(
+            "The source package in which the bug occurs. "
+            "Leave blank if you are not sure."
+        ),
+        vocabulary="DistributionSourcePackage",
+    )
 
 
 class DistroBugTaskCreationStep(BugTaskCreationStep):
-    """Specialized BugTaskCreationStep for reporting a bug in a distribution.
-    """
+    """Specialized BugTaskCreationStep for reporting a bug in a distro."""
 
     @property
     def schema(self):
-        if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
+        if bool(getFeatureFlag("disclosure.dsp_picker.enabled")):
             return IAddDistroBugTaskForm
         else:
             return IAddBugTaskForm
 
     custom_widget_sourcepackagename = BugTaskAlsoAffectsSourcePackageNameWidget
 
-    template = ViewPageTemplateFile('../templates/bugtask-requestfix.pt')
+    template = ViewPageTemplateFile("../templates/bugtask-requestfix.pt")
 
     label = "Also affects distribution/package"
-    target_field_names = ('distribution', 'sourcepackagename')
+    target_field_names = ("distribution", "sourcepackagename")
 
     @property
     def initial_values(self):
         """Return the initial values for the view's fields."""
-        return {'distribution': getUtility(ILaunchpadCelebrities).ubuntu}
+        return {"distribution": getUtility(ILaunchpadCelebrities).ubuntu}
 
     def getTarget(self, data=None):
         if data is not None:
-            return data.get('distribution')
+            return data.get("distribution")
         else:
-            return self.widgets['distribution'].getInputValue()
+            return self.widgets["distribution"].getInputValue()
 
     def main_action(self, data):
         """Create the new bug task, confirming if necessary."""
-        bug_url = data.get('bug_url', '')
+        bug_url = data.get("bug_url", "")
         target = self.getTarget(data)
 
-        if (not bug_url and
-            not self.request.get('ignore_missing_remote_bug') and
-            target.bug_tracking_usage != ServiceUsage.LAUNCHPAD):
+        if (
+            not bug_url
+            and not self.request.get("ignore_missing_remote_bug")
+            and target.bug_tracking_usage != ServiceUsage.LAUNCHPAD
+        ):
             # We have no URL for the remote bug and the target does not use
             # Launchpad for bug tracking, so we warn the user this is not
             # optimal and ask for their confirmation.
@@ -399,14 +402,21 @@ class DistroBugTaskCreationStep(BugTaskCreationStep):
                 '<input type="hidden" name="%s" value="1" />'
                 '<input style="font-size: smaller" type="submit"'
                 ' value="Add Anyway" name="ignore_missing_remote_bug" />',
-                self.continue_action.__name__)
-            self.notifications.append(structured(
-                dedent("""
+                self.continue_action.__name__,
+            )
+            self.notifications.append(
+                structured(
+                    dedent(
+                        """
                     %s doesn't use Launchpad as its bug tracker. Without a bug
                     URL to watch, the %s status will not update automatically.
-                    %s"""),
-                target.displayname, target.displayname,
-                confirm_button).escapedtext)
+                    %s"""
+                    ),
+                    target.displayname,
+                    target.displayname,
+                    confirm_button,
+                ).escapedtext
+            )
             return None
         # Create the task.
         return super().main_action(data)
@@ -419,31 +429,36 @@ class DistroBugTaskCreationStep(BugTaskCreationStep):
         3. it's possible to create a new task for the given package/distro.
         """
         target = self.getTarget(data)
-        bug_url = data.get('bug_url')
+        bug_url = data.get("bug_url")
         if bug_url and target.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             self.addError(
                 "Bug watches can not be added for %s, as it uses Launchpad"
                 " as its official bug tracker. Alternatives are to add a"
                 " watch for another project, or a comment containing a"
-                " URL to the related bug report." % target.displayname)
+                " URL to the related bug report." % target.displayname
+            )
 
-        distribution = data.get('distribution')
-        sourcepackagename = data.get('sourcepackagename')
+        distribution = data.get("distribution")
+        sourcepackagename = data.get("sourcepackagename")
         entered_package = self.request.form.get(
-            self.widgets['sourcepackagename'].name)
+            self.widgets["sourcepackagename"].name
+        )
         if sourcepackagename is None and entered_package:
             # The entered package doesn't exist.
             if distribution.has_published_binaries:
-                binary_tracking = ''
+                binary_tracking = ""
             else:
                 binary_tracking = structured(
-                    ' Launchpad does not track binary package names '
-                    'in %s.', distribution.displayname)
+                    " Launchpad does not track binary package names " "in %s.",
+                    distribution.displayname,
+                )
             error = structured(
                 'There is no package in %s named "%s".%s',
-                distribution.displayname, entered_package,
-                binary_tracking)
-            self.setFieldError('sourcepackagename', error)
+                distribution.displayname,
+                entered_package,
+                binary_tracking,
+            )
+            self.setFieldError("sourcepackagename", error)
         elif not IDistributionSourcePackage.providedBy(sourcepackagename):
             try:
                 target = distribution
@@ -452,27 +467,30 @@ class DistroBugTaskCreationStep(BugTaskCreationStep):
                 # The validity of the source package has already been checked
                 # by the bug target widget.
                 validate_new_target(
-                    self.context.bug, target, check_source_package=False)
+                    self.context.bug, target, check_source_package=False
+                )
                 if sourcepackagename:
-                    data['sourcepackagename'] = target
+                    data["sourcepackagename"] = target
             except IllegalTarget as e:
                 if sourcepackagename:
-                    self.setFieldError('sourcepackagename', e.args[0])
+                    self.setFieldError("sourcepackagename", e.args[0])
                 else:
-                    self.setFieldError('distribution', e.args[0])
+                    self.setFieldError("distribution", e.args[0])
 
         super().validateStep(data)
 
     def render(self):
         for bugtask in IBug(self.context).bugtasks:
-            if (IDistributionSourcePackage.providedBy(bugtask.target) and
-                (not self.widgets['sourcepackagename'].hasInput())):
+            if IDistributionSourcePackage.providedBy(bugtask.target) and (
+                not self.widgets["sourcepackagename"].hasInput()
+            ):
                 # Set the rendered value to the raw name string rather than
                 # the SPN object.  The widget won't have its distribution
                 # set up in order to be able to look up the SPN, and even if
                 # it did it might not correspond to a valid DSP.
-                self.widgets['sourcepackagename'].setRenderedValue(
-                    bugtask.sourcepackagename.name)
+                self.widgets["sourcepackagename"].setRenderedValue(
+                    bugtask.sourcepackagename.name
+                )
                 break
         return super().render()
 
@@ -484,43 +502,46 @@ class LinkUpstreamHowOptions(EnumeratedType):
         Enter the URL in the upstream bug tracker. If it's in a
         supported upstream bug tracker, Launchpad can download the
         status and display it in the bug report.
-        """)
-
-# XXX: GavinPanella 2008-02-13 bug=201793: This will be uncommented in
-# a later branch.
-#
-#     EMAIL_UPSTREAM = Item(
-#         """I would like to email an upstream bug contact.
-#
-#         Launchpad will prepare an example email containing all the
-#         pertinent details. You can send it from Launchpad or from your
-#         own mail software. If you send it from Launchpad, it'll save
-#         the message id and - in the future - will use it to try and
-#         follow the resulting conversation, provided it happens on a
-#         public mailing list.
-#         """)
+        """
+    )
+
+    # XXX: GavinPanella 2008-02-13 bug=201793: This will be uncommented in
+    # a later branch.
+    #
+    #     EMAIL_UPSTREAM = Item(
+    #         """I would like to email an upstream bug contact.
+    #
+    #         Launchpad will prepare an example email containing all the
+    #         pertinent details. You can send it from Launchpad or from your
+    #         own mail software. If you send it from Launchpad, it'll save
+    #         the message id and - in the future - will use it to try and
+    #         follow the resulting conversation, provided it happens on a
+    #         public mailing list.
+    #         """)
 
     EMAIL_UPSTREAM_DONE = Item(
         """I have already emailed an upstream bug contact:
 
         Launchpad will record that.
-        """)
+        """
+    )
 
-# XXX: GavinPanella 2008-02-13 bug=201793: This additional description
-# for EMAIL_UPSTREAM_DONE should be appended when EMAIL_UPSTREAM is
-# made available.
-#
-#   "Next time, try using Launchpad to send the message upstream
-#    too. That way it may be able to follow the conversation that
-#    results from your bug report. This is especially true for public
-#    mailing lists."
+    # XXX: GavinPanella 2008-02-13 bug=201793: This additional description
+    # for EMAIL_UPSTREAM_DONE should be appended when EMAIL_UPSTREAM is
+    # made available.
+    #
+    #   "Next time, try using Launchpad to send the message upstream
+    #    too. That way it may be able to follow the conversation that
+    #    results from your bug report. This is especially true for public
+    #    mailing lists."
 
     UNLINKED_UPSTREAM = Item(
         """I want to add this upstream project to the bug report, but someone\
         must find or report this bug in the upstream bug tracker.
 
         Launchpad will record that.
-        """)
+        """
+    )
 
 
 class IAddBugTaskWithUpstreamLinkForm(IAddBugTaskForm):
@@ -535,44 +556,60 @@ class IAddBugTaskWithUpstreamLinkForm(IAddBugTaskForm):
     check is left to the view, in part so that better error messages
     can be provided.
     """
+
     # link_upstream_how must have required=False, since
     # ProductBugTaskCreationStep doesn't always display a form input for it.
     link_upstream_how = Choice(
-        title=_('How'), required=False,
+        title=_("How"),
+        required=False,
         vocabulary=LinkUpstreamHowOptions,
         default=LinkUpstreamHowOptions.LINK_UPSTREAM,
-        description=_("How to link to an upstream bug."))
+        description=_("How to link to an upstream bug."),
+    )
     bug_url = StrippedTextLine(
-        title=_('Bug URL'), required=False, constraint=valid_remote_bug_url,
-        description=_("The URL of this bug in the remote bug tracker."))
+        title=_("Bug URL"),
+        required=False,
+        constraint=valid_remote_bug_url,
+        description=_("The URL of this bug in the remote bug tracker."),
+    )
     upstream_email_address_done = StrippedTextLine(
-        title=_('Email Address'), required=False, constraint=email_validator,
-        description=_("The upstream email address that this bug has been "
-                      "forwarded to."))
+        title=_("Email Address"),
+        required=False,
+        constraint=email_validator,
+        description=_(
+            "The upstream email address that this bug has been "
+            "forwarded to."
+        ),
+    )
 
 
 class ProductBugTaskCreationStep(BugTaskCreationStep):
     """Specialized BugTaskCreationStep for reporting a bug in an upstream."""
 
     template = ViewPageTemplateFile(
-        '../templates/bugtask-requestfix-upstream.pt')
+        "../templates/bugtask-requestfix-upstream.pt"
+    )
 
     label = "Confirm project"
-    target_field_names = ('product', 'add_packaging')
-    main_action_label = 'Add to Bug Report'
+    target_field_names = ("product", "add_packaging")
+    main_action_label = "Add to Bug Report"
     schema = IAddBugTaskWithUpstreamLinkForm
 
     custom_widget_link_upstream_how = CustomWidgetFactory(
-        LaunchpadRadioWidget, _displayItemForMissingValue=False)
+        LaunchpadRadioWidget, _displayItemForMissingValue=False
+    )
     custom_widget_bug_url = CustomWidgetFactory(
-        StrippedTextWidget, displayWidth=42)
+        StrippedTextWidget, displayWidth=42
+    )
     custom_widget_upstream_email_address_done = CustomWidgetFactory(
-        StrippedTextWidget, displayWidth=42)
+        StrippedTextWidget, displayWidth=42
+    )
 
     @property
     def field_names(self):
-        return ['link_upstream_how', 'upstream_email_address_done'] + (
-            super().field_names)
+        return ["link_upstream_how", "upstream_email_address_done"] + (
+            super().field_names
+        )
 
     def validate_widgets(self, data, names=None):
         # The form is essentially just a radio group, with zero or one
@@ -591,13 +628,14 @@ class ProductBugTaskCreationStep(BugTaskCreationStep):
 
         # A mapping from radio buttons to their related text widgets.
         link_upstream_options = {
-            LinkUpstreamHowOptions.LINK_UPSTREAM:
-                'bug_url',
-            LinkUpstreamHowOptions.EMAIL_UPSTREAM_DONE:
-                'upstream_email_address_done'}
+            LinkUpstreamHowOptions.LINK_UPSTREAM: "bug_url",
+            LinkUpstreamHowOptions.EMAIL_UPSTREAM_DONE: (
+                "upstream_email_address_done"
+            ),
+        }
 
         # Examine the radio group if it has valid input.
-        link_upstream_how = self.widgets['link_upstream_how']
+        link_upstream_how = self.widgets["link_upstream_how"]
         if link_upstream_how.hasValidInput():
             link_upstream_how = link_upstream_how.getInputValue()
 
@@ -611,8 +649,7 @@ class ProductBugTaskCreationStep(BugTaskCreationStep):
                     # fields in the schema are set to required=False
                     # to make the radio+text-widget mechanism work.
                     if not self.widgets[name].getInputValue():
-                        self.setFieldError(
-                            name, 'Required input is missing.')
+                        self.setFieldError(name, "Required input is missing.")
 
         else:
             # Don't validate these widgets when we don't yet know how
@@ -623,9 +660,9 @@ class ProductBugTaskCreationStep(BugTaskCreationStep):
 
     def getTarget(self, data=None):
         if data is not None:
-            return data.get('product')
+            return data.get("product")
         else:
-            return self.widgets['product'].getInputValue()
+            return self.widgets["product"].getInputValue()
 
     @cachedproperty
     def link_upstream_how_items(self):
@@ -636,7 +673,7 @@ class ProductBugTaskCreationStep(BugTaskCreationStep):
         widgets. We need to dig down a bit and place the individually
         rendered radio buttons into our custom layout.
         """
-        widget = self.widgets['link_upstream_how']
+        widget = self.widgets["link_upstream_how"]
         try:
             current_value = widget.getInputValue()
         except MissingInputError:
@@ -648,33 +685,37 @@ class ProductBugTaskCreationStep(BugTaskCreationStep):
         # link_upstream_how has _displayItemForMissingValue=False
         # so that renderItems() doesn't return an extra radio button which
         # prevents it from matching widget.vocabulary's ordering.
-        return {entry.token: items[i]
-                    for i, entry in enumerate(widget.vocabulary)}
+        return {
+            entry.token: items[i] for i, entry in enumerate(widget.vocabulary)
+        }
 
     def main_action(self, data):
-        link_upstream_how = data.get('link_upstream_how')
+        link_upstream_how = data.get("link_upstream_how")
 
         if link_upstream_how == LinkUpstreamHowOptions.UNLINKED_UPSTREAM:
             # Erase bug_url because we don't want to create a bug
             # watch against a specific URL.
-            if 'bug_url' in data:
-                del data['bug_url']
+            if "bug_url" in data:
+                del data["bug_url"]
         elif link_upstream_how == LinkUpstreamHowOptions.EMAIL_UPSTREAM_DONE:
             # Ensure there's a bug tracker for this email address.
-            bug_url = 'mailto:' + data['upstream_email_address_done']
+            bug_url = "mailto:"; + data["upstream_email_address_done"]
             getUtility(IBugTrackerSet).ensureBugTracker(
-                bug_url, self.user, BugTrackerType.EMAILADDRESS)
-            data['bug_url'] = bug_url
-        if data.get('add_packaging', False):
+                bug_url, self.user, BugTrackerType.EMAILADDRESS
+            )
+            data["bug_url"] = bug_url
+        if data.get("add_packaging", False):
             # Create a packaging link so that Launchpad will suggest the
             # upstream project to the user.
             series = self.context.target.distribution.currentseries
             if series:
                 getUtility(IPackagingUtil).createPackaging(
-                    productseries=data['product'].development_focus,
+                    productseries=data["product"].development_focus,
                     sourcepackagename=self.context.target.sourcepackagename,
-                    distroseries=series, packaging=PackagingType.PRIME,
-                    owner=self.user)
+                    distroseries=series,
+                    packaging=PackagingType.PRIME,
+                    owner=self.user,
+                )
         return super().main_action(data)
 
     @property
@@ -693,9 +734,12 @@ class ProductBugTaskCreationStep(BugTaskCreationStep):
         bug = self.context.bug
         title = bug.title
         description = "Originally reported at:\n  %s\n\n%s" % (
-            canonical_url(bug), bug.description)
+            canonical_url(bug),
+            bug.description,
+        )
         return target.bugtracker.getBugFilingAndSearchLinks(
-            target.remote_product, title, description)
+            target.remote_product, title, description
+        )
 
 
 class BugTrackerCreationStep(AlsoAffectsStep):
@@ -707,51 +751,60 @@ class BugTrackerCreationStep(AlsoAffectsStep):
     """
 
     custom_widget_bug_url = CustomWidgetFactory(
-        StrippedTextWidget, displayWidth=62)
+        StrippedTextWidget, displayWidth=62
+    )
     step_name = "bugtracker_creation"
-    main_action_label = 'Register Bug Tracker and Add to Bug Report'
+    main_action_label = "Register Bug Tracker and Add to Bug Report"
     _next_step = None
 
     def main_action(self, data):
-        assert self._next_step is not None, (
-            "_next_step must be specified in subclasses.")
-        bug_url = data.get('bug_url').strip()
+        assert (
+            self._next_step is not None
+        ), "_next_step must be specified in subclasses."
+        bug_url = data.get("bug_url").strip()
         try:
             getUtility(IBugWatchSet).extractBugTrackerAndBug(bug_url)
         except NoBugTrackerFound as error:
             getUtility(IBugTrackerSet).ensureBugTracker(
-                error.base_url, self.user, error.bugtracker_type)
+                error.base_url, self.user, error.bugtracker_type
+            )
         self.next_step = self._next_step
 
 
 class DistroBugTrackerCreationStep(BugTrackerCreationStep):
 
     _next_step = DistroBugTaskCreationStep
-    _field_names = ['distribution', 'sourcepackagename', 'bug_url']
+    _field_names = ["distribution", "sourcepackagename", "bug_url"]
     custom_widget_distribution = CustomWidgetFactory(
-        DropdownWidget, visible=False)
+        DropdownWidget, visible=False
+    )
     custom_widget_sourcepackagename = CustomWidgetFactory(
-        DropdownWidget, visible=False)
+        DropdownWidget, visible=False
+    )
     label = "Also affects distribution/package"
     template = ViewPageTemplateFile(
-        '../templates/bugtask-confirm-bugtracker-creation.pt')
+        "../templates/bugtask-confirm-bugtracker-creation.pt"
+    )
 
 
 class UpstreamBugTrackerCreationStep(BugTrackerCreationStep):
 
     schema = IAddBugTaskWithUpstreamLinkForm
     _next_step = ProductBugTaskCreationStep
-    _field_names = ['product', 'bug_url', 'link_upstream_how']
+    _field_names = ["product", "bug_url", "link_upstream_how"]
     custom_widget_product = CustomWidgetFactory(DropdownWidget, visible=False)
     custom_widget_link_upstream_how = CustomWidgetFactory(
-        LaunchpadRadioWidget, visible=False)
+        LaunchpadRadioWidget, visible=False
+    )
     label = "Confirm project"
     template = ViewPageTemplateFile(
-        '../templates/bugtask-confirm-bugtracker-creation.pt')
+        "../templates/bugtask-confirm-bugtracker-creation.pt"
+    )
 
 
-class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
-                                                   LaunchpadFormView):
+class BugAlsoAffectsProductWithProductCreationView(
+    LinkPackgingMixin, LaunchpadFormView
+):
     """Register a product and indicate this bug affects it.
 
     If there's no bugtracker with the given URL registered in Launchpad, then
@@ -761,7 +814,8 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
     label = "Register project affected by this bug"
     schema = IAddBugTaskWithProductCreationForm
     custom_widget_bug_url = CustomWidgetFactory(
-        StrippedTextWidget, displayWidth=62)
+        StrippedTextWidget, displayWidth=62
+    )
     custom_widget_existing_product = LaunchpadRadioWidget
     existing_products = None
     MAX_PRODUCTS_TO_DISPLAY = 10
@@ -770,9 +824,9 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
     @property
     def field_names(self):
         """The fields needed to choose an existing project."""
-        names = ['bug_url', 'display_name', 'name', 'summary']
+        names = ["bug_url", "display_name", "name", "summary"]
         if self.can_link_package:
-            names.append('add_packaging')
+            names.append("add_packaging")
         return names
 
     def _loadProductsUsingBugTracker(self):
@@ -783,7 +837,7 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
         If there are too many products using that bugtracker then we'll store
         only the first ones that somehow match the name given.
         """
-        bug_url = self.request.form.get('field.bug_url')
+        bug_url = self.request.form.get("field.bug_url")
         if not bug_url:
             return
 
@@ -802,12 +856,16 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
             # Use a local import as we don't want removeSecurityProxy used
             # anywhere else.
             from zope.security.proxy import removeSecurityProxy
+
             name_matches = removeSecurityProxy(
-                getUtility(IProductSet).search(self.user,
-                self.request.form.get('field.name')))
+                getUtility(IProductSet).search(
+                    self.user, self.request.form.get("field.name")
+                )
+            )
             products = name_matches.find(Product.bugtracker == bugtracker.id)
             self.existing_products = list(
-                products[:self.MAX_PRODUCTS_TO_DISPLAY])
+                products[: self.MAX_PRODUCTS_TO_DISPLAY]
+            )
         else:
             # The bugtracker is registered in Launchpad but there are no
             # products using it at the moment.
@@ -829,37 +887,44 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
         for product in self.existing_products:
             terms.append(SimpleTerm(product, product.name, product.title))
         existing_product = form.FormField(
-            Choice(__name__='existing_product',
-                   title=_("Existing project"), required=True,
-                   vocabulary=SimpleVocabulary(terms)))
+            Choice(
+                __name__="existing_product",
+                title=_("Existing project"),
+                required=True,
+                vocabulary=SimpleVocabulary(terms),
+            )
+        )
         self.form_fields += form.Fields(existing_product)
-        if 'field.existing_product' not in self.request.form:
+        if "field.existing_product" not in self.request.form:
             # This is the first time the form is being submitted, so the
             # request doesn't contain a value for the existing_product
             # widget and thus we'll end up rendering an error message around
             # said widget unless we sneak a value for it in our request.
-            self.request.form['field.existing_product'] = terms[0].token
+            self.request.form["field.existing_product"] = terms[0].token
 
     def validate_existing_product(self, action, data):
         """Check if the chosen project is not already affected by this bug."""
         self._validate(action, data)
-        project = data.get('existing_product')
+        project = data.get("existing_product")
         try:
             validate_target(self.context.bug, project)
         except IllegalTarget as e:
-            self.setFieldError('existing_product', e.args[0])
+            self.setFieldError("existing_product", e.args[0])
 
-    @action('Use Existing Project', name='use_existing_product',
-            validator=validate_existing_product)
+    @action(
+        "Use Existing Project",
+        name="use_existing_product",
+        validator=validate_existing_product,
+    )
     def use_existing_product_action(self, action, data):
         """Record the chosen project as being affected by this bug.
 
         Also creates a bugwatch for the given remote bug.
         """
-        data['product'] = data['existing_product']
+        data["product"] = data["existing_product"]
         self._createBugTaskAndWatch(data)
 
-    @action('Continue', name='continue')
+    @action("Continue", name="continue")
     def continue_action(self, action, data):
         """Create a new product and a bugtask for this bug on that product.
 
@@ -867,7 +932,7 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
         other products registered in Launchpad, then we show these products to
         the user and ask if they don't want to create the task in one of them.
         """
-        if self.existing_products and not self.request.form.get('create_new'):
+        if self.existing_products and not self.request.form.get("create_new"):
             # Present the projects using that bugtracker to the user as
             # possible options to report the bug on. If there are too many
             # projects using that bugtracker then show only the ones that
@@ -876,11 +941,14 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
         # Products created through this view have DONT_KNOW licensing.
         product = getUtility(IProductSet).createProduct(
             owner=self.user,
-            name=data['name'],
-            display_name=data['display_name'], title=data['display_name'],
-            summary=data['summary'], licenses=self.licenses,
-            registrant=self.user)
-        data['product'] = product
+            name=data["name"],
+            display_name=data["display_name"],
+            title=data["display_name"],
+            summary=data["summary"],
+            licenses=self.licenses,
+            registrant=self.user,
+        )
+        data["product"] = product
         self._createBugTaskAndWatch(data, set_bugtracker=True)
         # Now that the product is configured set the owner to be the registry
         # experts team.
@@ -910,5 +978,5 @@ class BugAlsoAffectsProductWithProductCreationView(LinkPackgingMixin,
         view.main_action(data)
 
         if set_bugtracker:
-            data['product'].bugtracker = view.task_added.bugwatch.bugtracker
+            data["product"].bugtracker = view.task_added.bugwatch.bugtracker
         self.next_url = canonical_url(view.task_added)
diff --git a/lib/lp/bugs/browser/bugattachment.py b/lib/lp/bugs/browser/bugattachment.py
index 324c4e7..7d45e45 100644
--- a/lib/lp/bugs/browser/bugattachment.py
+++ b/lib/lp/bugs/browser/bugattachment.py
@@ -4,25 +4,19 @@
 """Bug attachment views."""
 
 __all__ = [
-    'BugAttachmentContentCheck',
-    'BugAttachmentFileNavigation',
-    'BugAttachmentSetNavigation',
-    'BugAttachmentEditView',
-    'BugAttachmentURL',
-    ]
+    "BugAttachmentContentCheck",
+    "BugAttachmentFileNavigation",
+    "BugAttachmentSetNavigation",
+    "BugAttachmentEditView",
+    "BugAttachmentURL",
+]
 
 from lazr.restful.utils import smartquote
-from zope.component import (
-    getMultiAdapter,
-    getUtility,
-    )
+from zope.component import getMultiAdapter, getUtility
 from zope.contenttype import guess_content_type
 from zope.interface import implementer
 
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
 from lp.app.widgets.itemswidgets import LaunchpadBooleanRadioWidget
 from lp.bugs.interfaces.bugattachment import (
     BugAttachmentType,
@@ -30,58 +24,52 @@ from lp.bugs.interfaces.bugattachment import (
     IBugAttachmentEditForm,
     IBugAttachmentIsPatchConfirmationForm,
     IBugAttachmentSet,
-    )
+)
 from lp.services.librarian.browser import (
     FileNavigationMixin,
     ProxiedLibraryFileAlias,
-    )
+)
 from lp.services.librarian.interfaces import ILibraryFileAliasWithParent
-from lp.services.webapp import (
-    canonical_url,
-    GetitemNavigation,
-    Navigation,
-    )
+from lp.services.webapp import GetitemNavigation, Navigation, canonical_url
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.escaping import structured
-from lp.services.webapp.interfaces import (
-    ICanonicalUrlData,
-    ILaunchBag,
-    )
+from lp.services.webapp.interfaces import ICanonicalUrlData, ILaunchBag
 
 
 class BugAttachmentContentCheck:
-    """A mixin class that checks the consistency of patch flag and file type.
-    """
+    """Mixin that checks the consistency of patch flag and file type."""
 
     def guessContentType(self, filename, file_content):
         """Guess the content type a file with the given name and content."""
         guessed_type, encoding = guess_content_type(
-            name=filename, body=file_content)
+            name=filename, body=file_content
+        )
         # Zope's guess_content_type() doesn't consider all the factors
         # we want considered.  So after we get its answer, we probe a
         # little further.  But we still don't look at the encoding nor
         # the file content, because we'd like to avoid reimplementing
         # 'patch'.  See bug #538219 for more.
-        if (guessed_type == 'text/plain'
-            and (filename.endswith('.diff')
-                 or filename.endswith('.debdiff')
-                 or filename.endswith('.patch'))):
-            guessed_type = 'text/x-diff'
+        if guessed_type == "text/plain" and (
+            filename.endswith(".diff")
+            or filename.endswith(".debdiff")
+            or filename.endswith(".patch")
+        ):
+            guessed_type = "text/x-diff"
         return guessed_type
 
     def attachmentTypeConsistentWithContentType(
-        self, patch_flag_set, filename, file_content):
-        """Return True iff patch_flag is consistent with filename and content.
-        """
+        self, patch_flag_set, filename, file_content
+    ):
+        """True iff patch_flag is consistent with filename and content."""
         guessed_type = self.guessContentType(filename, file_content)
         # An XOR of "is the patch flag selected?" with "is the
         # guessed type not a diff?" tells us if the type selected
         # by the user matches the guessed type.
-        return (patch_flag_set ^ (guessed_type != 'text/x-diff'))
+        return patch_flag_set ^ (guessed_type != "text/x-diff")
 
     def nextUrlForInconsistentPatchFlags(self, attachment):
         """The next_url value used for an inconistent patch flag."""
-        return canonical_url(attachment) + '/+confirm-is-patch'
+        return canonical_url(attachment) + "/+confirm-is-patch"
 
 
 class BugAttachmentSetNavigation(GetitemNavigation):
@@ -96,7 +84,7 @@ class BugAttachmentSetNavigation(GetitemNavigation):
 class BugAttachmentURL:
     """Bug URL creation rules."""
 
-    rootsite = 'bugs'
+    rootsite = "bugs"
 
     def __init__(self, context):
         self.context = context
@@ -120,12 +108,13 @@ class BugAttachmentEditView(LaunchpadFormView, BugAttachmentContentCheck):
     """Edit a bug attachment."""
 
     schema = IBugAttachmentEditForm
-    field_names = ['title', 'patch', 'contenttype']
+    field_names = ["title", "patch", "contenttype"]
 
     def __init__(self, context, request):
         LaunchpadFormView.__init__(self, context, request)
-        self.next_url = self.cancel_url = (
-            canonical_url(ICanonicalUrlData(context).inside))
+        self.next_url = self.cancel_url = canonical_url(
+            ICanonicalUrlData(context).inside
+        )
 
     @property
     def initial_values(self):
@@ -133,14 +122,15 @@ class BugAttachmentEditView(LaunchpadFormView, BugAttachmentContentCheck):
         return dict(
             title=attachment.title,
             patch=attachment.type == BugAttachmentType.PATCH,
-            contenttype=attachment.libraryfile.mimetype)
+            contenttype=attachment.libraryfile.mimetype,
+        )
 
     def canEditAttachment(self, action):
-        return check_permission('launchpad.Edit', self.context)
+        return check_permission("launchpad.Edit", self.context)
 
-    @action('Change', name='change', condition=canEditAttachment)
+    @action("Change", name="change", condition=canEditAttachment)
     def change_action(self, action, data):
-        if data['patch']:
+        if data["patch"]:
             new_type = BugAttachmentType.PATCH
         else:
             new_type = BugAttachmentType.UNSPECIFIED
@@ -155,37 +145,46 @@ class BugAttachmentEditView(LaunchpadFormView, BugAttachmentContentCheck):
             # choice of the patch flag.
             new_type_consistent_with_guessed_type = (
                 self.attachmentTypeConsistentWithContentType(
-                    new_type == BugAttachmentType.PATCH, filename,
-                    file_content))
+                    new_type == BugAttachmentType.PATCH, filename, file_content
+                )
+            )
             if new_type_consistent_with_guessed_type:
                 self.context.type = new_type
             else:
                 self.next_url = self.nextUrlForInconsistentPatchFlags(
-                    self.context)
+                    self.context
+                )
 
-        if data['title'] != self.context.title:
-            self.context.title = data['title']
+        if data["title"] != self.context.title:
+            self.context.title = data["title"]
 
-        if self.context.libraryfile.mimetype != data['contenttype']:
+        if self.context.libraryfile.mimetype != data["contenttype"]:
             lfa_with_parent = getMultiAdapter(
                 (self.context.libraryfile, self.context),
-                ILibraryFileAliasWithParent)
-            lfa_with_parent.mimetype = data['contenttype']
+                ILibraryFileAliasWithParent,
+            )
+            lfa_with_parent.mimetype = data["contenttype"]
 
-    @action('Delete Attachment', name='delete', condition=canEditAttachment)
+    @action("Delete Attachment", name="delete", condition=canEditAttachment)
     def delete_action(self, action, data):
         libraryfile_url = ProxiedLibraryFileAlias(
-            self.context.libraryfile, self.context).http_url
-        self.request.response.addInfoNotification(structured(
-            'Attachment "<a href="%(url)s">%(name)s</a>" has been deleted.',
-            url=libraryfile_url, name=self.context.title))
+            self.context.libraryfile, self.context
+        ).http_url
+        self.request.response.addInfoNotification(
+            structured(
+                'Attachment "<a href="%(url)s">%(name)s</a>" has been '
+                "deleted.",
+                url=libraryfile_url,
+                name=self.context.title,
+            )
+        )
         self.context.removeFromBug(user=self.user)
 
     @property
     def label(self):
         return smartquote('Edit attachment "%s"') % self.context.title
 
-    page_title = 'Edit attachment'
+    page_title = "Edit attachment"
 
 
 class BugAttachmentPatchConfirmationView(LaunchpadFormView):
@@ -203,27 +202,29 @@ class BugAttachmentPatchConfirmationView(LaunchpadFormView):
 
     def __init__(self, context, request):
         LaunchpadFormView.__init__(self, context, request)
-        self.next_url = self.cancel_url = (
-            canonical_url(ICanonicalUrlData(context).inside))
+        self.next_url = self.cancel_url = canonical_url(
+            ICanonicalUrlData(context).inside
+        )
 
     def initialize(self):
         super().initialize()
-        self.widgets['patch'].setRenderedValue(self.is_patch)
+        self.widgets["patch"].setRenderedValue(self.is_patch)
 
     @property
     def label(self):
         return smartquote('Confirm attachment type of "%s"') % (
-            self.context.title)
+            self.context.title
+        )
 
-    page_title = 'Confirm attachment type'
+    page_title = "Confirm attachment type"
 
-    @action('Change', name='change')
+    @action("Change", name="change")
     def change_action(self, action, data):
         current_patch_setting = self.context.type == BugAttachmentType.PATCH
-        if data['patch'] != current_patch_setting:
-            if data['patch']:
+        if data["patch"] != current_patch_setting:
+            if data["patch"]:
                 self.context.type = BugAttachmentType.PATCH
-                #xxxxxxxxxx adjust content type!
+                # xxxxxxxxxx adjust content type!
                 # xxx use mixin, together with BugAttachmnetEditView
             else:
                 self.context.type = BugAttachmentType.UNSPECIFIED
diff --git a/lib/lp/bugs/browser/bugbranch.py b/lib/lp/bugs/browser/bugbranch.py
index e7ea7ab..a2a650d 100644
--- a/lib/lp/bugs/browser/bugbranch.py
+++ b/lib/lp/bugs/browser/bugbranch.py
@@ -4,58 +4,49 @@
 """Browser view classes for BugBranch-related objects."""
 
 __all__ = [
-    'BranchLinkToBugView',
-    'BugBranchAddView',
-    'BugBranchDeleteView',
-    'BugBranchView',
-    ]
+    "BranchLinkToBugView",
+    "BugBranchAddView",
+    "BugBranchDeleteView",
+    "BugBranchView",
+]
 
 from lazr.restful.interfaces import IWebServiceClientRequest
-from zope.component import (
-    adapter,
-    getMultiAdapter,
-    )
-from zope.interface import (
-    implementer,
-    Interface,
-    )
+from zope.component import adapter, getMultiAdapter
+from zope.interface import Interface, implementer
 
 from lp import _
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
-    )
+    action,
+)
 from lp.bugs.interfaces.bugbranch import IBugBranch
 from lp.code.browser.branchmergeproposal import (
     latest_proposals_for_each_branch,
-    )
+)
 from lp.code.enums import BranchLifecycleStatus
 from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
-    canonical_url,
-    LaunchpadView,
-    )
+from lp.services.webapp import LaunchpadView, canonical_url
 
 
 class BugBranchAddView(LaunchpadFormView):
     """Browser view for linking a bug to a branch."""
+
     schema = IBugBranch
     # In order to have the branch field rendered using the appropriate
     # widget, we set the LaunchpadFormView attribute for_input to True
     # to get the read only fields rendered as input widgets.
     for_input = True
-    page_title = 'Add branch'
-    field_names = ['branch']
+    page_title = "Add branch"
+    field_names = ["branch"]
 
-    @action(_('Continue'), name='continue')
+    @action(_("Continue"), name="continue")
     def continue_action(self, action, data):
-        branch = data['branch']
-        self.context.bug.linkBranch(
-            branch=branch, registrant=self.user)
+        branch = data["branch"]
+        self.context.bug.linkBranch(branch=branch, registrant=self.user)
         self.request.response.addNotification(
-            "Successfully registered branch %s for this bug." %
-            branch.name)
+            "Successfully registered branch %s for this bug." % branch.name
+        )
 
     @property
     def next_url(self):
@@ -63,13 +54,14 @@ class BugBranchAddView(LaunchpadFormView):
 
     @property
     def label(self):
-        return 'Add a branch to bug #%i' % self.context.bug.id
+        return "Add a branch to bug #%i" % self.context.bug.id
 
     cancel_url = next_url
 
 
 class BugBranchDeleteView(LaunchpadEditFormView):
     """View to update a BugBranch."""
+
     schema = IBugBranch
 
     field_names = []
@@ -83,11 +75,11 @@ class BugBranchDeleteView(LaunchpadEditFormView):
 
     cancel_url = next_url
 
-    @action('Remove link', name='delete')
+    @action("Remove link", name="delete")
     def delete_action(self, action, data):
         self.context.bug.unlinkBranch(self.context.branch, self.user)
 
-    label = 'Remove bug branch link'
+    label = "Remove bug branch link"
 
 
 class BugBranchView(LaunchpadView):
@@ -103,19 +95,22 @@ class BugBranchView(LaunchpadView):
     def show_branch_status(self):
         """Show the branch status if merged and there are no proposals."""
         lifecycle_status = self.context.branch.lifecycle_status
-        return (len(self.merge_proposals) == 0 and
-                lifecycle_status == BranchLifecycleStatus.MERGED)
+        return (
+            len(self.merge_proposals) == 0
+            and lifecycle_status == BranchLifecycleStatus.MERGED
+        )
 
 
 class BranchLinkToBugView(LaunchpadFormView):
     """The view to create bug-branch links."""
+
     schema = IBugBranch
     # In order to have the bug field rendered using the appropriate
     # widget, we set the LaunchpadFormView attribute for_input to True
     # to get the read only fields rendered as input widgets.
     for_input = True
 
-    field_names = ['bug']
+    field_names = ["bug"]
 
     @property
     def label(self):
@@ -123,7 +118,7 @@ class BranchLinkToBugView(LaunchpadFormView):
 
     @property
     def page_title(self):
-        return 'Link branch %s to a bug report' % self.context.displayname
+        return "Link branch %s to a bug report" % self.context.displayname
 
     @property
     def next_url(self):
@@ -131,9 +126,9 @@ class BranchLinkToBugView(LaunchpadFormView):
 
     cancel_url = next_url
 
-    @action(_('Continue'), name='continue')
+    @action(_("Continue"), name="continue")
     def continue_action(self, action, data):
-        bug = data['bug']
+        bug = data["bug"]
         bug.linkBranch(branch=self.context, registrant=self.user)
 
 
@@ -147,5 +142,6 @@ class BugBranchXHTMLRepresentation:
     def __call__(self):
         """Render `BugBranch` as XHTML using the webservice."""
         branch_view = getMultiAdapter(
-            (self.branch, self.request), name="+bug-branch")
+            (self.branch, self.request), name="+bug-branch"
+        )
         return branch_view()
diff --git a/lib/lp/bugs/browser/bugcomment.py b/lib/lp/bugs/browser/bugcomment.py
index 5d2d087..b8650ca 100644
--- a/lib/lp/bugs/browser/bugcomment.py
+++ b/lib/lp/bugs/browser/bugcomment.py
@@ -4,34 +4,24 @@
 """Bug comment browser view classes."""
 
 __all__ = [
-    'BugComment',
-    'BugCommentBoxExpandedReplyView',
-    'BugCommentBoxView',
-    'BugCommentBreadcrumb',
-    'BugCommentView',
-    'BugCommentXHTMLRepresentation',
-    'build_comments_from_chunks',
-    'group_comments_with_activity',
-    ]
+    "BugComment",
+    "BugCommentBoxExpandedReplyView",
+    "BugCommentBoxView",
+    "BugCommentBreadcrumb",
+    "BugCommentView",
+    "BugCommentXHTMLRepresentation",
+    "build_comments_from_chunks",
+    "group_comments_with_activity",
+]
 
 from datetime import timedelta
-from itertools import (
-    chain,
-    groupby,
-    )
+from itertools import chain, groupby
 from operator import itemgetter
 
 from lazr.delegates import delegate_to
 from lazr.restful.interfaces import IWebServiceClientRequest
-from zope.component import (
-    adapter,
-    getMultiAdapter,
-    getUtility,
-    )
-from zope.interface import (
-    implementer,
-    Interface,
-    )
+from zope.component import adapter, getMultiAdapter, getUtility
+from zope.interface import Interface, implementer
 from zope.security.proxy import removeSecurityProxy
 
 from lp.bugs.interfaces.bugattachment import BugAttachmentType
@@ -41,29 +31,26 @@ from lp.services.comments.browser.messagecomment import MessageComment
 from lp.services.config import config
 from lp.services.librarian.browser import ProxiedLibraryFileAlias
 from lp.services.messages.interfaces.message import IMessage
-from lp.services.propertycache import (
-    cachedproperty,
-    get_property_cache,
-    )
+from lp.services.propertycache import cachedproperty, get_property_cache
 from lp.services.webapp import (
-    canonical_url,
     LaunchpadView,
     Navigation,
+    canonical_url,
     stepthrough,
-    )
+)
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.interfaces import ILaunchBag
 
-
 COMMENT_ACTIVITY_GROUPING_WINDOW = timedelta(minutes=5)
 
 
 class BugCommentNavigation(Navigation):
     """Navigation for the `IBugComment`."""
+
     usedfor = IBugComment
 
-    @stepthrough('revisions')
+    @stepthrough("revisions")
     def traverse_revisions(self, revision):
         try:
             revision = int(revision)
@@ -73,8 +60,13 @@ class BugCommentNavigation(Navigation):
 
 
 def build_comments_from_chunks(
-        bugtask, truncate=False, slice_info=None, show_spam_controls=False,
-        user=None, hide_first=False):
+    bugtask,
+    truncate=False,
+    slice_info=None,
+    show_spam_controls=False,
+    user=None,
+    hide_first=False,
+):
     """Build BugComments from MessageChunks.
 
     :param truncate: Perform truncation of large messages.
@@ -85,7 +77,7 @@ def build_comments_from_chunks(
     comments = {}
     for bugmessage, message, chunk in chunks:
         cache = get_property_cache(message)
-        if getattr(cache, 'chunks', None) is None:
+        if getattr(cache, "chunks", None) is None:
             cache.chunks = []
         # Soft-deleted messages will have None chunk here. Skip cache
         # filling in this case.
@@ -94,15 +86,19 @@ def build_comments_from_chunks(
         bug_comment = comments.get(message.id)
         if bug_comment is None:
             if bugmessage.index == 0 and hide_first:
-                display = 'hide'
+                display = "hide"
             elif truncate:
-                display = 'truncate'
+                display = "truncate"
             else:
-                display = 'full'
+                display = "full"
             bug_comment = BugComment(
-                bugmessage.index, message, bugtask,
-                show_spam_controls=show_spam_controls, user=user,
-                display=display)
+                bugmessage.index,
+                message,
+                bugtask,
+                show_spam_controls=show_spam_controls,
+                user=user,
+                display=display,
+            )
             comments[message.id] = bug_comment
             # This code path is currently only used from a BugTask view which
             # has already loaded all the bug watches. If we start lazy loading
@@ -111,7 +107,8 @@ def build_comments_from_chunks(
             if bugmessage.bugwatch_id is not None:
                 bug_comment.bugwatch = bugmessage.bugwatch
                 bug_comment.synchronized = (
-                    bugmessage.remote_comment_id is not None)
+                    bugmessage.remote_comment_id is not None
+                )
     return comments
 
 
@@ -133,14 +130,26 @@ def group_comments_with_activity(comments, activities):
     else:
         max_index = 0
     comments = (
-        (comment.datecreated, comment.index,
-            comment.owner, comment_kind, comment)
-        for comment in comments)
+        (
+            comment.datecreated,
+            comment.index,
+            comment.owner,
+            comment_kind,
+            comment,
+        )
+        for comment in comments
+    )
     activity_kind = "activity"
     activity = (
-        (activity.datechanged, max_index,
-            activity.person, activity_kind, activity)
-        for activity in activities)
+        (
+            activity.datechanged,
+            max_index,
+            activity.person,
+            activity_kind,
+            activity,
+        )
+        for activity in activities
+    )
 
     # when an action and a comment happen at the same time, the action comes
     # second, when two events are tied the comment index is used to
@@ -166,11 +175,14 @@ def group_comments_with_activity(comments, activities):
         for date, _, actor, kind, event in events:
             window_ended = (
                 # A window may contain only one comment.
-                (window_comment is not None and kind is comment_kind) or
+                (window_comment is not None and kind is comment_kind)
+                or
                 # All events must have happened within a given timeframe.
-                (window_end is None or date >= window_end) or
+                (window_end is None or date >= window_end)
+                or
                 # All events within the window must belong to the same actor.
-                (window_actor is None or actor != window_actor))
+                (window_actor is None or actor != window_actor)
+            )
             if window_ended:
                 window_comment, window_actor = None, actor
                 window_index, window_end = window_index + 1, date + window
@@ -181,14 +193,15 @@ def group_comments_with_activity(comments, activities):
     event_windows = gen_event_windows(events)
     event_windows_grouper = groupby(event_windows, itemgetter(0))
     for window_index, window_group in event_windows_grouper:
-        window_group = [
-            (kind, event) for (index, kind, event) in window_group]
+        window_group = [(kind, event) for (index, kind, event) in window_group]
         for kind, event in window_group:
             if kind is comment_kind:
                 window_comment = event
                 window_comment.activity.extend(
-                    event for (kind, event) in window_group
-                    if kind is activity_kind)
+                    event
+                    for (kind, event) in window_group
+                    if kind is activity_kind
+                )
                 yield window_comment
                 # There's only one comment per window.
                 break
@@ -197,7 +210,7 @@ def group_comments_with_activity(comments, activities):
 
 
 @implementer(IBugComment)
-@delegate_to(IMessage, context='_message')
+@delegate_to(IMessage, context="_message")
 class BugComment(MessageComment):
     """Data structure that holds all data pertaining to a bug comment.
 
@@ -211,9 +224,16 @@ class BugComment(MessageComment):
     """
 
     def __init__(
-            self, index, message, bugtask, activity=None,
-            show_spam_controls=False, user=None, display='full'):
-        if display == 'truncate':
+        self,
+        index,
+        message,
+        bugtask,
+        activity=None,
+        show_spam_controls=False,
+        user=None,
+        display="full",
+    ):
+        if display == "truncate":
             comment_limit = config.malone.max_comment_size
         else:
             comment_limit = None
@@ -237,12 +257,15 @@ class BugComment(MessageComment):
         # We use a feature flag to control users deleting their own comments.
         user_owns_comment = user is not None and user == self.owner
         self.show_spam_controls = show_spam_controls or user_owns_comment
-        self.hide_text = (display == 'hide')
+        self.hide_text = display == "hide"
 
     @cachedproperty
     def bugattachments(self):
-        return [attachment for attachment in self._message.bugattachments if
-         attachment.type != BugAttachmentType.PATCH]
+        return [
+            attachment
+            for attachment in self._message.bugattachments
+            if attachment.type != BugAttachmentType.PATCH
+        ]
 
     @property
     def show_for_admin(self):
@@ -259,7 +282,7 @@ class BugComment(MessageComment):
     @cachedproperty
     def text_for_display(self):
         if self.hide_text:
-            return ''
+            return ""
         else:
             return super().text_for_display
 
@@ -273,8 +296,12 @@ class BugComment(MessageComment):
             return False
         if self.title != other.title:
             return False
-        if (self.bugattachments or self.patches or other.bugattachments or
-            other.patches):
+        if (
+            self.bugattachments
+            or self.patches
+            or other.bugattachments
+            or other.patches
+        ):
             # We shouldn't collapse comments which have attachments;
             # there's really no possible identity in that case.
             return False
@@ -283,23 +310,24 @@ class BugComment(MessageComment):
     def isEmpty(self):
         """Return True if text_for_display is empty."""
 
-        return (len(self.text_for_display) == 0 and
-            len(self.bugattachments) == 0 and len(self.patches) == 0)
+        return (
+            len(self.text_for_display) == 0
+            and len(self.bugattachments) == 0
+            and len(self.patches) == 0
+        )
 
     @property
     def add_comment_url(self):
-        return canonical_url(self.bugtask, view_name='+addcomment')
+        return canonical_url(self.bugtask, view_name="+addcomment")
 
     @property
     def download_url(self):
-        return canonical_url(self, view_name='+download')
+        return canonical_url(self, view_name="+download")
 
     @property
     def show_activity(self):
         """Return True if the activity should be shown for this comment."""
-        return bool(
-            len(self.activity) > 0 or
-            self.bugwatch)
+        return bool(len(self.activity) > 0 or self.bugwatch)
 
 
 class BugCommentView(LaunchpadView):
@@ -326,8 +354,10 @@ class BugCommentView(LaunchpadView):
         return self.comment.show_spam_controls
 
     def page_title(self):
-        return 'Comment %d for bug %d' % (
-            self.comment.index, self.context.bug.id)
+        return "Comment %d for bug %d" % (
+            self.comment.index,
+            self.context.bug.id,
+        )
 
     @property
     def page_description(self):
@@ -339,10 +369,11 @@ class BugCommentBoxViewMixin:
 
     @property
     def show_spam_controls(self):
-        if hasattr(self.context, 'show_spam_controls'):
+        if hasattr(self.context, "show_spam_controls"):
             return self.context.show_spam_controls
-        elif (hasattr(self, 'comment') and
-            hasattr(self.comment, 'show_spam_controls')):
+        elif hasattr(self, "comment") and hasattr(
+            self.comment, "show_spam_controls"
+        ):
             return self.comment.show_spam_controls
         else:
             return False
@@ -350,7 +381,8 @@ class BugCommentBoxViewMixin:
     def proxiedUrlOfLibraryFileAlias(self, attachment):
         """Return the proxied URL for the Librarian file of the attachment."""
         return ProxiedLibraryFileAlias(
-            attachment.libraryfile, attachment).http_url
+            attachment.libraryfile, attachment
+        ).http_url
 
 
 class BugCommentBoxView(LaunchpadView, BugCommentBoxViewMixin):
@@ -360,7 +392,7 @@ class BugCommentBoxView(LaunchpadView, BugCommentBoxViewMixin):
 
     @property
     def can_edit(self):
-        return check_permission('launchpad.Edit', self.context)
+        return check_permission("launchpad.Edit", self.context)
 
 
 class BugCommentBoxExpandedReplyView(LaunchpadView, BugCommentBoxViewMixin):
@@ -383,7 +415,8 @@ class BugCommentXHTMLRepresentation:
     def __call__(self):
         """Render `BugComment` as XHTML using the webservice."""
         comment_view = getMultiAdapter(
-            (self.comment, self.request), name="+box")
+            (self.comment, self.request), name="+box"
+        )
         return comment_view()
 
 
diff --git a/lib/lp/bugs/browser/buglinktarget.py b/lib/lp/bugs/browser/buglinktarget.py
index f3e2655..9dda500 100644
--- a/lib/lp/bugs/browser/buglinktarget.py
+++ b/lib/lp/bugs/browser/buglinktarget.py
@@ -4,10 +4,10 @@
 """Views for IBugLinkTarget."""
 
 __all__ = [
-    'BugLinkView',
-    'BugLinksListingView',
-    'BugsUnlinkView',
-    ]
+    "BugLinkView",
+    "BugLinksListingView",
+    "BugsUnlinkView",
+]
 
 from collections import defaultdict
 
@@ -19,23 +19,14 @@ from zope.interface import providedBy
 from zope.security.interfaces import Unauthorized
 
 from lp import _
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
 from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget
 from lp.bugs.browser.buglisting import BugListingBatchNavigator
-from lp.bugs.interfaces.buglink import (
-    IBugLinkForm,
-    IUnlinkBugsForm,
-    )
+from lp.bugs.interfaces.buglink import IBugLinkForm, IUnlinkBugsForm
 from lp.bugs.interfaces.bugtask import IBugTaskSet
 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
 from lp.services.config import config
-from lp.services.propertycache import (
-    cachedproperty,
-    get_property_cache,
-    )
+from lp.services.propertycache import cachedproperty, get_property_cache
 from lp.services.searchbuilder import any
 from lp.services.webapp import canonical_url
 from lp.services.webapp.authorization import check_permission
@@ -45,41 +36,46 @@ from lp.services.webapp.publisher import LaunchpadView
 class BugLinkView(LaunchpadFormView):
     """This view is used to link bugs to any IBugLinkTarget."""
 
-    label = _('Link a bug report')
+    label = _("Link a bug report")
     schema = IBugLinkForm
     page_title = label
 
-    focused_element_id = 'bug'
+    focused_element_id = "bug"
 
     @property
     def cancel_url(self):
         """See `LaunchpadFormview`."""
         return canonical_url(self.context)
 
-    @action(_('Link'))
+    @action(_("Link"))
     def linkBug(self, action, data):
         """Link to the requested bug. Publish an ObjectModifiedEvent and
         display a notification.
         """
         response = self.request.response
         target_unmodified = Snapshot(
-            self.context, providing=providedBy(self.context))
-        bug = data['bug']
+            self.context, providing=providedBy(self.context)
+        )
+        bug = data["bug"]
         try:
             self.context.linkBug(bug, user=self.user)
         except Unauthorized:
             # XXX flacoste 2006-08-23 bug=57470: This should use proper _().
             self.setFieldError(
-                'bug',
-                'You are not allowed to link to private bug #%d.' % bug.id)
+                "bug",
+                "You are not allowed to link to private bug #%d." % bug.id,
+            )
             return
-        bug_props = {'bugid': bug.id, 'title': bug.title}
+        bug_props = {"bugid": bug.id, "title": bug.title}
         response.addNotification(
-            _('Added link to bug #$bugid: '
-              '\N{left double quotation mark}$title'
-              '\N{right double quotation mark}.', mapping=bug_props))
-        notify(ObjectModifiedEvent(
-            self.context, target_unmodified, ['bugs']))
+            _(
+                "Added link to bug #$bugid: "
+                "\N{left double quotation mark}$title"
+                "\N{right double quotation mark}.",
+                mapping=bug_props,
+            )
+        )
+        notify(ObjectModifiedEvent(self.context, target_unmodified, ["bugs"]))
         self.next_url = canonical_url(self.context)
 
 
@@ -107,29 +103,39 @@ class BugLinksListingView(LaunchpadView):
         tags = bugtask_set.getBugTaskTags(bugtasks)
         people = bugtask_set.getBugTaskPeople(bugtasks)
         links = []
-        columns_to_show = ["id", "summary", "bugtargetdisplayname",
-            "importance", "status"]
+        columns_to_show = [
+            "id",
+            "summary",
+            "bugtargetdisplayname",
+            "importance",
+            "status",
+        ]
         for bug, tasks in bugs.items():
-            navigator = BugListingBatchNavigator(tasks, self.request,
+            navigator = BugListingBatchNavigator(
+                tasks,
+                self.request,
                 columns_to_show=columns_to_show,
-                size=config.malone.buglist_batch_size)
+                size=config.malone.buglist_batch_size,
+            )
             get_property_cache(navigator).bug_badge_properties = badges
             get_property_cache(navigator).tags_for_batch = tags
             get_property_cache(navigator).bugtask_people = people
-            links.append({
-                'bug': bug,
-                'title': bug.title,
-                'can_view_bug': True,
-                'tasks': tasks,
-                'batch_navigator': navigator,
-                })
+            links.append(
+                {
+                    "bug": bug,
+                    "title": bug.title,
+                    "can_view_bug": True,
+                    "tasks": tasks,
+                    "batch_navigator": navigator,
+                }
+            )
         return links
 
 
 class BugsUnlinkView(LaunchpadFormView):
     """This view is used to remove bug links from any IBugLinkTarget."""
 
-    label = _('Remove links to bug reports')
+    label = _("Remove links to bug reports")
     schema = IUnlinkBugsForm
     custom_widget_bugs = LabeledMultiCheckBoxWidget
     page_title = label
@@ -139,27 +145,35 @@ class BugsUnlinkView(LaunchpadFormView):
         """See `LaunchpadFormview`."""
         return canonical_url(self.context)
 
-    @action(_('Remove'))
+    @action(_("Remove"))
     def unlinkBugs(self, action, data):
         response = self.request.response
         target_unmodified = Snapshot(
-            self.context, providing=providedBy(self.context))
-        for bug in data['bugs']:
-            replacements = {'bugid': bug.id}
+            self.context, providing=providedBy(self.context)
+        )
+        for bug in data["bugs"]:
+            replacements = {"bugid": bug.id}
             try:
                 self.context.unlinkBug(bug, user=self.user)
                 response.addNotification(
-                    _('Removed link to bug #$bugid.', mapping=replacements))
+                    _("Removed link to bug #$bugid.", mapping=replacements)
+                )
             except Unauthorized:
                 response.addErrorNotification(
-                    _('Cannot remove link to private bug #$bugid.',
-                      mapping=replacements))
-        notify(ObjectModifiedEvent(self.context, target_unmodified, ['bugs']))
+                    _(
+                        "Cannot remove link to private bug #$bugid.",
+                        mapping=replacements,
+                    )
+                )
+        notify(ObjectModifiedEvent(self.context, target_unmodified, ["bugs"]))
         self.next_url = canonical_url(self.context)
 
     def bugsWithPermission(self):
         """Return the bugs that the user has permission to remove. This
         exclude private bugs to which the user doesn't have any permission.
         """
-        return [bug for bug in self.context.bugs
-                if check_permission('launchpad.View', bug)]
+        return [
+            bug
+            for bug in self.context.bugs
+            if check_permission("launchpad.View", bug)
+        ]
diff --git a/lib/lp/bugs/browser/buglisting.py b/lib/lp/bugs/browser/buglisting.py
index 4fbb813..2007a51 100644
--- a/lib/lp/bugs/browser/buglisting.py
+++ b/lib/lp/bugs/browser/buglisting.py
@@ -4,48 +4,36 @@
 """IBugTask-related browser views."""
 
 __all__ = [
-    'BugNominationsView',
-    'BugListingBatchNavigator',
-    'BugListingPortletInfoView',
-    'BugListingPortletStatsView',
-    'BugsBugTaskSearchListingView',
-    'BugTargetView',
-    'BugTaskExpirableListingView',
-    'BugTaskListingItem',
-    'BugTaskListingView',
-    'BugTaskSearchListingView',
-    'get_buglisting_search_filter_url',
-    'get_sortorder_from_request',
-    'TextualBugTaskSearchListingView',
-    ]
+    "BugNominationsView",
+    "BugListingBatchNavigator",
+    "BugListingPortletInfoView",
+    "BugListingPortletStatsView",
+    "BugsBugTaskSearchListingView",
+    "BugTargetView",
+    "BugTaskExpirableListingView",
+    "BugTaskListingItem",
+    "BugTaskListingView",
+    "BugTaskSearchListingView",
+    "get_buglisting_search_filter_url",
+    "get_sortorder_from_request",
+    "TextualBugTaskSearchListingView",
+]
 
 import os.path
-from urllib.parse import (
-    parse_qs,
-    parse_qsl,
-    quote,
-    urlencode,
-    )
+from urllib.parse import parse_qs, parse_qsl, quote, urlencode
 
+import pystache
 from lazr.delegates import delegate_to
 from lazr.restful.interfaces import IJSONRequestCache
 from lazr.uri import URI
-import pystache
 from simplejson import dumps
 from simplejson.encoder import JSONEncoderForHTML
 from zope.authentication.interfaces import IUnauthenticatedPrincipal
 from zope.browserpage import ViewPageTemplateFile
-from zope.component import (
-    getAdapter,
-    getUtility,
-    queryMultiAdapter,
-    )
+from zope.component import getAdapter, getUtility, queryMultiAdapter
 from zope.formlib.interfaces import InputErrors
 from zope.formlib.itemswidgets import RadioWidget
-from zope.interface import (
-    implementer,
-    Interface,
-    )
+from zope.interface import Interface, implementer
 from zope.schema.vocabulary import getVocabularyRegistry
 from zope.security.proxy import isinstance as zope_isinstance
 from zope.traversing.interfaces import IPathAdapter
@@ -58,58 +46,52 @@ from lp.app.browser.tales import (
     BugTrackerFormatterAPI,
     DateTimeFormatterAPI,
     PersonFormatterAPI,
-    )
-from lp.app.enums import (
-    InformationType,
-    ServiceUsage,
-    )
-from lp.app.errors import (
-    NotFoundError,
-    UnexpectedFormData,
-    )
+)
+from lp.app.enums import InformationType, ServiceUsage
+from lp.app.errors import NotFoundError, UnexpectedFormData
 from lp.app.interfaces.launchpad import (
     IHeadingContext,
     IPrivacy,
     IServiceUsage,
-    )
+)
 from lp.app.vocabularies import InformationTypeVocabulary
 from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget
 from lp.app.widgets.popup import PersonPickerWidget
 from lp.app.widgets.project import ProjectScopeWidget
 from lp.bugs.browser.structuralsubscription import (
     expose_structural_subscription_data_to_js,
-    )
+)
 from lp.bugs.browser.widgets.bug import BugTagsWidget
 from lp.bugs.browser.widgets.bugtask import NewLineToSpacesWidget
 from lp.bugs.interfaces.bug import IBugSet
 from lp.bugs.interfaces.bugattachment import BugAttachmentType
 from lp.bugs.interfaces.bugtask import (
+    UNRESOLVED_BUGTASK_STATUSES,
     BugTaskImportance,
     BugTaskStatus,
     BugTaskStatusSearch,
     BugTaskStatusSearchDisplay,
     IBugTask,
     IBugTaskSet,
-    UNRESOLVED_BUGTASK_STATUSES,
-    )
+)
 from lp.bugs.interfaces.bugtasksearch import (
+    DEFAULT_SEARCH_BUGTASK_STATUSES_FOR_DISPLAY,
     BugBlueprintSearch,
     BugBranchSearch,
     BugTagsSearchCombinator,
     BugTaskSearchParams,
-    DEFAULT_SEARCH_BUGTASK_STATUSES_FOR_DISPLAY,
     IBugTaskSearch,
     IFrontPageBugTaskSearch,
     IPersonBugTaskSearch,
     IUpstreamProductBugTaskSearch,
-    )
+)
 from lp.bugs.interfaces.bugtracker import IHasExternalBugTracker
 from lp.bugs.interfaces.malone import IMaloneApplication
 from lp.layers import FeedsLayer
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
-    )
+)
 from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.ociproject import IOCIProject
 from lp.registry.interfaces.person import IPerson
@@ -118,33 +100,25 @@ from lp.registry.interfaces.productseries import IProductSeries
 from lp.registry.interfaces.projectgroup import IProjectGroup
 from lp.registry.interfaces.sourcepackage import ISourcePackage
 from lp.services.config import config
-from lp.services.feeds.browser import (
-    BugTargetLatestBugsFeedLink,
-    FeedsMixin,
-    )
+from lp.services.feeds.browser import BugTargetLatestBugsFeedLink, FeedsMixin
 from lp.services.helpers import shortlist
 from lp.services.propertycache import cachedproperty
-from lp.services.searchbuilder import (
-    all,
-    any,
-    NULL,
-    )
+from lp.services.searchbuilder import NULL, all, any
 from lp.services.utils import obfuscate_structure
 from lp.services.webapp import (
-    canonical_url,
-    enabled_with_permission,
     LaunchpadView,
     Link,
     NavigationMenu,
-    )
+    canonical_url,
+    enabled_with_permission,
+)
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.batching import (
-    get_batch_properties_for_json_cache,
     TableBatchNavigator,
-    )
+    get_batch_properties_for_json_cache,
+)
 from lp.services.webapp.interfaces import ILaunchBag
 
-
 vocabulary_registry = getVocabularyRegistry()
 
 DISPLAY_BUG_STATUS_FOR_PATCHES = {
@@ -162,7 +136,7 @@ DISPLAY_BUG_STATUS_FOR_PATCHES = {
     BugTaskStatus.DOESNOTEXIST: False,
     BugTaskStatusSearch.INCOMPLETE_WITHOUT_RESPONSE: True,
     BugTaskStatusSearch.INCOMPLETE_WITH_RESPONSE: True,
-    }
+}
 
 
 def get_sortorder_from_request(request):
@@ -181,20 +155,20 @@ def get_sortorder_from_request(request):
     ...     LaunchpadTestRequest(form={'orderby': 'priority,-severity'}))
     ['-importance']
     """
-    order_by_string = request.get("orderby", '')
+    order_by_string = request.get("orderby", "")
     if order_by_string:
         if not zope_isinstance(order_by_string, list):
-            order_by = order_by_string.split(',')
+            order_by = order_by_string.split(",")
         else:
             order_by = order_by_string
     else:
         order_by = []
     # Remove old order_by values that people might have in bookmarks.
-    for old_order_by_column in ['priority', 'severity']:
+    for old_order_by_column in ["priority", "severity"]:
         if old_order_by_column in order_by:
             order_by.remove(old_order_by_column)
-        if '-' + old_order_by_column in order_by:
-            order_by.remove('-' + old_order_by_column)
+        if "-" + old_order_by_column in order_by:
+            order_by.remove("-" + old_order_by_column)
     if order_by:
         return order_by
     else:
@@ -217,14 +191,15 @@ def get_default_search_params(user):
 
     """
     return BugTaskSearchParams(
-        user=user, status=any(*UNRESOLVED_BUGTASK_STATUSES), omit_dupes=True)
+        user=user, status=any(*UNRESOLVED_BUGTASK_STATUSES), omit_dupes=True
+    )
 
 
 OLD_BUGTASK_STATUS_MAP = {
-    'Unconfirmed': 'New',
-    'Needs Info': 'Incomplete',
-    'Rejected': 'Invalid',
-    }
+    "Unconfirmed": "New",
+    "Needs Info": "Incomplete",
+    "Rejected": "Invalid",
+}
 
 
 def rewrite_old_bugtask_status_query_string(query_string):
@@ -235,11 +210,12 @@ def rewrite_old_bugtask_status_query_string(query_string):
     query string.
     """
     query_elements = parse_qsl(
-        query_string, keep_blank_values=True, strict_parsing=False)
+        query_string, keep_blank_values=True, strict_parsing=False
+    )
     query_elements_mapped = []
 
     for name, value in query_elements:
-        if name == 'field.status:list':
+        if name == "field.status:list":
             value = OLD_BUGTASK_STATUS_MAP.get(value, value)
         query_elements_mapped.append((name, value))
 
@@ -268,6 +244,7 @@ def target_has_expirable_bugs_listing(target):
 
 class BugTaskListingView(LaunchpadView):
     """A view designed for displaying bug tasks in lists."""
+
     # Note that this right now is only used in tests and to render
     # status in the CVEReportView. It may be a candidate for refactoring
     # or removal.
@@ -283,14 +260,13 @@ class BugTaskListingView(LaunchpadView):
         status_title = status.title.capitalize()
 
         if not assignee:
-            return status_title + ' (unassigned)'
-        assignee_html = PersonFormatterAPI(assignee).link('+assignedbugs')
+            return status_title + " (unassigned)"
+        assignee_html = PersonFormatterAPI(assignee).link("+assignedbugs")
 
-        if status in (BugTaskStatus.INVALID,
-                      BugTaskStatus.FIXCOMMITTED):
-            return '%s by %s' % (status_title, assignee_html)
+        if status in (BugTaskStatus.INVALID, BugTaskStatus.FIXCOMMITTED):
+            return "%s by %s" % (status_title, assignee_html)
         else:
-            return '%s, assigned to %s' % (status_title, assignee_html)
+            return "%s, assigned to %s" % (status_title, assignee_html)
 
     @property
     def status_elsewhere(self):
@@ -303,12 +279,18 @@ class BugTaskListingView(LaunchpadView):
             return "not filed elsewhere"
 
         fixes_found = len(
-            [task for task in related_tasks
-             if task.status in (BugTaskStatus.FIXCOMMITTED,
-                                BugTaskStatus.FIXRELEASED)])
+            [
+                task
+                for task in related_tasks
+                if task.status
+                in (BugTaskStatus.FIXCOMMITTED, BugTaskStatus.FIXRELEASED)
+            ]
+        )
         if fixes_found:
             return "fixed in %d of %d places" % (
-                fixes_found, len(bugtask.bug.bugtasks))
+                fixes_found,
+                len(bugtask.bug.bugtasks),
+            )
         elif len(related_tasks) == 1:
             return "filed in 1 other place"
         else:
@@ -326,19 +308,23 @@ class BugsInfoMixin:
     def bugs_fixed_elsewhere_url(self):
         """A URL to a list of bugs fixed elsewhere."""
         return "%s?field.status_upstream=resolved_upstream" % (
-            canonical_url(self.context, view_name='+bugs'))
+            canonical_url(self.context, view_name="+bugs")
+        )
 
     @property
     def open_cve_bugs_url(self):
         """A URL to a list of open bugs linked to CVEs."""
         return "%s?field.has_cve=on" % (
-            canonical_url(self.context, view_name='+bugs'))
+            canonical_url(self.context, view_name="+bugs")
+        )
 
     @property
     def open_cve_bugs_has_report(self):
         """Whether or not the context has a CVE report page."""
-        return queryMultiAdapter(
-            (self.context, self.request), name='+cve') is not None
+        return (
+            queryMultiAdapter((self.context, self.request), name="+cve")
+            is not None
+        )
 
     @property
     def pending_bugwatches_url(self):
@@ -351,7 +337,8 @@ class BugsInfoMixin:
         if self.context.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
             return None
         return "%s?field.status_upstream=pending_bugwatch" % (
-            canonical_url(self.context, view_name='+bugs'))
+            canonical_url(self.context, view_name="+bugs")
+        )
 
     @property
     def expirable_bugs_url(self):
@@ -363,40 +350,42 @@ class BugsInfoMixin:
         or `IProductSeries`.
         """
         if target_has_expirable_bugs_listing(self.context):
-            return canonical_url(self.context, view_name='+expirable-bugs')
+            return canonical_url(self.context, view_name="+expirable-bugs")
         else:
             return None
 
     @property
     def new_bugs_url(self):
         """A URL to a page of new bugs."""
-        return get_buglisting_search_filter_url(
-            status=BugTaskStatus.NEW.title)
+        return get_buglisting_search_filter_url(status=BugTaskStatus.NEW.title)
 
     @property
     def inprogress_bugs_url(self):
         """A URL to a page of inprogress bugs."""
         return get_buglisting_search_filter_url(
-            status=BugTaskStatus.INPROGRESS.title)
+            status=BugTaskStatus.INPROGRESS.title
+        )
 
     @property
     def open_bugs_url(self):
         """A URL to a list of open bugs."""
-        return canonical_url(self.context, view_name='+bugs')
+        return canonical_url(self.context, view_name="+bugs")
 
     @property
     def critical_bugs_url(self):
         """A URL to a list of critical bugs."""
         return get_buglisting_search_filter_url(
             status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES],
-            importance=BugTaskImportance.CRITICAL.title)
+            importance=BugTaskImportance.CRITICAL.title,
+        )
 
     @property
     def high_bugs_url(self):
         """A URL to a list of high priority bugs."""
         return get_buglisting_search_filter_url(
             status=[status.title for status in UNRESOLVED_BUGTASK_STATUSES],
-            importance=BugTaskImportance.HIGH.title)
+            importance=BugTaskImportance.HIGH.title,
+        )
 
     @property
     def my_bugs_url(self):
@@ -414,8 +403,8 @@ class BugsInfoMixin:
         if self.user is None:
             return None
         return get_buglisting_search_filter_url(
-            affecting_me=True,
-            orderby='-date_last_updated')
+            affecting_me=True, orderby="-date_last_updated"
+        )
 
     @property
     def my_reported_bugs_url(self):
@@ -435,9 +424,13 @@ class BugsStatsMixin(BugsInfoMixin):
     def _bug_stats(self):
         # Circular fail.
         from lp.bugs.model.bugsummary import BugSummary
+
         bug_task_set = getUtility(IBugTaskSet)
         groups = (
-            BugSummary.status, BugSummary.importance, BugSummary.has_patch)
+            BugSummary.status,
+            BugSummary.importance,
+            BugSummary.has_patch,
+        )
         counts = bug_task_set.countBugs(self.user, [self.context], groups)
         # Sum the split out aggregates.
         new = 0
@@ -462,8 +455,13 @@ class BugsStatsMixin(BugsInfoMixin):
                 with_patch += count
             open += count
         result = dict(
-            new=new, open=open, inprogress=inprogress, high=high,
-            critical=critical, with_patch=with_patch)
+            new=new,
+            open=open,
+            inprogress=inprogress,
+            high=high,
+            critical=critical,
+            with_patch=with_patch,
+        )
         return result
 
     @property
@@ -497,35 +495,38 @@ class BugsStatsMixin(BugsInfoMixin):
         or `IProductSeries`.
         """
         if target_has_expirable_bugs_listing(self.context):
-            return getUtility(IBugTaskSet).findExpirableBugTasks(
-                0, user=self.user, target=self.context).count()
+            return (
+                getUtility(IBugTaskSet)
+                .findExpirableBugTasks(0, user=self.user, target=self.context)
+                .count()
+            )
         else:
             return None
 
     @property
     def new_bugs_count(self):
         """A count of new bugs."""
-        return self._bug_stats['new']
+        return self._bug_stats["new"]
 
     @property
     def open_bugs_count(self):
         """A count of open bugs."""
-        return self._bug_stats['open']
+        return self._bug_stats["open"]
 
     @property
     def inprogress_bugs_count(self):
         """A count of in-progress bugs."""
-        return self._bug_stats['inprogress']
+        return self._bug_stats["inprogress"]
 
     @property
     def critical_bugs_count(self):
         """A count of critical bugs."""
-        return self._bug_stats['critical']
+        return self._bug_stats["critical"]
 
     @property
     def high_bugs_count(self):
         """A count of high priority bugs."""
-        return self._bug_stats['high']
+        return self._bug_stats["high"]
 
     @property
     def my_bugs_count(self):
@@ -558,7 +559,7 @@ class BugsStatsMixin(BugsInfoMixin):
     @property
     def bugs_with_patches_count(self):
         """A count of unresolved bugs with patches."""
-        return self._bug_stats['with_patch']
+        return self._bug_stats["with_patch"]
 
 
 class BugListingPortletInfoView(LaunchpadView, BugsInfoMixin):
@@ -570,40 +571,45 @@ class BugListingPortletStatsView(LaunchpadView, BugsStatsMixin):
 
 
 def get_buglisting_search_filter_url(
-        assignee=None, importance=None, status=None, status_upstream=None,
-        has_patches=None, bug_reporter=None,
-        affecting_me=None,
-        orderby=None):
+    assignee=None,
+    importance=None,
+    status=None,
+    status_upstream=None,
+    has_patches=None,
+    bug_reporter=None,
+    affecting_me=None,
+    orderby=None,
+):
     """Return the given URL with the search parameters specified."""
     search_params = []
 
     if assignee is not None:
-        search_params.append(('field.assignee', assignee))
+        search_params.append(("field.assignee", assignee))
     if importance is not None:
-        search_params.append(('field.importance', importance))
+        search_params.append(("field.importance", importance))
     if status is not None:
-        search_params.append(('field.status', status))
+        search_params.append(("field.status", status))
     if status_upstream is not None:
-        search_params.append(('field.status_upstream', status_upstream))
+        search_params.append(("field.status_upstream", status_upstream))
     if has_patches is not None:
-        search_params.append(('field.has_patch', 'on'))
+        search_params.append(("field.has_patch", "on"))
     if bug_reporter is not None:
-        search_params.append(('field.bug_reporter', bug_reporter))
+        search_params.append(("field.bug_reporter", bug_reporter))
     if affecting_me is not None:
-        search_params.append(('field.affects_me', 'on'))
+        search_params.append(("field.affects_me", "on"))
     if orderby is not None:
-        search_params.append(('orderby', orderby))
+        search_params.append(("orderby", orderby))
 
     query_string = urlencode(search_params, doseq=True)
 
     search_filter_url = "+bugs?search=Search"
-    if query_string != '':
+    if query_string != "":
         search_filter_url += "&" + query_string
 
     return search_filter_url
 
 
-@delegate_to(IBugTask, context='bugtask')
+@delegate_to(IBugTask, context="bugtask")
 class BugTaskListingItem:
     """A decorated bug task.
 
@@ -612,9 +618,17 @@ class BugTaskListingItem:
     prefetched by the view and decorate the bug task.
     """
 
-    def __init__(self, bugtask, has_bug_branch,
-                 has_specification, has_patch, tags,
-                 people, request=None, target_context=None):
+    def __init__(
+        self,
+        bugtask,
+        has_bug_branch,
+        has_specification,
+        has_patch,
+        tags,
+        people,
+        request=None,
+        target_context=None,
+    ):
         self.bugtask = bugtask
         self.review_action_widget = None
         self.has_bug_branch = has_bug_branch
@@ -628,30 +642,34 @@ class BugTaskListingItem:
     @property
     def last_significant_change_date(self):
         """The date of the last significant change."""
-        return (self.bugtask.date_closed or self.bugtask.date_fix_committed or
-                self.bugtask.date_inprogress or self.bugtask.date_left_new or
-                self.bugtask.datecreated)
+        return (
+            self.bugtask.date_closed
+            or self.bugtask.date_fix_committed
+            or self.bugtask.date_inprogress
+            or self.bugtask.date_left_new
+            or self.bugtask.datecreated
+        )
 
     @property
     def bug_heat_html(self):
         """Returns the bug heat flames HTML."""
-        return (
-            '<span class="sprite flame">%d</span>'
-            % self.bugtask.bug.heat)
+        return '<span class="sprite flame">%d</span>' % self.bugtask.bug.heat
 
     @property
     def model(self):
         """Provide flattened data about bugtask for simple templaters."""
         age = DateTimeFormatterAPI(self.bug.datecreated).durationsince()
-        age += ' old'
+        age += " old"
         date_last_updated = self.bug.date_last_message
-        if (date_last_updated is None or
-            self.bug.date_last_updated > date_last_updated):
+        if (
+            date_last_updated is None
+            or self.bug.date_last_updated > date_last_updated
+        ):
             date_last_updated = self.bug.date_last_updated
         last_updated_formatter = DateTimeFormatterAPI(date_last_updated)
         last_updated = last_updated_formatter.displaydate()
-        badges = getAdapter(self, IPathAdapter, 'image').badges()
-        target_image = getAdapter(self.target, IPathAdapter, 'image')
+        badges = getAdapter(self, IPathAdapter, "image").badges()
+        target_image = getAdapter(self.target, IPathAdapter, "image")
         if self.bugtask.milestone is not None:
             milestone_name = self.bugtask.milestone.displayname
         else:
@@ -665,74 +683,79 @@ class BugTaskListingItem:
         # are related to a user account) is intercepted
         if self.target_context is None:
             base_tag_url = "%s/?field.tag=" % canonical_url(
-                self.bugtask.target,
-                view_name="+bugs")
+                self.bugtask.target, view_name="+bugs"
+            )
         else:
             base_tag_url = "%s/?field.tag=" % canonical_url(
-                self.target_context,
-                view_name="+bugs")
+                self.target_context, view_name="+bugs"
+            )
 
         flattened = {
-            'age': age,
-            'assignee': assignee,
-            'bug_url': canonical_url(self.bugtask),
-            'bugtarget': self.bugtargetdisplayname,
-            'bugtarget_css': target_image.sprite_css(),
-            'bug_heat_html': self.bug_heat_html,
-            'badges': badges,
-            'id': self.bug.id,
-            'importance': self.importance.title,
-            'importance_class': 'importance' + self.importance.name,
-            'information_type': self.bug.information_type.title,
-            'last_updated': last_updated,
-            'milestone_name': milestone_name,
-            'reporter': reporter.displayname,
-            'status': self.status.title,
-            'status_class': 'status' + self.status.name,
-            'tags': [{'url': base_tag_url + quote(tag), 'tag': tag}
-                for tag in self.tags],
-            'title': self.bug.title,
-            }
+            "age": age,
+            "assignee": assignee,
+            "bug_url": canonical_url(self.bugtask),
+            "bugtarget": self.bugtargetdisplayname,
+            "bugtarget_css": target_image.sprite_css(),
+            "bug_heat_html": self.bug_heat_html,
+            "badges": badges,
+            "id": self.bug.id,
+            "importance": self.importance.title,
+            "importance_class": "importance" + self.importance.name,
+            "information_type": self.bug.information_type.title,
+            "last_updated": last_updated,
+            "milestone_name": milestone_name,
+            "reporter": reporter.displayname,
+            "status": self.status.title,
+            "status_class": "status" + self.status.name,
+            "tags": [
+                {"url": base_tag_url + quote(tag), "tag": tag}
+                for tag in self.tags
+            ],
+            "title": self.bug.title,
+        }
 
         # This is a total hack, but pystache will run both truth/false values
         # for an empty list for some reason, and it "works" if it's just a
         # flag like this. We need this value for the mustache template to be
         # able to tell that there are no tags without looking at the list.
-        flattened['has_tags'] = True if len(flattened['tags']) else False
+        flattened["has_tags"] = True if len(flattened["tags"]) else False
         return flattened
 
 
 class BugListingBatchNavigator(TableBatchNavigator):
     """A specialised batch navigator to load smartly extra bug information."""
 
-    def __init__(self, tasks, request, columns_to_show, size,
-                 target_context=None):
+    def __init__(
+        self, tasks, request, columns_to_show, size, target_context=None
+    ):
         self.request = request
         self.target_context = target_context
         self.user = getUtility(ILaunchBag).user
         self.field_visibility_defaults = {
-            'show_datecreated': False,
-            'show_assignee': False,
-            'show_targetname': True,
-            'show_heat': True,
-            'show_id': True,
-            'show_importance': True,
-            'show_information_type': False,
-            'show_date_last_updated': False,
-            'show_milestone_name': False,
-            'show_reporter': False,
-            'show_status': True,
-            'show_tag': False,
+            "show_datecreated": False,
+            "show_assignee": False,
+            "show_targetname": True,
+            "show_heat": True,
+            "show_id": True,
+            "show_importance": True,
+            "show_information_type": False,
+            "show_date_last_updated": False,
+            "show_milestone_name": False,
+            "show_reporter": False,
+            "show_status": True,
+            "show_tag": False,
         }
         self.field_visibility = None
         self._setFieldVisibility()
         TableBatchNavigator.__init__(
-            self, tasks, request, columns_to_show=columns_to_show, size=size)
+            self, tasks, request, columns_to_show=columns_to_show, size=size
+        )
 
     @cachedproperty
     def bug_badge_properties(self):
         return getUtility(IBugTaskSet).getBugTaskBadgeProperties(
-            self.currentBatch())
+            self.currentBatch()
+        )
 
     @cachedproperty
     def tags_for_batch(self):
@@ -746,12 +769,12 @@ class BugListingBatchNavigator(TableBatchNavigator):
 
     def getCookieName(self):
         """Return the cookie name used in bug listings js code."""
-        cookie_name_template = '%s-buglist-fields'
-        cookie_name = ''
+        cookie_name_template = "%s-buglist-fields"
+        cookie_name = ""
         if self.user is not None:
             cookie_name = cookie_name_template % self.user.name
         else:
-            cookie_name = cookie_name_template % 'anon'
+            cookie_name = cookie_name_template % "anon"
         return cookie_name
 
     def _setFieldVisibility(self):
@@ -774,14 +797,15 @@ class BugListingBatchNavigator(TableBatchNavigator):
             if field not in self.field_visibility:
                 continue
             # We only record True or False for field values.
-            self.field_visibility[field] = (value == 'true')
+            self.field_visibility[field] = value == "true"
 
     def _getListingItem(self, bugtask):
         """Return a decorated bugtask for the bug listing."""
         badge_property = self.bug_badge_properties[bugtask]
         tags = self.tags_for_batch.get(bugtask.id, ())
-        if (IMaloneApplication.providedBy(self.target_context) or
-            IPerson.providedBy(self.target_context)):
+        if IMaloneApplication.providedBy(
+            self.target_context
+        ) or IPerson.providedBy(self.target_context):
             # XXX Tom Berger bug=529846
             # When we have a specific interface for things that have bug heat
             # it would be better to use that for the check here instead.
@@ -790,13 +814,14 @@ class BugListingBatchNavigator(TableBatchNavigator):
             target_context = self.target_context
         return BugTaskListingItem(
             bugtask,
-            badge_property['has_branch'],
-            badge_property['has_specification'],
-            badge_property['has_patch'],
+            badge_property["has_branch"],
+            badge_property["has_specification"],
+            badge_property["has_patch"],
             tags,
             self.bugtask_people,
             request=self.request,
-            target_context=target_context)
+            target_context=target_context,
+        )
 
     def getBugListingItems(self):
         """Return a decorated list of visible bug tasks."""
@@ -805,14 +830,16 @@ class BugListingBatchNavigator(TableBatchNavigator):
     @cachedproperty
     def mustache_template(self):
         template_path = os.path.join(
-            config.root, 'lib/lp/bugs/templates/buglisting.mustache')
+            config.root, "lib/lp/bugs/templates/buglisting.mustache"
+        )
         with open(template_path) as template_file:
             return template_file.read()
 
     @property
     def mustache_listings(self):
-        return 'LP.mustache_listings = %s;' % dumps(
-            self.mustache_template, cls=JSONEncoderForHTML)
+        return "LP.mustache_listings = %s;" % dumps(
+            self.mustache_template, cls=JSONEncoderForHTML
+        )
 
     @property
     def mustache(self):
@@ -820,14 +847,14 @@ class BugListingBatchNavigator(TableBatchNavigator):
         objects = IJSONRequestCache(self.request).objects
         if IUnauthenticatedPrincipal.providedBy(self.request.principal):
             objects = obfuscate_structure(objects)
-        model = dict(objects['mustache_model'])
+        model = dict(objects["mustache_model"])
         model.update(self.field_visibility)
         return pystache.render(self.mustache_template, model)
 
     @property
     def model(self):
         items = [bugtask.model for bugtask in self.getBugListingItems()]
-        return {'items': items}
+        return {"items": items}
 
 
 class IBugTaskSearchListingMenu(Interface):
@@ -836,70 +863,63 @@ class IBugTaskSearchListingMenu(Interface):
 
 class BugTaskSearchListingMenu(NavigationMenu):
     """The search listing navigation menu."""
+
     usedfor = IBugTaskSearchListingMenu
-    facet = 'bugs'
+    facet = "bugs"
 
     @property
     def links(self):
         bug_target = self.context.context
         if IDistroSeries.providedBy(bug_target):
-            return (
-                'nominations',
-                )
+            return ("nominations",)
         if IProductSeries.providedBy(bug_target):
-            return (
-                'nominations',
-                )
+            return ("nominations",)
         else:
             return ()
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def bugsupervisor(self):
-        return Link('+bugsupervisor', 'Change bug supervisor', icon='edit')
+        return Link("+bugsupervisor", "Change bug supervisor", icon="edit")
 
     def nominations(self):
-        return Link('+nominations', 'Review nominations', icon='bug')
+        return Link("+nominations", "Review nominations", icon="bug")
 
 
 # All sort orders supported by BugTaskSet.search() and a title for
 # them.  Keep in sync with lp.bugs.model.bugtasksearch.orderby_expression.
 SORT_KEYS = [
-    ('importance', 'Importance', 'desc'),
-    ('status', 'Status', 'asc'),
-    ('information_type', 'Information Type', 'asc'),
-    ('id', 'Number', 'desc'),
-    ('title', 'Title', 'asc'),
-    ('targetname', 'Package/Project/Series name', 'asc'),
-    ('milestone_name', 'Milestone', 'asc'),
-    ('date_last_updated', 'Date last updated', 'desc'),
-    ('assignee', 'Assignee', 'asc'),
-    ('reporter', 'Reporter', 'asc'),
-    ('datecreated', 'Age', 'desc'),
-    ('tag', 'Tags', 'asc'),
-    ('heat', 'Heat', 'desc'),
-    ('date_closed', 'Date closed', 'desc'),
-    ('dateassigned', 'Date when the bug task was assigned', 'desc'),
-    ('number_of_duplicates', 'Number of duplicates', 'desc'),
-    ('latest_patch_uploaded', 'Date latest patch uploaded', 'desc'),
-    ('message_count', 'Number of comments', 'desc'),
-    ('milestone', 'Milestone ID', 'desc'),
-    ('task', 'Bug task ID', 'desc'),
-    ('users_affected_count', 'Number of affected users', 'desc'),
-    ]
+    ("importance", "Importance", "desc"),
+    ("status", "Status", "asc"),
+    ("information_type", "Information Type", "asc"),
+    ("id", "Number", "desc"),
+    ("title", "Title", "asc"),
+    ("targetname", "Package/Project/Series name", "asc"),
+    ("milestone_name", "Milestone", "asc"),
+    ("date_last_updated", "Date last updated", "desc"),
+    ("assignee", "Assignee", "asc"),
+    ("reporter", "Reporter", "asc"),
+    ("datecreated", "Age", "desc"),
+    ("tag", "Tags", "asc"),
+    ("heat", "Heat", "desc"),
+    ("date_closed", "Date closed", "desc"),
+    ("dateassigned", "Date when the bug task was assigned", "desc"),
+    ("number_of_duplicates", "Number of duplicates", "desc"),
+    ("latest_patch_uploaded", "Date latest patch uploaded", "desc"),
+    ("message_count", "Number of comments", "desc"),
+    ("milestone", "Milestone ID", "desc"),
+    ("task", "Bug task ID", "desc"),
+    ("users_affected_count", "Number of affected users", "desc"),
+]
 
 
 @implementer(IBugTaskSearchListingMenu)
 class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
     """View that renders a list of bugs for a given set of search criteria."""
 
-    related_features = {
-        'bugs.dynamic_bug_listings.pre_fetch': False
-        }
+    related_features = {"bugs.dynamic_bug_listings.pre_fetch": False}
 
     # Only include <link> tags for bug feeds when using this view.
-    feed_types = (
-        BugTargetLatestBugsFeedLink,
-        )
+    feed_types = (BugTargetLatestBugsFeedLink,)
 
     # These widgets are customised so as to keep the presentation of this view
     # and its descendants consistent after refactoring to use
@@ -949,8 +969,9 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
 
     @property
     def can_have_external_bugtracker(self):
-        return (IProduct.providedBy(self.context)
-                or IProductSeries.providedBy(self.context))
+        return IProduct.providedBy(self.context) or IProductSeries.providedBy(
+            self.context
+        )
 
     @property
     def bugtracker(self):
@@ -959,11 +980,11 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         :returns: str which may contain HTML.
         """
         if self.bug_tracking_usage == ServiceUsage.LAUNCHPAD:
-            return 'Launchpad'
+            return "Launchpad"
         elif self.external_bugtracker:
             return BugTrackerFormatterAPI(self.external_bugtracker).link(None)
         else:
-            return 'None specified'
+            return "None specified"
 
     @cachedproperty
     def upstream_project(self):
@@ -999,12 +1020,14 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         :returns: `IProduct` or None
         """
         product = self.upstream_project
-        if (product is not None and
-            product.bug_tracking_usage == ServiceUsage.LAUNCHPAD):
+        if (
+            product is not None
+            and product.bug_tracking_usage == ServiceUsage.LAUNCHPAD
+        ):
             return product
         return None
 
-    page_title = 'Bugs'
+    page_title = "Bugs"
 
     @property
     def label(self):
@@ -1029,7 +1052,7 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         view, but it does not match this view's bug listing when
         any search parameters are passed in.
         """
-        if self.request.get('QUERY_STRING', '') == '':
+        if self.request.get("QUERY_STRING", "") == "":
             # There is no query in this request, so it's okay for this page to
             # have its feed links.
             return super().feed_links
@@ -1043,13 +1066,15 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
 
         Look for old status names and redirect to a new location if found.
         """
-        query_string = self.request.get('QUERY_STRING')
+        query_string = self.request.get("QUERY_STRING")
         if query_string:
-            query_string_rewritten = (
-                rewrite_old_bugtask_status_query_string(query_string))
+            query_string_rewritten = rewrite_old_bugtask_status_query_string(
+                query_string
+            )
             if query_string_rewritten != query_string:
                 redirect_uri = URI(self.request.getURL()).replace(
-                    query=query_string_rewritten)
+                    query=query_string_rewritten
+                )
                 self.request.response.redirect(str(redirect_uri), status=301)
                 return
 
@@ -1064,37 +1089,46 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         self._validate(None, {})
 
         expose_structural_subscription_data_to_js(
-            self.context, self.request, self.user)
-        can_view = (IPrivacy(self.context, None) is None
-            or check_permission('launchpad.View', self.context))
-        if (can_view and
-            not FeedsLayer.providedBy(self.request) and
-            not self.request.form.get('advanced')):
+            self.context, self.request, self.user
+        )
+        can_view = IPrivacy(self.context, None) is None or check_permission(
+            "launchpad.View", self.context
+        )
+        if (
+            can_view
+            and not FeedsLayer.providedBy(self.request)
+            and not self.request.form.get("advanced")
+        ):
             cache = IJSONRequestCache(self.request)
-            view_names = {reg.name for reg
-                in iter_view_registrations(self.__class__)}
+            view_names = {
+                reg.name for reg in iter_view_registrations(self.__class__)
+            }
             if len(view_names) != 1:
                 raise AssertionError("Ambiguous view name.")
-            cache.objects['view_name'] = view_names.pop()
+            cache.objects["view_name"] = view_names.pop()
             batch_navigator = self.search()
-            cache.objects['mustache_model'] = batch_navigator.model
+            cache.objects["mustache_model"] = batch_navigator.model
             cache.objects.update(
-                get_batch_properties_for_json_cache(self, batch_navigator))
-            cache.objects['field_visibility'] = (
-                batch_navigator.field_visibility)
-            cache.objects['field_visibility_defaults'] = (
-                batch_navigator.field_visibility_defaults)
-            cache.objects['cbl_cookie_name'] = (
-                batch_navigator.getCookieName())
-
-            cache.objects['order_by'] = ','.join(
-                get_sortorder_from_request(self.request))
-            cache.objects['sort_keys'] = SORT_KEYS
+                get_batch_properties_for_json_cache(self, batch_navigator)
+            )
+            cache.objects[
+                "field_visibility"
+            ] = batch_navigator.field_visibility
+            cache.objects[
+                "field_visibility_defaults"
+            ] = batch_navigator.field_visibility_defaults
+            cache.objects["cbl_cookie_name"] = batch_navigator.getCookieName()
+
+            cache.objects["order_by"] = ",".join(
+                get_sortorder_from_request(self.request)
+            )
+            cache.objects["sort_keys"] = SORT_KEYS
 
     @property
     def show_config_portlet(self):
-        if (IDistribution.providedBy(self.context) or
-            IProduct.providedBy(self.context)):
+        if IDistribution.providedBy(self.context) or IProduct.providedBy(
+            self.context
+        ):
             return True
         else:
             return False
@@ -1111,33 +1145,55 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         sourcepackage_context = self._sourcePackageContext()
         ociproject_context = self._ociprojectContext()
 
-        if (upstream_context or productseries_context or
-            distrosourcepackage_context or sourcepackage_context):
+        if (
+            upstream_context
+            or productseries_context
+            or distrosourcepackage_context
+            or sourcepackage_context
+        ):
             return ["id", "summary", "importance", "status", "heat"]
         elif distribution_context or distroseries_context:
             return [
-                "id", "summary", "packagename", "importance", "status",
-                "heat"]
+                "id",
+                "summary",
+                "packagename",
+                "importance",
+                "status",
+                "heat",
+            ]
         elif project_context:
             return [
-                "id", "summary", "productname", "importance", "status",
-                "heat"]
+                "id",
+                "summary",
+                "productname",
+                "importance",
+                "status",
+                "heat",
+            ]
         elif ociproject_context:
             return [
-                "id", "summary", "ociproject", "importance", "status", "heat"]
+                "id",
+                "summary",
+                "ociproject",
+                "importance",
+                "status",
+                "heat",
+            ]
         else:
             raise AssertionError(
                 "Unrecognized context; don't know which report "
-                "columns to show.")
+                "columns to show."
+            )
 
     bugtask_table_template = ViewPageTemplateFile(
-        '../templates/bugs-table-include.pt')
+        "../templates/bugs-table-include.pt"
+    )
 
     @property
     def template(self):
-        query_string = self.request.get('QUERY_STRING') or ''
+        query_string = self.request.get("QUERY_STRING") or ""
         query_params = parse_qs(query_string)
-        if 'batch_request' in query_params:
+        if "batch_request" in query_params:
             return self.bugtask_table_template
         else:
             return super().template
@@ -1150,13 +1206,19 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         """
         # The only way the user should get these field values incorrect is
         # through a stale bookmark or a hand-hacked URL.
-        for field_name in ("status", "importance", "milestone", "component",
-                           "status_upstream"):
+        for field_name in (
+            "status",
+            "importance",
+            "milestone",
+            "component",
+            "status_upstream",
+        ):
             if self.getFieldError(field_name):
                 raise UnexpectedFormData(
                     "Unexpected value for field '%s'. Perhaps your bookmarks "
-                    "are out of date or you changed the URL by hand?" %
-                    field_name)
+                    "are out of date or you changed the URL by hand?"
+                    % field_name
+                )
 
         sort_column_names = {sort_key[0] for sort_key in SORT_KEYS}
         orderby = get_sortorder_from_request(self.request)
@@ -1166,14 +1228,16 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
 
             if orderby_col not in sort_column_names:
                 raise UnexpectedFormData(
-                    "Unknown sort column '%s'" % orderby_col)
+                    "Unknown sort column '%s'" % orderby_col
+                )
 
     def setUpWidgets(self):
         """Customize the onKeyPress event of the assignee chooser."""
         LaunchpadFormView.setUpWidgets(self)
 
-        self.widgets["assignee"].onKeyPress = (
-            "selectWidget('assignee_option', event)")
+        self.widgets[
+            "assignee"
+        ].onKeyPress = "selectWidget('assignee_option', event)"
 
     def validate(self, data):
         """Validates the form."""
@@ -1190,15 +1254,19 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         convert the old string parameter into a list.
         """
         old_upstream_status_values_to_new_values = {
-            'only_resolved_upstream': 'resolved_upstream'}
-
-        status_upstream = self.request.get('field.status_upstream')
-        if (not isinstance(status_upstream, list) and
-                status_upstream in old_upstream_status_values_to_new_values):
-            self.request.form['field.status_upstream'] = [
-                old_upstream_status_values_to_new_values[status_upstream]]
-        elif status_upstream == '':
-            del self.request.form['field.status_upstream']
+            "only_resolved_upstream": "resolved_upstream"
+        }
+
+        status_upstream = self.request.get("field.status_upstream")
+        if (
+            not isinstance(status_upstream, list)
+            and status_upstream in old_upstream_status_values_to_new_values
+        ):
+            self.request.form["field.status_upstream"] = [
+                old_upstream_status_values_to_new_values[status_upstream]
+            ]
+        elif status_upstream == "":
+            del self.request.form["field.status_upstream"]
         else:
             # The value of status_upstream is either correct, so nothing to
             # do, or it has some other error, which is handled in
@@ -1220,7 +1288,7 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         if data:
             searchtext = data.get("searchtext")
             if searchtext:
-                if searchtext.startswith('#'):
+                if searchtext.startswith("#"):
                     searchtext = searchtext[1:]
                 if searchtext.isdigit():
                     try:
@@ -1232,32 +1300,33 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
 
             assignee_option = self.request.form.get("assignee_option")
             if assignee_option == "none":
-                data['assignee'] = NULL
+                data["assignee"] = NULL
 
             has_patch = data.pop("has_patch", False)
             if has_patch:
                 data["attachmenttype"] = BugAttachmentType.PATCH
 
-            has_branches = data.get('has_branches', True)
-            has_no_branches = data.get('has_no_branches', True)
+            has_branches = data.get("has_branches", True)
+            has_no_branches = data.get("has_no_branches", True)
             if has_branches and not has_no_branches:
-                data['linked_branches'] = BugBranchSearch.BUGS_WITH_BRANCHES
+                data["linked_branches"] = BugBranchSearch.BUGS_WITH_BRANCHES
             elif not has_branches and has_no_branches:
-                data['linked_branches'] = (
-                    BugBranchSearch.BUGS_WITHOUT_BRANCHES)
+                data["linked_branches"] = BugBranchSearch.BUGS_WITHOUT_BRANCHES
             else:
-                data['linked_branches'] = BugBranchSearch.ALL
+                data["linked_branches"] = BugBranchSearch.ALL
 
-            has_blueprints = data.get('has_blueprints', True)
-            has_no_blueprints = data.get('has_no_blueprints', True)
+            has_blueprints = data.get("has_blueprints", True)
+            has_no_blueprints = data.get("has_no_blueprints", True)
             if has_blueprints and not has_no_blueprints:
-                data['linked_blueprints'] = (
-                    BugBlueprintSearch.BUGS_WITH_BLUEPRINTS)
+                data[
+                    "linked_blueprints"
+                ] = BugBlueprintSearch.BUGS_WITH_BLUEPRINTS
             elif not has_blueprints and has_no_blueprints:
-                data['linked_blueprints'] = (
-                    BugBlueprintSearch.BUGS_WITHOUT_BLUEPRINTS)
+                data[
+                    "linked_blueprints"
+                ] = BugBlueprintSearch.BUGS_WITHOUT_BLUEPRINTS
             else:
-                data['linked_blueprints'] = BugBlueprintSearch.ALL
+                data["linked_blueprints"] = BugBlueprintSearch.ALL
 
             # Filter appropriately if the user wants to restrict the
             # search to only bugs with no package information.
@@ -1270,7 +1339,7 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         # "Normalize" the form data into search arguments.
         form_values = {}
         for key, value in data.items():
-            if key in ('tag'):
+            if key in ("tag"):
                 # Skip tag-related parameters, they
                 # are handled later on.
                 continue
@@ -1280,24 +1349,25 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
             else:
                 form_values[key] = value
 
-        if 'tag' in data:
+        if "tag" in data:
             # Tags require special handling, since they can be used
             # to search either inclusively or exclusively.
             # We take a look at the `tags_combinator` field, and wrap
             # the tag list in the appropriate search directive (either
             # `any` or `all`). If no value is supplied, we assume `any`,
             # in order to remain compatible with old saved search URLs.
-            tags = data['tag']
+            tags = data["tag"]
             tags_combinator_all = (
-                'tags_combinator' in data and
-                data['tags_combinator'] == BugTagsSearchCombinator.ALL)
+                "tags_combinator" in data
+                and data["tags_combinator"] == BugTagsSearchCombinator.ALL
+            )
             if zope_isinstance(tags, (list, tuple)) and len(tags) > 0:
                 if tags_combinator_all:
-                    form_values['tag'] = all(*tags)
+                    form_values["tag"] = all(*tags)
                 else:
-                    form_values['tag'] = any(*tags)
+                    form_values["tag"] = any(*tags)
             else:
-                form_values['tag'] = tags
+                form_values["tag"] = tags
 
         search_params = get_default_search_params(self.user)
         search_params.orderby = get_sortorder_from_request(self.request)
@@ -1306,27 +1376,30 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         return search_params
 
     def _buildUpstreamStatusParams(self, data):
-        """ Convert the status_upstream value to parameters we can
+        """Convert the status_upstream value to parameters we can
         send to BugTaskSet.search().
         """
-        if 'status_upstream' in data:
-            status_upstream = data['status_upstream']
-            if 'pending_bugwatch' in status_upstream:
-                data['pending_bugwatch_elsewhere'] = True
-            if 'resolved_upstream' in status_upstream:
-                data['resolved_upstream'] = True
-            if 'open_upstream' in status_upstream:
-                data['open_upstream'] = True
-            if 'hide_upstream' in status_upstream:
-                data['has_no_upstream_bugtask'] = True
-            del data['status_upstream']
+        if "status_upstream" in data:
+            status_upstream = data["status_upstream"]
+            if "pending_bugwatch" in status_upstream:
+                data["pending_bugwatch_elsewhere"] = True
+            if "resolved_upstream" in status_upstream:
+                data["resolved_upstream"] = True
+            if "open_upstream" in status_upstream:
+                data["open_upstream"] = True
+            if "hide_upstream" in status_upstream:
+                data["has_no_upstream_bugtask"] = True
+            del data["status_upstream"]
 
     def _getBatchNavigator(self, tasks):
         """Return the batch navigator to be used to batch the bugtasks."""
         return BugListingBatchNavigator(
-            tasks, self.request, columns_to_show=self.columns_to_show,
+            tasks,
+            self.request,
+            columns_to_show=self.columns_to_show,
             size=config.malone.buglist_batch_size,
-            target_context=self.context)
+            target_context=self.context,
+        )
 
     def buildBugTaskSearchParams(self, searchtext=None, extra_params=None):
         """Build the parameters to submit to the `searchTasks` method.
@@ -1358,14 +1431,14 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         # A mapping of parameters that appear in the destination
         # with a different name, or are being dropped altogether.
         param_names_map = {
-            'searchtext': 'search_text',
-            'omit_dupes': 'omit_duplicates',
-            'subscriber': 'bug_subscriber',
-            'tag': 'tags',
+            "searchtext": "search_text",
+            "omit_dupes": "omit_duplicates",
+            "subscriber": "bug_subscriber",
+            "tag": "tags",
             # The correct value is being retrieved
             # using get_sortorder_from_request()
-            'orderby': None,
-            }
+            "orderby": None,
+        }
 
         for key, value in data.items():
             if key in param_names_map:
@@ -1377,9 +1450,9 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
 
         assignee_option = self.request.form.get("assignee_option")
         if assignee_option == "none":
-            params['assignee'] = NULL
+            params["assignee"] = NULL
 
-        params['order_by'] = get_sortorder_from_request(self.request)
+        params["order_by"] = get_sortorder_from_request(self.request)
 
         return params
 
@@ -1396,12 +1469,14 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         """
         if self._batch_navigator is None:
             unbatchedTasks = self.searchUnbatched(
-                searchtext, context, extra_params)
+                searchtext, context, extra_params
+            )
             self._batch_navigator = self._getBatchNavigator(unbatchedTasks)
         return self._batch_navigator
 
-    def searchUnbatched(self, searchtext=None, context=None,
-                        extra_params=None):
+    def searchUnbatched(
+        self, searchtext=None, context=None, extra_params=None
+    ):
         """Return a `SelectResults` object for the GET search criteria.
 
         :param searchtext: Text that must occur in the bug report. If
@@ -1417,40 +1492,46 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
             context = self.context
 
         search_params = self.buildSearchParams(
-            searchtext=searchtext, extra_params=extra_params)
+            searchtext=searchtext, extra_params=extra_params
+        )
         search_params.user = self.user
         try:
             tasks = context.searchTasks(search_params)
         except ValueError as e:
             self.request.response.addErrorNotification(str(e))
-            self.request.response.redirect(canonical_url(
-                self.context, rootsite='bugs', view_name='+bugs'))
+            self.request.response.redirect(
+                canonical_url(self.context, rootsite="bugs", view_name="+bugs")
+            )
             tasks = None
         return tasks
 
     def getWidgetValues(
-        self, vocabulary_name=None, vocabulary=None, default_values=()):
+        self, vocabulary_name=None, vocabulary=None, default_values=()
+    ):
         """Return data used to render a field's widget.
 
         Either `vocabulary_name` or `vocabulary` must be supplied."""
         widget_values = []
 
         if vocabulary is None:
-            assert vocabulary_name is not None, 'No vocabulary specified.'
-            vocabulary = vocabulary_registry.get(
-                self.context, vocabulary_name)
+            assert vocabulary_name is not None, "No vocabulary specified."
+            vocabulary = vocabulary_registry.get(self.context, vocabulary_name)
         for term in vocabulary:
             widget_values.append(
                 dict(
-                    value=term.token, title=term.title or term.token,
-                    checked=term.value in default_values))
+                    value=term.token,
+                    title=term.title or term.token,
+                    checked=term.value in default_values,
+                )
+            )
         return shortlist(widget_values, longest_expected=13)
 
     def getStatusWidgetValues(self):
         """Return data used to render the status checkboxes."""
         return self.getWidgetValues(
             vocabulary=BugTaskStatusSearchDisplay,
-            default_values=DEFAULT_SEARCH_BUGTASK_STATUSES_FOR_DISPLAY)
+            default_values=DEFAULT_SEARCH_BUGTASK_STATUSES_FOR_DISPLAY,
+        )
 
     def getImportanceWidgetValues(self):
         """Return data used to render the Importance checkboxes."""
@@ -1458,10 +1539,12 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
 
     def getInformationTypeWidgetValues(self):
         """Return data used to render the Information Type checkboxes."""
-        if (IProduct.providedBy(self.context)
-            or IDistribution.providedBy(self.context)):
+        if IProduct.providedBy(self.context) or IDistribution.providedBy(
+            self.context
+        ):
             vocab = InformationTypeVocabulary(
-                types=self.context.getAllowedBugInformationTypes())
+                types=self.context.getAllowedBugInformationTypes()
+            )
         else:
             vocab = InformationType
         return self.getWidgetValues(vocabulary=vocab)
@@ -1482,10 +1565,13 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         """Show the component widget on the advanced search page?"""
         context = self.context
         return (
-            (IDistribution.providedBy(context) and
-             context.currentseries is not None) or
-            IDistroSeries.providedBy(context) or
-            ISourcePackage.providedBy(context))
+            (
+                IDistribution.providedBy(context)
+                and context.currentseries is not None
+            )
+            or IDistroSeries.providedBy(context)
+            or ISourcePackage.providedBy(context)
+        )
 
     def shouldShowStructuralSubscriberWidget(self):
         """Should the structural subscriber widget be shown on the page?
@@ -1500,8 +1586,9 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         The widget will be shown only on a distribution or
         distroseries's advanced search page.
         """
-        return (IDistribution.providedBy(self.context) or
-                IDistroSeries.providedBy(self.context))
+        return IDistribution.providedBy(
+            self.context
+        ) or IDistroSeries.providedBy(self.context)
 
     def shouldShowReporterWidget(self):
         """Should the reporter widget be shown on the advanced search page?"""
@@ -1512,10 +1599,13 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
         for different series.
         """
         return (
-            IDistribution.providedBy(self.context) and self.context.series
+            IDistribution.providedBy(self.context)
+            and self.context.series
             or IDistroSeries.providedBy(self.context)
-            or IProduct.providedBy(self.context) and self.context.series
-            or IProductSeries.providedBy(self.context))
+            or IProduct.providedBy(self.context)
+            and self.context.series
+            or IProductSeries.providedBy(self.context)
+        )
 
     def shouldShowSubscriberWidget(self):
         """Show the subscriber widget on the advanced search page?"""
@@ -1524,8 +1614,9 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
     def shouldShowUpstreamStatusBox(self):
         """Should the upstream status filtering widgets be shown?"""
         return self.isUpstreamProduct or not (
-            IProduct.providedBy(self.context) or
-            IProjectGroup.providedBy(self.context))
+            IProduct.providedBy(self.context)
+            or IProjectGroup.providedBy(self.context)
+        )
 
     def shouldShowTeamPortlet(self):
         """Should the User's Teams portlet me shown in the results?"""
@@ -1539,15 +1630,15 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
             target = self.context
 
         if IDistribution.providedBy(target):
-            return 'Package or series subscriber'
+            return "Package or series subscriber"
         elif IDistroSeries.providedBy(target):
-            return 'Package subscriber'
+            return "Package subscriber"
         elif IProduct.providedBy(target):
-            return 'Series subscriber'
+            return "Series subscriber"
         elif IProjectGroup.providedBy(target):
-            return 'Project or series subscriber'
+            return "Project or series subscriber"
         elif IPerson.providedBy(target):
-            return 'Project, distribution, package, or series subscriber'
+            return "Project, distribution, package, or series subscriber"
         else:
             return None
 
@@ -1570,8 +1661,7 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
 
     def shouldShowAdvancedForm(self):
         """Return True if the advanced form should be shown, or False."""
-        if (self.request.form.get('advanced')
-            or self.form_has_errors):
+        if self.request.form.get("advanced") or self.form_has_errors:
             return True
         else:
             return False
@@ -1588,21 +1678,28 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
     def validateVocabulariesAdvancedForm(self):
         """Provides a meaningful message for vocabulary validation errors."""
         error_message = _(
-            "There's no person with the name or email address '%s'.")
+            "There's no person with the name or email address '%s'."
+        )
 
-        for name in ('assignee', 'bug_reporter', 'structural_subscriber',
-                     'bug_commenter', 'subscriber'):
+        for name in (
+            "assignee",
+            "bug_reporter",
+            "structural_subscriber",
+            "bug_commenter",
+            "subscriber",
+        ):
             if self.getFieldError(name):
                 self.setFieldError(
-                    name, error_message %
-                        self.request.get('field.%s' % name))
+                    name, error_message % self.request.get("field.%s" % name)
+                )
 
     @property
     def isUpstreamProduct(self):
         """Is the context a Product that does not use Malone?"""
         return (
             IProduct.providedBy(self.context)
-            and self.context.bug_tracking_usage != ServiceUsage.LAUNCHPAD)
+            and self.context.bug_tracking_usage != ServiceUsage.LAUNCHPAD
+        )
 
     def _upstreamContext(self):
         """Is this page being viewed in an upstream context?
@@ -1674,8 +1771,8 @@ class BugTaskSearchListingView(LaunchpadFormView, FeedsMixin, BugsInfoMixin):
             answers_usage = IServiceUsage(self.context).answers_usage
             if answers_usage == ServiceUsage.LAUNCHPAD:
                 return canonical_url(
-                    self.context, rootsite='answers',
-                    view_name='+addquestion')
+                    self.context, rootsite="answers", view_name="+addquestion"
+                )
         else:
             return None
 
@@ -1685,22 +1782,22 @@ class BugTargetView(LaunchpadView):
 
     def latestBugTasks(self, quantity=5):
         """Return <quantity> latest bugs reported against this target."""
-        params = BugTaskSearchParams(orderby="-datecreated",
-                                     omit_dupes=True,
-                                     user=getUtility(ILaunchBag).user)
+        params = BugTaskSearchParams(
+            orderby="-datecreated",
+            omit_dupes=True,
+            user=getUtility(ILaunchBag).user,
+        )
 
         tasklist = self.context.searchTasks(params)
         return tasklist[:quantity]
 
 
 class TextualBugTaskSearchListingView(BugTaskSearchListingView):
-    """View that renders a list of bug IDs for a given set of search criteria.
-    """
+    """View that renders a list of bug IDs for a set of search criteria."""
 
     def render(self):
         """Render the BugTarget for text display."""
-        self.request.response.setHeader(
-            'Content-type', 'text/plain')
+        self.request.response.setHeader("Content-type", "text/plain")
 
         # This uses the BugTaskSet internal API instead of using the
         # standard searchTasks() because the latter can retrieve a lot
@@ -1709,18 +1806,26 @@ class TextualBugTaskSearchListingView(BugTaskSearchListingView):
         search_params = self.buildSearchParams()
         search_params.setTarget(self.context)
 
-        return "".join("%d\n" % bug_id for bug_id in
-            getUtility(IBugTaskSet).searchBugIds(search_params))
+        return "".join(
+            "%d\n" % bug_id
+            for bug_id in getUtility(IBugTaskSet).searchBugIds(search_params)
+        )
 
 
 class BugsBugTaskSearchListingView(BugTaskSearchListingView):
     """Search all bug reports."""
 
-    columns_to_show = ["id", "summary", "bugtargetdisplayname",
-                       "importance", "status", "heat"]
+    columns_to_show = [
+        "id",
+        "summary",
+        "bugtargetdisplayname",
+        "importance",
+        "status",
+        "heat",
+    ]
     schema = IFrontPageBugTaskSearch
     custom_widget_scope = ProjectScopeWidget
-    label = page_title = 'Search all bug reports'
+    label = page_title = "Search all bug reports"
 
     def initialize(self):
         """Initialize the view for the request."""
@@ -1738,16 +1843,18 @@ class BugsBugTaskSearchListingView(BugTaskSearchListingView):
         which will handle the error.
         """
         try:
-            search_target = self.widgets['scope'].getInputValue()
+            search_target = self.widgets["scope"].getInputValue()
         except InputErrors:
-            query_string = self.request['QUERY_STRING']
+            query_string = self.request["QUERY_STRING"]
             bugs_url = "%s?%s" % (canonical_url(self.context), query_string)
             self.request.response.redirect(bugs_url)
         else:
             if search_target is not None:
-                query_string = self.request['QUERY_STRING']
+                query_string = self.request["QUERY_STRING"]
                 search_url = "%s/+bugs?%s" % (
-                    canonical_url(search_target), query_string)
+                    canonical_url(search_target),
+                    query_string,
+                )
                 self.request.response.redirect(search_url)
 
 
@@ -1762,23 +1869,33 @@ class BugTaskExpirableListingView(BugTaskSearchListingView):
     @property
     def columns_to_show(self):
         """Show the columns that summarise expirable bugs."""
-        if (IDistribution.providedBy(self.context)
-            or IDistroSeries.providedBy(self.context)):
+        if IDistribution.providedBy(self.context) or IDistroSeries.providedBy(
+            self.context
+        ):
             return [
-                'id', 'summary', 'packagename', 'date_last_updated', 'heat']
+                "id",
+                "summary",
+                "packagename",
+                "date_last_updated",
+                "heat",
+            ]
         else:
-            return ['id', 'summary', 'date_last_updated', 'heat']
+            return ["id", "summary", "date_last_updated", "heat"]
 
     def search(self):
         """Return an `ITableBatchNavigator` for the expirable bugtasks."""
         bugtaskset = getUtility(IBugTaskSet)
         bugtasks = bugtaskset.findExpirableBugTasks(
-            user=self.user, target=self.context, min_days_old=0)
+            user=self.user, target=self.context, min_days_old=0
+        )
         return BugListingBatchNavigator(
-            bugtasks, self.request, columns_to_show=self.columns_to_show,
-            size=config.malone.buglist_batch_size)
+            bugtasks,
+            self.request,
+            columns_to_show=self.columns_to_show,
+            size=config.malone.buglist_batch_size,
+        )
 
-    page_title = 'Expirable bugs'
+    page_title = "Expirable bugs"
 
     @property
     def label(self):
@@ -1790,7 +1907,7 @@ class BugTaskExpirableListingView(BugTaskSearchListingView):
 class BugNominationsView(BugTaskSearchListingView):
     """View for accepting/declining bug nominations."""
 
-    page_title = 'Nominated bugs'
+    page_title = "Nominated bugs"
 
     @property
     def label(self):
@@ -1804,7 +1921,10 @@ class BugNominationsView(BugTaskSearchListingView):
             main_context = self.context.product
         else:
             raise AssertionError(
-                'Unknown nomination target: %r' % self.context)
+                "Unknown nomination target: %r" % self.context
+            )
         return BugTaskSearchListingView.search(
-            self, context=main_context,
-            extra_params=dict(nominated_for=self.context))
+            self,
+            context=main_context,
+            extra_params=dict(nominated_for=self.context),
+        )
diff --git a/lib/lp/bugs/browser/bugmessage.py b/lib/lp/bugs/browser/bugmessage.py
index 1ba55da..b0267a1 100644
--- a/lib/lp/bugs/browser/bugmessage.py
+++ b/lib/lp/bugs/browser/bugmessage.py
@@ -4,8 +4,8 @@
 """IBugMessage-related browser view classes."""
 
 __all__ = [
-    'BugMessageAddFormView',
-    ]
+    "BugMessageAddFormView",
+]
 
 from io import BytesIO
 
@@ -13,10 +13,7 @@ from zope.component import getUtility
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.widgets import TextAreaWidget
 
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
 from lp.bugs.browser.bugattachment import BugAttachmentContentCheck
 from lp.bugs.interfaces.bugmessage import IBugMessageAddForm
 from lp.bugs.interfaces.bugwatch import IBugWatchSet
@@ -30,13 +27,14 @@ class BugMessageAddFormView(LaunchpadFormView, BugAttachmentContentCheck):
     initial_focus_widget = None
 
     custom_widget_comment = CustomWidgetFactory(
-        TextAreaWidget, cssClass='comment-text')
+        TextAreaWidget, cssClass="comment-text"
+    )
 
     page_title = "Add a comment or attachment"
 
     @property
     def label(self):
-        return 'Add a comment or attachment to bug #%d' % self.context.bug.id
+        return "Add a comment or attachment to bug #%d" % self.context.bug.id
 
     @property
     def initial_values(self):
@@ -54,57 +52,61 @@ class BugMessageAddFormView(LaunchpadFormView, BugAttachmentContentCheck):
         # Ensure either a comment or filecontent was provide, but only
         # if no errors have already been noted.
         if len(self.errors) == 0:
-            comment = data.get('comment') or ''
-            filecontent = data.get('filecontent', None)
+            comment = data.get("comment") or ""
+            filecontent = data.get("filecontent", None)
             if not comment.strip() and not filecontent:
-                self.addError("Either a comment or attachment "
-                              "must be provided.")
+                self.addError(
+                    "Either a comment or attachment " "must be provided."
+                )
 
-    @action("Post Comment", name='save')
+    @action("Post Comment", name="save")
     def save_action(self, action, data):
         """Add the comment and/or attachment."""
 
         bug = self.context.bug
 
         # Subscribe to this bug if the checkbox exists and was selected
-        if data.get('email_me'):
+        if data.get("email_me"):
             bug.subscribe(self.user, self.user)
 
         # XXX: Bjorn Tillenius 2005-06-16:
         # Write proper FileUpload field and widget instead of this hack.
-        file_ = self.request.form.get(self.widgets['filecontent'].name)
+        file_ = self.request.form.get(self.widgets["filecontent"].name)
 
         message = None
-        if data['comment'] or file_:
-            bugwatch_id = data.get('bugwatch_id')
+        if data["comment"] or file_:
+            bugwatch_id = data.get("bugwatch_id")
             if bugwatch_id is not None:
                 bugwatch = getUtility(IBugWatchSet).get(bugwatch_id)
             else:
                 bugwatch = None
-            message = bug.newMessage(subject=data.get('subject'),
-                                     content=data['comment'],
-                                     owner=self.user,
-                                     bugwatch=bugwatch)
+            message = bug.newMessage(
+                subject=data.get("subject"),
+                content=data["comment"],
+                owner=self.user,
+                bugwatch=bugwatch,
+            )
 
             # A blank comment with only a subect line is always added
             # when the user attaches a file, so show the add comment
             # feedback message only when the user actually added a
             # comment.
-            if data['comment']:
+            if data["comment"]:
                 self.request.response.addNotification(
-                    "Thank you for your comment.")
+                    "Thank you for your comment."
+                )
 
         self.next_url = canonical_url(self.context)
         if file_:
 
             # Slashes in filenames cause problems, convert them to dashes
             # instead.
-            filename = file_.filename.replace('/', '-')
+            filename = file_.filename.replace("/", "-")
 
             # if no description was given use the converted filename
             file_description = None
-            if 'attachment_description' in data:
-                file_description = data['attachment_description']
+            if "attachment_description" in data:
+                file_description = data["attachment_description"]
             if not file_description:
                 file_description = filename
 
@@ -116,24 +118,33 @@ class BugMessageAddFormView(LaunchpadFormView, BugAttachmentContentCheck):
             # guess is wrong.
             patch_flag_consistent = (
                 self.attachmentTypeConsistentWithContentType(
-                    data['patch'], filename, data['filecontent']))
+                    data["patch"], filename, data["filecontent"]
+                )
+            )
             if not patch_flag_consistent:
                 guessed_type = self.guessContentType(
-                    filename, data['filecontent'])
-                is_patch = guessed_type == 'text/x-diff'
+                    filename, data["filecontent"]
+                )
+                is_patch = guessed_type == "text/x-diff"
             else:
-                is_patch = data['patch']
+                is_patch = data["patch"]
             attachment = bug.addAttachment(
-                owner=self.user, data=BytesIO(data['filecontent']),
-                filename=filename, description=file_description,
-                comment=message, is_patch=is_patch)
+                owner=self.user,
+                data=BytesIO(data["filecontent"]),
+                filename=filename,
+                description=file_description,
+                comment=message,
+                is_patch=is_patch,
+            )
 
             if not patch_flag_consistent:
                 self.next_url = self.nextUrlForInconsistentPatchFlags(
-                    attachment)
+                    attachment
+                )
 
             self.request.response.addNotification(
-                "Attachment %s added to bug." % filename)
+                "Attachment %s added to bug." % filename
+            )
 
     def shouldShowEmailMeWidget(self):
         """Should the subscribe checkbox be shown?"""
diff --git a/lib/lp/bugs/browser/bugnomination.py b/lib/lp/bugs/browser/bugnomination.py
index 6a61931..7319d28 100644
--- a/lib/lp/bugs/browser/bugnomination.py
+++ b/lib/lp/bugs/browser/bugnomination.py
@@ -4,31 +4,23 @@
 """Browser view classes related to bug nominations."""
 
 __all__ = [
-    'BugNominationContextMenu',
-    'BugNominationView',
-    'BugNominationEditView',
-    'BugNominationTableRowView']
+    "BugNominationContextMenu",
+    "BugNominationView",
+    "BugNominationEditView",
+    "BugNominationTableRowView",
+]
 
 from zope.component import getUtility
 from zope.interface import Interface
 
 from lp import _
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
 from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget
 from lp.bugs.browser.bug import BugContextMenu
-from lp.bugs.interfaces.bugnomination import (
-    IBugNomination,
-    IBugNominationForm,
-    )
+from lp.bugs.interfaces.bugnomination import IBugNomination, IBugNominationForm
 from lp.bugs.interfaces.cve import ICveSet
 from lp.services.features import getFeatureFlag
-from lp.services.webapp import (
-    canonical_url,
-    LaunchpadView,
-    )
+from lp.services.webapp import LaunchpadView, canonical_url
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.interfaces import ILaunchBag
 
@@ -46,16 +38,16 @@ class BugNominationView(LaunchpadFormView):
     def initialize(self):
         LaunchpadFormView.initialize(self)
         # Update the submit label based on the user's permission.
-        submit_action = self.__class__.actions.byname['actions.submit']
+        submit_action = self.__class__.actions.byname["actions.submit"]
         if self.userCanTarget():
             submit_action.label = _("Target")
         elif self.userCanNominate():
             submit_action.label = _("Nominate")
         else:
             self.request.response.addErrorNotification(
-                "You do not have permission to nominate this bug.")
-            self.request.response.redirect(
-                canonical_url(self.current_bugtask))
+                "You do not have permission to nominate this bug."
+            )
+            self.request.response.redirect(canonical_url(self.current_bugtask))
 
     @property
     def label(self):
@@ -72,21 +64,18 @@ class BugNominationView(LaunchpadFormView):
 
     def userCanTarget(self):
         """Can the current user target the bug to a series?"""
-        return (
-            self.current_bugtask.userHasDriverPrivileges(self.user)
-            or (getFeatureFlag('bugs.nominations.bug_supervisors_can_target')
-                and self.current_bugtask.userHasBugSupervisorPrivileges(
-                    self.user)))
+        return self.current_bugtask.userHasDriverPrivileges(self.user) or (
+            getFeatureFlag("bugs.nominations.bug_supervisors_can_target")
+            and self.current_bugtask.userHasBugSupervisorPrivileges(self.user)
+        )
 
     def userCanNominate(self):
         """Can the current user nominate the bug for a series?"""
-        return self.current_bugtask.userHasBugSupervisorPrivileges(
-            self.user)
+        return self.current_bugtask.userHasBugSupervisorPrivileges(self.user)
 
     def userCanChangeDriver(self):
         """Can the current user set the release management team?"""
-        return check_permission(
-            "launchpad.Edit", self.getReleaseContext())
+        return check_permission("launchpad.Edit", self.getReleaseContext())
 
     def getReleaseManager(self):
         """Return the IPerson or ITeam that does release management."""
@@ -111,25 +100,27 @@ class BugNominationView(LaunchpadFormView):
 
         for series in nominatable_series:
             nomination = self.context.bug.addNomination(
-                target=series, owner=self.user)
+                target=series, owner=self.user
+            )
 
             # If the user has the permission to approve the nomination,
             # we approve it automatically.
             if nomination.canApprove(self.user):
                 nomination.approve(self.user)
                 approved_nominations.append(
-                    nomination.target.bugtargetdisplayname)
+                    nomination.target.bugtargetdisplayname
+                )
             else:
                 nominated_series.append(series.bugtargetdisplayname)
 
         if approved_nominations:
             self.request.response.addNotification(
-                "Targeted bug to: %s" %
-                ", ".join(approved_nominations))
+                "Targeted bug to: %s" % ", ".join(approved_nominations)
+            )
         if nominated_series:
             self.request.response.addNotification(
-                "Added nominations for: %s" %
-                ", ".join(nominated_series))
+                "Added nominations for: %s" % ", ".join(nominated_series)
+            )
 
     @property
     def next_url(self):
@@ -149,10 +140,10 @@ class BugNominationTableRowView(LaunchpadView):
 
     def getNominationEditLink(self):
         """Return a link to the nomination edit form."""
-        return (
-            "%s/nominations/%d/+editstatus" % (
-                canonical_url(getUtility(ILaunchBag).bugtask),
-                self.context.id))
+        return "%s/nominations/%d/+editstatus" % (
+            canonical_url(getUtility(ILaunchBag).bugtask),
+            self.context.id,
+        )
 
     def getApproveDeclineLinkText(self):
         """Return a string used for the approve/decline form expander link."""
@@ -163,7 +154,8 @@ class BugNominationTableRowView(LaunchpadView):
         else:
             assert (
                 "Expected nomination to be Proposed or Declined. "
-                "Got status: %s" % self.context.status.title)
+                "Got status: %s" % self.context.status.title
+            )
 
     def userCanMakeDecisionForNomination(self):
         """Can the user approve/decline this nomination?"""
@@ -183,12 +175,14 @@ class BugNominationEditView(LaunchpadFormView):
 
     @property
     def label(self):
-        return 'Approve or decline nomination for bug #%d in %s' % (
-            self.context.bug.id, self.context.target.bugtargetdisplayname)
+        return "Approve or decline nomination for bug #%d in %s" % (
+            self.context.bug.id,
+            self.context.target.bugtargetdisplayname,
+        )
 
     @property
     def page_title(self):
-        text = 'Review nomination for %s'
+        text = "Review nomination for %s"
         return text % self.context.target.bugtargetdisplayname
 
     def initialize(self):
@@ -199,7 +193,8 @@ class BugNominationEditView(LaunchpadFormView):
     def action_url(self):
         return "%s/nominations/%d/+editstatus" % (
             canonical_url(self.current_bugtask),
-            self.context.id)
+            self.context.id,
+        )
 
     def shouldShowApproveButton(self, action):
         """Should the approve button be shown?"""
@@ -213,15 +208,17 @@ class BugNominationEditView(LaunchpadFormView):
     def approve(self, action, data):
         self.context.approve(self.user)
         self.request.response.addNotification(
-            "Approved nomination for %s" %
-                self.context.target.bugtargetdisplayname)
+            "Approved nomination for %s"
+            % self.context.target.bugtargetdisplayname
+        )
 
     @action(_("Decline"), name="decline", condition=shouldShowDeclineButton)
     def decline(self, action, data):
         self.context.decline(self.user)
         self.request.response.addNotification(
-            "Declined nomination for %s" %
-                self.context.target.bugtargetdisplayname)
+            "Declined nomination for %s"
+            % self.context.target.bugtargetdisplayname
+        )
 
     @property
     def next_url(self):
diff --git a/lib/lp/bugs/browser/bugsubscription.py b/lib/lp/bugs/browser/bugsubscription.py
index b28876c..0710d66 100644
--- a/lib/lp/bugs/browser/bugsubscription.py
+++ b/lib/lp/bugs/browser/bugsubscription.py
@@ -4,56 +4,47 @@
 """Views for BugSubscription."""
 
 __all__ = [
-    'AdvancedSubscriptionMixin',
-    'BugMuteSelfView',
-    'BugPortletSubscribersWithDetails',
-    'BugSubscriptionAddView',
-    'BugSubscriptionListView',
-    ]
+    "AdvancedSubscriptionMixin",
+    "BugMuteSelfView",
+    "BugPortletSubscribersWithDetails",
+    "BugSubscriptionAddView",
+    "BugSubscriptionListView",
+]
 
 from lazr.delegates import delegate_to
-from lazr.restful.interfaces import (
-    IJSONRequestCache,
-    IWebServiceClientRequest,
-    )
+from lazr.restful.interfaces import IJSONRequestCache, IWebServiceClientRequest
 from simplejson import dumps
 from zope import formlib
 from zope.formlib.itemswidgets import RadioWidget
 from zope.formlib.widget import CustomWidgetFactory
 from zope.schema import Choice
-from zope.schema.vocabulary import (
-    SimpleTerm,
-    SimpleVocabulary,
-    )
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
 from zope.security.proxy import removeSecurityProxy
 from zope.traversing.browser import absoluteURL
 
 from lp import _
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadFormView,
     ReturnToReferrerMixin,
-    )
+    action,
+)
 from lp.app.errors import SubscriptionPrivacyViolation
 from lp.bugs.browser.structuralsubscription import (
     expose_structural_subscription_data_to_js,
-    )
+)
 from lp.bugs.enums import BugNotificationLevel
 from lp.bugs.interfaces.bug import IBug
 from lp.bugs.interfaces.bugsubscription import IBugSubscription
 from lp.bugs.model.personsubscriptioninfo import PersonSubscriptions
 from lp.bugs.model.structuralsubscription import (
     get_structural_subscriptions_for_bug,
-    )
+)
 from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
-    canonical_url,
-    LaunchpadView,
-    )
+from lp.services.webapp import LaunchpadView, canonical_url
 from lp.services.webapp.authorization import (
     check_permission,
     precache_permission_for_objects,
-    )
+)
 from lp.services.webapp.escaping import structured
 
 
@@ -62,28 +53,30 @@ class BugSubscriptionAddView(LaunchpadFormView):
 
     schema = IBugSubscription
 
-    field_names = ['person']
+    field_names = ["person"]
 
     def setUpFields(self):
         """Set up 'person' as an input field."""
         super().setUpFields()
-        self.form_fields['person'].for_input = True
+        self.form_fields["person"].for_input = True
 
-    @action('Subscribe user', name='add')
+    @action("Subscribe user", name="add")
     def add_action(self, action, data):
-        person = data['person']
+        person = data["person"]
         try:
             self.context.bug.subscribe(
-                person, self.user, suppress_notify=False)
+                person, self.user, suppress_notify=False
+            )
         except SubscriptionPrivacyViolation as error:
-            self.setFieldError('person', str(error))
+            self.setFieldError("person", str(error))
         else:
             if person.is_team:
-                message = '%s team has been subscribed to this bug.'
+                message = "%s team has been subscribed to this bug."
             else:
-                message = '%s has been subscribed to this bug.'
+                message = "%s has been subscribed to this bug."
             self.request.response.addInfoNotification(
-                message % person.displayname)
+                message % person.displayname
+            )
 
     @property
     def next_url(self):
@@ -93,7 +86,7 @@ class BugSubscriptionAddView(LaunchpadFormView):
 
     @property
     def label(self):
-        return 'Subscribe someone else to bug #%i' % self.context.bug.id
+        return "Subscribe someone else to bug #%i" % self.context.bug.id
 
     page_title = label
 
@@ -122,57 +115,68 @@ class AdvancedSubscriptionMixin:
         # form. The BugNotificationLevel descriptions are too generic.
         bug_notification_level_terms = [
             SimpleTerm(
-                level, level.title,
-                self._bug_notification_level_descriptions[level])
+                level,
+                level.title,
+                self._bug_notification_level_descriptions[level],
+            )
             # We reorder the items so that COMMENTS comes first.
-            for level in sorted(BugNotificationLevel.items, reverse=True)]
+            for level in sorted(BugNotificationLevel.items, reverse=True)
+        ]
         bug_notification_vocabulary = SimpleVocabulary(
-            bug_notification_level_terms)
+            bug_notification_level_terms
+        )
 
         if self.current_user_subscription is not None:
             default_value = (
-                self.current_user_subscription.bug_notification_level)
+                self.current_user_subscription.bug_notification_level
+            )
         else:
             default_value = BugNotificationLevel.COMMENTS
 
         bug_notification_level_field = Choice(
-            __name__='bug_notification_level', title=_("Tell me when"),
-            vocabulary=bug_notification_vocabulary, required=True,
-            default=default_value)
+            __name__="bug_notification_level",
+            title=_("Tell me when"),
+            vocabulary=bug_notification_vocabulary,
+            required=True,
+            default=default_value,
+        )
         return bug_notification_level_field
 
     def _setUpBugNotificationLevelField(self):
         """Set up the bug_notification_level field."""
-        self.form_fields = self.form_fields.omit('bug_notification_level')
+        self.form_fields = self.form_fields.omit("bug_notification_level")
         self.form_fields += formlib.form.Fields(
-            self._bug_notification_level_field)
-        self.form_fields['bug_notification_level'].custom_widget = (
-            CustomWidgetFactory(RadioWidget))
+            self._bug_notification_level_field
+        )
+        self.form_fields[
+            "bug_notification_level"
+        ].custom_widget = CustomWidgetFactory(RadioWidget)
 
 
-class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
-                                       ReturnToReferrerMixin,
-                                       AdvancedSubscriptionMixin):
+class BugSubscriptionSubscribeSelfView(
+    LaunchpadFormView, ReturnToReferrerMixin, AdvancedSubscriptionMixin
+):
     """A view to handle the +subscribe page for a bug."""
 
     schema = IBugSubscription
-    page_title = 'Subscription options'
+    page_title = "Subscription options"
 
     # A mapping of BugNotificationLevel values to descriptions to be
     # shown on the +subscribe page.
     _bug_notification_level_descriptions = {
         BugNotificationLevel.COMMENTS: (
-            "a change is made to this bug or a new comment is added, "),
+            "a change is made to this bug or a new comment is added, "
+        ),
         BugNotificationLevel.METADATA: (
             "any change is made to this bug, other than a new comment "
-            "being added, or"),
-        BugNotificationLevel.LIFECYCLE: (
-            "this bug is fixed or re-opened."),
-        }
+            "being added, or"
+        ),
+        BugNotificationLevel.LIFECYCLE: ("this bug is fixed or re-opened."),
+    }
 
     @property
     def field_names(self):
-        return ['bug_notification_level']
+        return ["bug_notification_level"]
 
     @property
     def next_url(self):
@@ -216,26 +220,28 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
     @cachedproperty
     def _update_subscription_term(self):
         label = "update my current subscription"
-        return SimpleTerm(
-            'update-subscription', 'update-subscription', label)
+        return SimpleTerm("update-subscription", "update-subscription", label)
 
     @cachedproperty
     def _unsubscribe_current_user_term(self):
         if self.user_is_muted:
             label = "unmute bug mail from this bug"
         else:
-            label = 'unsubscribe me from this bug'
+            label = "unsubscribe me from this bug"
         return SimpleTerm(self.user, self.user.name, label)
 
     @cachedproperty
     def _unmute_user_term(self):
         if self.user_is_subscribed_directly:
             return SimpleTerm(
-                'update-subscription', 'update-subscription',
-                "unmute bug mail from this bug and restore my subscription")
+                "update-subscription",
+                "update-subscription",
+                "unmute bug mail from this bug and restore my subscription",
+            )
         else:
-            return SimpleTerm(self.user, self.user.name,
-                              "unmute bug mail from this bug")
+            return SimpleTerm(
+                self.user, self.user.name, "unmute bug mail from this bug"
+            )
 
     @cachedproperty
     def _subscription_field(self):
@@ -252,49 +258,65 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
                 else:
                     if self.user_is_subscribed_directly:
                         subscription_terms.append(
-                            self._update_subscription_term)
+                            self._update_subscription_term
+                        )
                     subscription_terms.insert(
-                        0, self._unsubscribe_current_user_term)
+                        0, self._unsubscribe_current_user_term
+                    )
                     self_subscribed = True
             else:
                 subscription_terms.append(
                     SimpleTerm(
-                        person, person.name,
+                        person,
+                        person.name,
                         structured(
                             'unsubscribe <a href="%s">%s</a> from this bug',
                             canonical_url(person),
-                            person.displayname).escapedtext))
+                            person.displayname,
+                        ).escapedtext,
+                    )
+                )
         if not self_subscribed:
             if not is_really_muted:
-                subscription_terms.insert(0,
+                subscription_terms.insert(
+                    0,
                     SimpleTerm(
-                        self.user, self.user.name,
-                        'subscribe me to this bug'))
+                        self.user, self.user.name, "subscribe me to this bug"
+                    ),
+                )
             elif not self.user_is_subscribed_directly:
-                subscription_terms.insert(0,
+                subscription_terms.insert(
+                    0,
                     SimpleTerm(
-                        'update-subscription', 'update-subscription',
-                        'unmute bug mail from this bug and subscribe me to '
-                        'this bug'))
+                        "update-subscription",
+                        "update-subscription",
+                        "unmute bug mail from this bug and subscribe me to "
+                        "this bug",
+                    ),
+                )
 
         # Add punctuation to the list of terms.
         if len(subscription_terms) > 1:
             for term in subscription_terms[:-1]:
-                term.title += ','
-            subscription_terms[-2].title += ' or'
-            subscription_terms[-1].title += '.'
+                term.title += ","
+            subscription_terms[-2].title += " or"
+            subscription_terms[-1].title += "."
 
         subscription_vocabulary = SimpleVocabulary(subscription_terms)
         if self.user_is_subscribed_directly or self.user_is_muted:
             default_subscription_value = self._update_subscription_term.value
         else:
             default_subscription_value = (
-                subscription_vocabulary.getTermByToken(self.user.name).value)
+                subscription_vocabulary.getTermByToken(self.user.name).value
+            )
 
         subscription_field = Choice(
-            __name__='subscription', title=_("Subscription options"),
-            vocabulary=subscription_vocabulary, required=True,
-            default=default_subscription_value)
+            __name__="subscription",
+            title=_("Subscription options"),
+            vocabulary=subscription_vocabulary,
+            required=True,
+            default=default_subscription_value,
+        )
         return subscription_field
 
     def setUpFields(self):
@@ -305,31 +327,33 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
 
         self.form_fields += formlib.form.Fields(self._subscription_field)
         self._setUpBugNotificationLevelField()
-        self.form_fields['subscription'].custom_widget = CustomWidgetFactory(
-            RadioWidget)
+        self.form_fields["subscription"].custom_widget = CustomWidgetFactory(
+            RadioWidget
+        )
 
     def setUpWidgets(self):
         """See `LaunchpadFormView`."""
         super().setUpWidgets()
-        self.widgets['subscription'].widget_class = 'bug-subscription-basic'
-        self.widgets['bug_notification_level'].widget_class = (
-            'bug-notification-level-field')
-        if (len(self.form_fields['subscription'].field.vocabulary) == 1):
+        self.widgets["subscription"].widget_class = "bug-subscription-basic"
+        self.widgets[
+            "bug_notification_level"
+        ].widget_class = "bug-notification-level-field"
+        if len(self.form_fields["subscription"].field.vocabulary) == 1:
             # We hide the subscription widget if the user isn't
             # subscribed, since we know who the subscriber is and we
             # don't need to present them with a single radio button.
-            self.widgets['subscription'].visible = False
+            self.widgets["subscription"].visible = False
         else:
             # We show the subscription widget when the user is
             # subscribed via a team, because they can either
             # subscribe theirself or unsubscribe their team.
-            self.widgets['subscription'].visible = True
+            self.widgets["subscription"].visible = True
 
         if self.user_is_subscribed_to_dupes_only:
             # If the user is subscribed via a duplicate but is not
             # directly subscribed, we hide the
             # bug_notification_level field, since it's not used.
-            self.widgets['bug_notification_level'].visible = False
+            self.widgets["bug_notification_level"].visible = False
 
     @cachedproperty
     def user_is_muted(self):
@@ -349,15 +373,17 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
     def user_is_subscribed(self):
         """Is the user subscribed to this bug?"""
         return (
-            self.user_is_subscribed_directly or
-            self.user_is_subscribed_to_dupes)
+            self.user_is_subscribed_directly
+            or self.user_is_subscribed_to_dupes
+        )
 
     @property
     def user_is_subscribed_to_dupes_only(self):
         """Is the user subscribed to this bug only via a dupe?"""
         return (
-            self.user_is_subscribed_to_dupes and
-            not self.user_is_subscribed_directly)
+            self.user_is_subscribed_to_dupes
+            and not self.user_is_subscribed_directly
+        )
 
     def shouldShowUnsubscribeFromDupesWarning(self):
         """Should we warn the user about unsubscribing and duplicates?
@@ -375,14 +401,15 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
 
         return False
 
-    @action('Continue', name='continue')
+    @action("Continue", name="continue")
     def subscribe_action(self, action, data):
         """Handle subscription requests."""
-        subscription_person = self.widgets['subscription'].getInputValue()
-        bug_notification_level = data.get('bug_notification_level', None)
+        subscription_person = self.widgets["subscription"].getInputValue()
+        bug_notification_level = data.get("bug_notification_level", None)
 
-        if (subscription_person == self._update_subscription_term.value and
-            (self.user_is_subscribed or self.user_is_muted)):
+        if subscription_person == self._update_subscription_term.value and (
+            self.user_is_subscribed or self.user_is_muted
+        ):
             if self.user_is_muted:
                 self._handleUnmute()
             if self.user_is_subscribed:
@@ -391,8 +418,9 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
                 self._handleSubscribe(level=bug_notification_level)
         elif self.user_is_muted and subscription_person == self.user:
             self._handleUnmute()
-        elif (not self.user_is_subscribed and
-            (subscription_person == self.user)):
+        elif not self.user_is_subscribed and (
+            subscription_person == self.user
+        ):
             self._handleSubscribe(bug_notification_level)
         else:
             self._handleUnsubscribe(subscription_person)
@@ -402,7 +430,8 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
         """Handle a subscribe request."""
         self.context.bug.subscribe(self.user, self.user, level=level)
         self.request.response.addNotification(
-            "You have subscribed to this bug report.")
+            "You have subscribed to this bug report."
+        )
 
     def _handleUnsubscribe(self, user):
         """Handle an unsubscribe request."""
@@ -421,12 +450,15 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
         # if the bug is private, the current user will be prevented from
         # calling methods on the main bug after they unsubscribe from it.
         unsubed_dupes = self.context.bug.unsubscribeFromDupes(
-            self.user, self.user)
+            self.user, self.user
+        )
         self.context.bug.unsubscribe(self.user, self.user)
 
         self.request.response.addNotification(
             structured(
-                self._getUnsubscribeNotification(self.user, unsubed_dupes)))
+                self._getUnsubscribeNotification(self.user, unsubed_dupes)
+            )
+        )
 
         # Because the unsubscribe above may change what the security policy
         # says about the bug, we need to clear its cache.
@@ -439,23 +471,25 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
 
     def _handleUnsubscribeOtherUser(self, user):
         """Handle unsubscribing someone other than the current user."""
-        assert user != self.user, (
-            "Expected a user other than the currently logged-in user.")
+        assert (
+            user != self.user
+        ), "Expected a user other than the currently logged-in user."
 
         # We'll also unsubscribe the other user from dupes of this bug,
         # otherwise they'll keep getting this bug's mail.
         self.context.bug.unsubscribe(user, self.user)
         unsubed_dupes = self.context.bug.unsubscribeFromDupes(user, user)
         self.request.response.addNotification(
-            structured(
-                self._getUnsubscribeNotification(user, unsubed_dupes)))
+            structured(self._getUnsubscribeNotification(user, unsubed_dupes))
+        )
 
     def _handleUpdateSubscription(self, level):
         """Handle updating a user's subscription."""
         subscription = self.current_user_subscription
         subscription.bug_notification_level = level
         self.request.response.addNotification(
-            "Your bug report subscription has been updated.")
+            "Your bug report subscription has been updated."
+        )
 
     def _getUnsubscribeNotification(self, user, unsubed_dupes):
         """Construct and return the unsubscribe-from-bug feedback message.
@@ -467,7 +501,8 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
         current_bug = self.context.bug
         current_user = self.user
         unsubed_dupes_msg_fragment = self._getUnsubscribedDupesMsgFragment(
-            unsubed_dupes)
+            unsubed_dupes
+        )
 
         if user == current_user:
             # Consider that the current user may have been "locked out"
@@ -478,17 +513,23 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
                 # special-casing needed.
                 return structured(
                     "You have been unsubscribed from bug %s%s.",
-                    current_bug.id, unsubed_dupes_msg_fragment).escapedtext
+                    current_bug.id,
+                    unsubed_dupes_msg_fragment,
+                ).escapedtext
             else:
                 return structured(
                     "You have been unsubscribed from bug %s%s. You no "
                     "longer have access to this private bug.",
-                    current_bug.id, unsubed_dupes_msg_fragment).escapedtext
+                    current_bug.id,
+                    unsubed_dupes_msg_fragment,
+                ).escapedtext
         else:
             return structured(
                 "%s has been unsubscribed from bug %s%s.",
-                user.displayname, current_bug.id,
-                unsubed_dupes_msg_fragment).escapedtext
+                user.displayname,
+                current_bug.id,
+                unsubed_dupes_msg_fragment,
+            ).escapedtext
 
     def _getUnsubscribedDupesMsgFragment(self, unsubed_dupes):
         """Return the duplicates fragment of the unsubscription notification.
@@ -501,13 +542,18 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
 
         dupe_links = []
         for unsubed_dupe in unsubed_dupes:
-            dupe_links.append(structured(
-                '<a href="%s" title="%s">#%s</a>',
-                canonical_url(unsubed_dupe), unsubed_dupe.title,
-                unsubed_dupe.id))
+            dupe_links.append(
+                structured(
+                    '<a href="%s" title="%s">#%s</a>',
+                    canonical_url(unsubed_dupe),
+                    unsubed_dupe.title,
+                    unsubed_dupe.id,
+                )
+            )
         # We can't current join structured()s, so do it manually.
         dupe_links_string = structured(
-            ", ".join(['%s'] * len(dupe_links)), *dupe_links)
+            ", ".join(["%s"] * len(dupe_links)), *dupe_links
+        )
 
         num_dupes = len(unsubed_dupes)
         if num_dupes > 1:
@@ -518,8 +564,10 @@ class BugSubscriptionSubscribeSelfView(LaunchpadFormView,
         return structured(
             " and %(num_dupes)s duplicate%(plural_suffix)s "
             "(%(dupe_links_string)s)",
-            num_dupes=num_dupes, plural_suffix=plural_suffix,
-            dupe_links_string=dupe_links_string)
+            num_dupes=num_dupes,
+            plural_suffix=plural_suffix,
+            dupe_links_string=dupe_links_string,
+        )
 
 
 class BugPortletSubscribersWithDetails(LaunchpadView):
@@ -551,21 +599,23 @@ class BugPortletSubscribersWithDetails(LaunchpadView):
             # The security adaptor will do the job also but we don't want or
             # need the expense of running several complex SQL queries.
             precache_permission_for_objects(
-                        self.request, 'launchpad.LimitedView', [person])
+                self.request, "launchpad.LimitedView", [person]
+            )
             subscriber = {
-                'name': person.name,
-                'display_name': person.displayname,
-                'web_link': canonical_url(person, rootsite='mainsite'),
-                'self_link': absoluteURL(person, self.api_request),
-                'is_team': person.is_team,
-                'can_edit': can_edit,
-                'display_subscribed_by': subscription.display_subscribed_by,
-                }
+                "name": person.name,
+                "display_name": person.displayname,
+                "web_link": canonical_url(person, rootsite="mainsite"),
+                "self_link": absoluteURL(person, self.api_request),
+                "is_team": person.is_team,
+                "can_edit": can_edit,
+                "display_subscribed_by": subscription.display_subscribed_by,
+            }
             record = {
-                'subscriber': subscriber,
-                'subscription_level': str(
-                    removeSecurityProxy(subscription.bug_notification_level)),
-                }
+                "subscriber": subscriber,
+                "subscription_level": str(
+                    removeSecurityProxy(subscription.bug_notification_level)
+                ),
+            }
             data.append(record)
         return data
 
@@ -581,7 +631,8 @@ class BugPortletSubscribersWithDetails(LaunchpadView):
         include_private = self.user is not None
         if include_private:
             precache_permission_for_objects(
-                self.request, 'launchpad.LimitedView', others)
+                self.request, "launchpad.LimitedView", others
+            )
         for person in others:
             if person == self.user:
                 # Skip the current user viewing the page,
@@ -590,17 +641,17 @@ class BugPortletSubscribersWithDetails(LaunchpadView):
                 # Do not include private teams if there's no logged in user.
                 continue
             subscriber = {
-                'name': person.name,
-                'display_name': person.displayname,
-                'web_link': canonical_url(person, rootsite='mainsite'),
-                'self_link': absoluteURL(person, self.api_request),
-                'is_team': person.is_team,
-                'can_edit': False,
-                }
+                "name": person.name,
+                "display_name": person.displayname,
+                "web_link": canonical_url(person, rootsite="mainsite"),
+                "self_link": absoluteURL(person, self.api_request),
+                "is_team": person.is_team,
+                "can_edit": False,
+            }
             record = {
-                'subscriber': subscriber,
-                'subscription_level': 'Maybe',
-                }
+                "subscriber": subscriber,
+                "subscription_level": "Maybe",
+            }
             data.append(record)
         return data
 
@@ -610,11 +661,11 @@ class BugPortletSubscribersWithDetails(LaunchpadView):
 
     def render(self):
         """Override the default render() to return only JSON."""
-        self.request.response.setHeader('content-type', 'application/json')
+        self.request.response.setHeader("content-type", "application/json")
         return self.subscriber_data_js
 
 
-@delegate_to(IBugSubscription, context='subscription')
+@delegate_to(IBugSubscription, context="subscription")
 class SubscriptionAttrDecorator:
     """A BugSubscription with added attributes for HTML/JS."""
 
@@ -623,7 +674,7 @@ class SubscriptionAttrDecorator:
 
     @property
     def css_name(self):
-        return 'subscriber-%s' % self.subscription.person.id
+        return "subscriber-%s" % self.subscription.person.id
 
 
 class BugSubscriptionListView(LaunchpadView):
@@ -631,17 +682,18 @@ class BugSubscriptionListView(LaunchpadView):
 
     def initialize(self):
         super().initialize()
-        subscriptions = list(get_structural_subscriptions_for_bug(
-            self.context.bug, self.user))
+        subscriptions = list(
+            get_structural_subscriptions_for_bug(self.context.bug, self.user)
+        )
         expose_structural_subscription_data_to_js(
-            self.context, self.request, self.user, subscriptions)
-        subscriptions_info = PersonSubscriptions(
-                self.user, self.context.bug)
+            self.context, self.request, self.user, subscriptions
+        )
+        subscriptions_info = PersonSubscriptions(self.user, self.context.bug)
         subdata, references = subscriptions_info.getDataForClient()
         cache = IJSONRequestCache(self.request).objects
         cache.update(references)
-        cache['bug_subscription_info'] = subdata
-        cache['bug_is_private'] = self.context.bug.private
+        cache["bug_subscription_info"] = subdata
+        cache["bug_is_private"] = self.context.bug.private
 
     @property
     def label(self):
@@ -675,18 +727,24 @@ class BugMuteSelfView(LaunchpadFormView):
         self.is_muted = self.context.bug.isMuted(self.user)
         super().initialize()
 
-    @action('Mute bug mail',
-            name='mute',
-            condition=lambda form, action: not form.is_muted)
+    @action(
+        "Mute bug mail",
+        name="mute",
+        condition=lambda form, action: not form.is_muted,
+    )
     def mute_action(self, action, data):
         self.context.bug.mute(self.user, self.user)
         self.request.response.addInfoNotification(
-            "Mail for bug #%s has been muted." % self.context.bug.id)
+            "Mail for bug #%s has been muted." % self.context.bug.id
+        )
 
-    @action('Unmute bug mail',
-            name='unmute',
-            condition=lambda form, action: form.is_muted)
+    @action(
+        "Unmute bug mail",
+        name="unmute",
+        condition=lambda form, action: form.is_muted,
+    )
     def unmute_action(self, action, data):
         self.context.bug.unmute(self.user, self.user)
         self.request.response.addInfoNotification(
-            "Mail for bug #%s has been unmuted." % self.context.bug.id)
+            "Mail for bug #%s has been unmuted." % self.context.bug.id
+        )
diff --git a/lib/lp/bugs/browser/bugsubscriptionfilter.py b/lib/lp/bugs/browser/bugsubscriptionfilter.py
index e12219f..6e41c8d 100644
--- a/lib/lp/bugs/browser/bugsubscriptionfilter.py
+++ b/lib/lp/bugs/browser/bugsubscriptionfilter.py
@@ -5,16 +5,13 @@
 
 __all__ = [
     "BugSubscriptionFilterView",
-    ]
+]
 
 
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.widgets import TextWidget
 
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadEditFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadEditFormView, action
 from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget
 from lp.bugs.browser.bugsubscription import AdvancedSubscriptionMixin
 from lp.bugs.browser.widgets.bug import BugTagsFrozenSetWidget
@@ -22,23 +19,22 @@ from lp.bugs.enums import BugNotificationLevel
 from lp.bugs.interfaces.bugsubscriptionfilter import IBugSubscriptionFilter
 from lp.services.helpers import english_list
 from lp.services.propertycache import cachedproperty
-from lp.services.webapp.publisher import (
-    canonical_url,
-    LaunchpadView,
-    )
+from lp.services.webapp.publisher import LaunchpadView, canonical_url
 
 
 def bug_notification_level_description_mapping(displayname):
     return {
         BugNotificationLevel.LIFECYCLE: (
-            "%s is fixed or re-opened." % displayname).capitalize(),
+            "%s is fixed or re-opened." % displayname
+        ).capitalize(),
         BugNotificationLevel.METADATA: (
             "Any change is made to %s, other than a new "
-            "comment being added." % displayname),
+            "comment being added." % displayname
+        ),
         BugNotificationLevel.COMMENTS: (
-            "A change is made or a new comment is added to %s."
-            % displayname),
-        }
+            "A change is made or a new comment is added to %s." % displayname
+        ),
+    }
 
 
 class BugSubscriptionFilterView(LaunchpadView):
@@ -72,9 +68,15 @@ class BugSubscriptionFilterView(LaunchpadView):
     def _add_english_condition(self, conditions, variable, description):
         if len(variable) > 0:
             conditions.append(
-                "the %s is %s" % (description, english_list(
-                    (kind.title for kind in sorted(variable)),
-                    conjunction="or")))
+                "the %s is %s"
+                % (
+                    description,
+                    english_list(
+                        (kind.title for kind in sorted(variable)),
+                        conjunction="or",
+                    ),
+                )
+            )
 
     @property
     def conditions(self):
@@ -82,27 +84,34 @@ class BugSubscriptionFilterView(LaunchpadView):
         conditions = []
         bug_notification_level = self.context.bug_notification_level
         if bug_notification_level < BugNotificationLevel.COMMENTS:
-            mapping = bug_notification_level_description_mapping(
-                'the bug')
-            conditions.append(
-                mapping[bug_notification_level].lower()[:-1])
+            mapping = bug_notification_level_description_mapping("the bug")
+            conditions.append(mapping[bug_notification_level].lower()[:-1])
         self._add_english_condition(
-            conditions, self.context.statuses, 'status')
+            conditions, self.context.statuses, "status"
+        )
         self._add_english_condition(
-            conditions, self.context.importances, 'importance')
+            conditions, self.context.importances, "importance"
+        )
         tags = self.context.tags
         if len(tags) > 0:
             conditions.append(
-                "the bug is tagged with %s" % english_list(
-                    sorted(tags), conjunction=(
-                        "and" if self.context.find_all_tags else "or")))
+                "the bug is tagged with %s"
+                % english_list(
+                    sorted(tags),
+                    conjunction=(
+                        "and" if self.context.find_all_tags else "or"
+                    ),
+                )
+            )
         self._add_english_condition(
-            conditions, self.context.information_types, 'information type')
+            conditions, self.context.information_types, "information type"
+        )
         return conditions
 
 
-class BugSubscriptionFilterEditViewBase(LaunchpadEditFormView,
-                                        AdvancedSubscriptionMixin):
+class BugSubscriptionFilterEditViewBase(
+    LaunchpadEditFormView, AdvancedSubscriptionMixin
+):
     """Base class for edit or create views of `IBugSubscriptionFilter`."""
 
     schema = IBugSubscriptionFilter
@@ -113,15 +122,17 @@ class BugSubscriptionFilterEditViewBase(LaunchpadEditFormView,
         "information_types",
         "tags",
         "find_all_tags",
-        )
+    )
 
     custom_widget_description = CustomWidgetFactory(
-        TextWidget, displayWidth=50)
+        TextWidget, displayWidth=50
+    )
     custom_widget_statuses = LabeledMultiCheckBoxWidget
     custom_widget_importances = LabeledMultiCheckBoxWidget
     custom_widget_information_types = LabeledMultiCheckBoxWidget
     custom_widget_tags = CustomWidgetFactory(
-        BugTagsFrozenSetWidget, displayWidth=35)
+        BugTagsFrozenSetWidget, displayWidth=35
+    )
 
     # Define in concrete subclass to be the target of the
     # structural subscription that we are modifying.
@@ -133,7 +144,8 @@ class BugSubscriptionFilterEditViewBase(LaunchpadEditFormView,
     @cachedproperty
     def _bug_notification_level_descriptions(self):
         return bug_notification_level_description_mapping(
-            'a bug in %s' % self.target.displayname)
+            "a bug in %s" % self.target.displayname
+        )
 
     def setUpFields(self):
         """Set up fields for form.
@@ -145,14 +157,12 @@ class BugSubscriptionFilterEditViewBase(LaunchpadEditFormView,
     @property
     def next_url(self):
         """Return to the user's structural subscriptions page."""
-        return canonical_url(
-            self.user, view_name="+structural-subscriptions")
+        return canonical_url(self.user, view_name="+structural-subscriptions")
 
     cancel_url = next_url
 
 
-class BugSubscriptionFilterEditView(
-    BugSubscriptionFilterEditViewBase):
+class BugSubscriptionFilterEditView(BugSubscriptionFilterEditViewBase):
     """Edit view for `IBugSubscriptionFilter`.
 
     :ivar context: A provider of `IBugSubscriptionFilter`.
@@ -180,8 +190,7 @@ class BugSubscriptionFilterEditView(
         return self.context.structural_subscription.target
 
 
-class BugSubscriptionFilterCreateView(
-    BugSubscriptionFilterEditViewBase):
+class BugSubscriptionFilterCreateView(BugSubscriptionFilterEditViewBase):
     """View to create a new `IBugSubscriptionFilter`.
 
     :ivar context: A provider of `IStructuralSubscription`.
diff --git a/lib/lp/bugs/browser/bugsupervisor.py b/lib/lp/bugs/browser/bugsupervisor.py
index 44040f2..731eb69 100644
--- a/lib/lp/bugs/browser/bugsupervisor.py
+++ b/lib/lp/bugs/browser/bugsupervisor.py
@@ -4,16 +4,13 @@
 """Browser view for bug supervisor."""
 
 __all__ = [
-    'BugSupervisorEditView',
-    ]
+    "BugSupervisorEditView",
+]
 
 from lazr.restful.interface import copy_field
 from zope.interface import Interface
 
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadEditFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadEditFormView, action
 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
 from lp.services.webapp.escaping import structured
 from lp.services.webapp.publisher import canonical_url
@@ -25,20 +22,22 @@ class BugSupervisorEditSchema(Interface):
     This is necessary to make an editable field for bug supervisor as it is
     defined as read-only in the interface to prevent setting it directly.
     """
+
     bug_supervisor = copy_field(
-        IHasBugSupervisor['bug_supervisor'], readonly=False)
+        IHasBugSupervisor["bug_supervisor"], readonly=False
+    )
 
 
 class BugSupervisorEditView(LaunchpadEditFormView):
     """Browser view class for editing the bug supervisor."""
 
     schema = BugSupervisorEditSchema
-    field_names = ['bug_supervisor']
+    field_names = ["bug_supervisor"]
 
     @property
     def label(self):
         """The form label."""
-        return 'Edit bug supervisor for %s' % self.context.displayname
+        return "Edit bug supervisor for %s" % self.context.displayname
 
     @property
     def page_title(self):
@@ -57,14 +56,15 @@ class BugSupervisorEditView(LaunchpadEditFormView):
 
     cancel_url = next_url
 
-    @action('Change', name='change')
+    @action("Change", name="change")
     def change_action(self, action, data):
         """Redirect to the target page with a success message."""
         self.updateContextFromData(data)
         if self.context.bug_supervisor is None:
             message = (
                 "Successfully cleared the bug supervisor. "
-                "You can set the bug supervisor again at any time.")
+                "You can set the bug supervisor again at any time."
+            )
         else:
-            message = structured('Bug supervisor privilege granted.')
+            message = structured("Bug supervisor privilege granted.")
         self.request.response.addNotification(message)
diff --git a/lib/lp/bugs/browser/bugtarget.py b/lib/lp/bugs/browser/bugtarget.py
index 1802a0e..5633d0a 100644
--- a/lib/lp/bugs/browser/bugtarget.py
+++ b/lib/lp/bugs/browser/bugtarget.py
@@ -15,16 +15,13 @@ __all__ = [
     "ProductConfigureBugTrackerView",
     "ProjectGroupFileBugGuidedView",
     "product_to_productbugconfiguration",
-    ]
+]
 
+import http.client
 from datetime import datetime
 from functools import partial
-import http.client
 from io import BytesIO
-from urllib.parse import (
-    quote,
-    urlencode,
-    )
+from urllib.parse import quote, urlencode
 
 from lazr.restful.interface import copy_field
 from lazr.restful.interfaces import IJSONRequestCache
@@ -35,15 +32,8 @@ from zope.component import getUtility
 from zope.formlib.form import Fields
 from zope.formlib.interfaces import InputErrors
 from zope.formlib.widget import CustomWidgetFactory
-from zope.formlib.widgets import (
-    TextAreaWidget,
-    TextWidget,
-    )
-from zope.interface import (
-    alsoProvides,
-    implementer,
-    Interface,
-    )
+from zope.formlib.widgets import TextAreaWidget, TextWidget
+from zope.interface import Interface, alsoProvides, implementer
 from zope.publisher.interfaces import NotFound
 from zope.publisher.interfaces.browser import IBrowserPublisher
 from zope.schema import Choice
@@ -52,28 +42,25 @@ from zope.schema.vocabulary import SimpleVocabulary
 from zope.security.proxy import removeSecurityProxy
 
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
+    action,
     safe_action,
-    )
+)
 from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
 from lp.app.browser.stringformatter import FormattersAPI
 from lp.app.enums import (
-    InformationType,
     PRIVATE_INFORMATION_TYPES,
     PUBLIC_INFORMATION_TYPES,
+    InformationType,
     ServiceUsage,
-    )
-from lp.app.errors import (
-    NotFoundError,
-    UnexpectedFormData,
-    )
+)
+from lp.app.errors import NotFoundError, UnexpectedFormData
 from lp.app.interfaces.launchpad import (
     IHeadingContext,
     ILaunchpadCelebrities,
     ILaunchpadUsage,
-    )
+)
 from lp.app.utilities import json_dump_information_types
 from lp.app.validators.name import valid_name_pattern
 from lp.app.vocabularies import InformationTypeVocabulary
@@ -82,14 +69,11 @@ from lp.app.widgets.product import (
     GhostCheckBoxWidget,
     GhostWidget,
     ProductBugTrackerWidget,
-    )
+)
 from lp.bugs.browser.structuralsubscription import (
     expose_structural_subscription_data_to_js,
-    )
-from lp.bugs.browser.widgets.bug import (
-    BugTagsWidget,
-    LargeBugTagsWidget,
-    )
+)
+from lp.bugs.browser.widgets.bug import BugTagsWidget, LargeBugTagsWidget
 from lp.bugs.browser.widgets.bugtask import FileBugSourcePackageNameWidget
 from lp.bugs.interfaces.apportjob import IProcessApportBlobJobSource
 from lp.bugs.interfaces.bug import (
@@ -98,30 +82,30 @@ from lp.bugs.interfaces.bug import (
     IBugAddForm,
     IBugSet,
     IProjectGroupBugAddForm,
-    )
+)
 from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
 from lp.bugs.interfaces.bugtarget import (
     IBugTarget,
     IOfficialBugTagTargetPublic,
     IOfficialBugTagTargetRestricted,
-    )
+)
 from lp.bugs.interfaces.bugtask import (
+    UNRESOLVED_BUGTASK_STATUSES,
     BugTaskImportance,
     BugTaskStatus,
     IBugTaskSet,
-    UNRESOLVED_BUGTASK_STATUSES,
-    )
+)
 from lp.bugs.interfaces.bugtracker import IBugTracker
 from lp.bugs.model.bugtask import BugTask
 from lp.bugs.model.structuralsubscription import (
     get_structural_subscriptions_for_target,
-    )
+)
 from lp.bugs.utilities.filebugdataparser import FileBugData
 from lp.registry.browser.product import ProductConfigureBase
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
-    )
+)
 from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.ociproject import IOCIProject
 from lp.registry.interfaces.person import IPerson
@@ -135,43 +119,43 @@ from lp.services.features import getFeatureFlag
 from lp.services.job.interfaces.job import JobStatus
 from lp.services.librarian.browser import ProxiedLibraryFileAlias
 from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
-    canonical_url,
-    LaunchpadView,
-    urlappend,
-    )
+from lp.services.webapp import LaunchpadView, canonical_url, urlappend
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.escaping import structured
 
-
 # A simple vocabulary for the subscribe_to_existing_bug form field.
 SUBSCRIBE_TO_BUG_VOCABULARY = SimpleVocabulary.fromItems(
-    [('yes', True), ('no', False)])
+    [("yes", True), ("no", False)]
+)
 
 
 class IProductBugConfiguration(Interface):
     """A composite schema for editing bug app configuration."""
 
     bug_supervisor = copy_field(
-        IHasBugSupervisor['bug_supervisor'], readonly=False)
-    official_malone = copy_field(ILaunchpadUsage['official_malone'])
+        IHasBugSupervisor["bug_supervisor"], readonly=False
+    )
+    official_malone = copy_field(ILaunchpadUsage["official_malone"])
     enable_bug_expiration = copy_field(
-        ILaunchpadUsage['enable_bug_expiration'])
-    bugtracker = copy_field(IProduct['bugtracker'])
-    remote_product = copy_field(IProduct['remote_product'])
+        ILaunchpadUsage["enable_bug_expiration"]
+    )
+    bugtracker = copy_field(IProduct["bugtracker"])
+    remote_product = copy_field(IProduct["remote_product"])
     bug_reporting_guidelines = copy_field(
-        IBugTarget['bug_reporting_guidelines'])
+        IBugTarget["bug_reporting_guidelines"]
+    )
     bug_reported_acknowledgement = copy_field(
-        IBugTarget['bug_reported_acknowledgement'])
+        IBugTarget["bug_reported_acknowledgement"]
+    )
     enable_bugfiling_duplicate_search = copy_field(
-        IBugTarget['enable_bugfiling_duplicate_search'])
+        IBugTarget["enable_bugfiling_duplicate_search"]
+    )
 
 
 def product_to_productbugconfiguration(product):
     """Adapts an `IProduct` into an `IProductBugConfiguration`."""
-    alsoProvides(
-        removeSecurityProxy(product), IProductBugConfiguration)
+    alsoProvides(removeSecurityProxy(product), IProductBugConfiguration)
     return product
 
 
@@ -196,7 +180,7 @@ class ProductConfigureBugTrackerView(ProductConfigureBase):
             "bug_reporting_guidelines",
             "bug_reported_acknowledgement",
             "enable_bugfiling_duplicate_search",
-            ]
+        ]
         if check_permission("launchpad.Edit", self.context):
             field_names.append("bug_supervisor")
 
@@ -210,11 +194,11 @@ class ProductConfigureBugTrackerView(ProductConfigureBase):
         # JavaScript fails to activate or run. Note that the bugtracker
         # name : values are {'In Launchpad' : object, 'Somewhere else' : None
         # 'In a registered bug tracker' : IBugTracker}.
-        bugtracker = data.get('bugtracker', None)
+        bugtracker = data.get("bugtracker", None)
         if bugtracker is None or IBugTracker.providedBy(bugtracker):
-            data['enable_bug_expiration'] = False
+            data["enable_bug_expiration"] = False
 
-    @action("Change", name='change')
+    @action("Change", name="change")
     def change_action(self, action, data):
         self.updateContextFromData(data)
 
@@ -227,7 +211,8 @@ class FileBugViewBase(LaunchpadFormView):
 
     custom_widget_information_type = LaunchpadRadioWidgetWithDescription
     custom_widget_comment = CustomWidgetFactory(
-        TextAreaWidget, cssClass='comment-text')
+        TextAreaWidget, cssClass="comment-text"
+    )
     custom_widget_packagename = FileBugSourcePackageNameWidget
 
     extra_data_token = None
@@ -253,14 +238,18 @@ class FileBugViewBase(LaunchpadFormView):
         # the form is rendered during LaunchpadFormView's initialize()
         # when an action is invokved.
         cache = IJSONRequestCache(self.request)
-        cache.objects['bug_private_by_default'] = (
-            self.context.pillar.getDefaultBugInformationType() in
-            PRIVATE_INFORMATION_TYPES)
+        cache.objects["bug_private_by_default"] = (
+            self.context.pillar.getDefaultBugInformationType()
+            in PRIVATE_INFORMATION_TYPES
+        )
         json_dump_information_types(
-            cache, self.context.pillar.getAllowedBugInformationTypes())
+            cache, self.context.pillar.getAllowedBugInformationTypes()
+        )
 
         bugtask_status_data = vocabulary_to_choice_edit_items(
-            BugTaskStatus, include_description=True, css_class_prefix='status',
+            BugTaskStatus,
+            include_description=True,
+            css_class_prefix="status",
             excluded_items=[
                 BugTaskStatus.UNKNOWN,
                 BugTaskStatus.EXPIRED,
@@ -268,95 +257,122 @@ class FileBugViewBase(LaunchpadFormView):
                 BugTaskStatus.OPINION,
                 BugTaskStatus.WONTFIX,
                 BugTaskStatus.INCOMPLETE,
-                BugTaskStatus.DOESNOTEXIST])
-        cache.objects['bugtask_status_data'] = bugtask_status_data
+                BugTaskStatus.DOESNOTEXIST,
+            ],
+        )
+        cache.objects["bugtask_status_data"] = bugtask_status_data
         bugtask_importance_data = vocabulary_to_choice_edit_items(
-            BugTaskImportance, include_description=True,
-            css_class_prefix='importance',
-            excluded_items=[BugTaskImportance.UNKNOWN])
-        cache.objects['bugtask_importance_data'] = bugtask_importance_data
-        cache.objects['enable_bugfiling_duplicate_search'] = (
-            self.context.enable_bugfiling_duplicate_search)
+            BugTaskImportance,
+            include_description=True,
+            css_class_prefix="importance",
+            excluded_items=[BugTaskImportance.UNKNOWN],
+        )
+        cache.objects["bugtask_importance_data"] = bugtask_importance_data
+        cache.objects[
+            "enable_bugfiling_duplicate_search"
+        ] = self.context.enable_bugfiling_duplicate_search
 
         super().initialize()
 
-        if (self.extra_data_token is not None and
-            not self.extra_data_to_process):
+        if (
+            self.extra_data_token is not None
+            and not self.extra_data_to_process
+        ):
             # self.extra_data has been initialized in publishTraverse().
             if self.extra_data.initial_summary:
-                self.widgets['title'].setRenderedValue(
-                    self.extra_data.initial_summary)
+                self.widgets["title"].setRenderedValue(
+                    self.extra_data.initial_summary
+                )
             if self.extra_data.initial_tags:
-                self.widgets['tags'].setRenderedValue(
-                    self.extra_data.initial_tags)
+                self.widgets["tags"].setRenderedValue(
+                    self.extra_data.initial_tags
+                )
             # XXX: Bjorn Tillenius 2006-01-15:
             #      We should include more details of what will be added
             #      to the bug report.
             self.request.response.addNotification(
-                'Extra debug information will be added to the bug report'
-                ' automatically.')
+                "Extra debug information will be added to the bug report"
+                " automatically."
+            )
 
     @cachedproperty
     def redirect_ubuntu_filebug(self):
         if IDistribution.providedBy(self.context):
             bug_supervisor = self.context.bug_supervisor
-        elif (IDistributionSourcePackage.providedBy(self.context) or
-              ISourcePackage.providedBy(self.context)):
+        elif IDistributionSourcePackage.providedBy(
+            self.context
+        ) or ISourcePackage.providedBy(self.context):
             bug_supervisor = self.context.distribution.bug_supervisor
         else:
             bug_supervisor = None
 
         # Work out whether the redirect should be overidden.
         do_not_redirect = (
-            self.request.form.get('no-redirect') is not None or
-            [key for key in self.request.form.keys()
-            if 'field.actions' in key] != [] or
-            self.user.inTeam(bug_supervisor))
+            self.request.form.get("no-redirect") is not None
+            or [
+                key
+                for key in self.request.form.keys()
+                if "field.actions" in key
+            ]
+            != []
+            or self.user.inTeam(bug_supervisor)
+        )
 
         return (
-            config.malone.ubuntu_disable_filebug and
-            self.targetIsUbuntu() and
-            self.extra_data_token is None and
-            not do_not_redirect)
+            config.malone.ubuntu_disable_filebug
+            and self.targetIsUbuntu()
+            and self.extra_data_token is None
+            and not do_not_redirect
+        )
 
     @property
     def field_names(self):
         """Return the list of field names to display."""
         context = self.context
-        field_names = ['title', 'comment', 'tags']
+        field_names = ["title", "comment", "tags"]
         if self.is_bug_supervisor:
-            field_names.append('information_type')
+            field_names.append("information_type")
         else:
-            field_names.append('security_related')
-        field_names.extend([
-            'bug_already_reported_as', 'filecontent', 'patch',
-            'attachment_description', 'subscribe_to_existing_bug'])
-        if (IDistribution.providedBy(context) or
-            IDistributionSourcePackage.providedBy(context)):
-            field_names.append('packagename')
+            field_names.append("security_related")
+        field_names.extend(
+            [
+                "bug_already_reported_as",
+                "filecontent",
+                "patch",
+                "attachment_description",
+                "subscribe_to_existing_bug",
+            ]
+        )
+        if IDistribution.providedBy(
+            context
+        ) or IDistributionSourcePackage.providedBy(context):
+            field_names.append("packagename")
 
         if self.is_bug_supervisor:
             field_names.extend(
-                ['assignee', 'importance', 'milestone', 'status'])
+                ["assignee", "importance", "milestone", "status"]
+            )
 
         return field_names
 
     @property
     def default_information_type(self):
         value = self.context.pillar.getDefaultBugInformationType()
-        if (self.extra_data
+        if (
+            self.extra_data
             and self.extra_data.private
-            and value in PUBLIC_INFORMATION_TYPES):
+            and value in PUBLIC_INFORMATION_TYPES
+        ):
             value = InformationType.USERDATA
         return value
 
     @property
     def initial_values(self):
         """Give packagename a default value, if applicable."""
-        values = {'information_type': self.default_information_type}
+        values = {"information_type": self.default_information_type}
 
         if IDistributionSourcePackage.providedBy(self.context):
-            values['packagename'] = self.context.name
+            values["packagename"] = self.context.name
 
         return values
 
@@ -373,39 +389,50 @@ class FileBugViewBase(LaunchpadFormView):
     def getPackageNameFieldCSSClass(self):
         """Return the CSS class for the packagename field."""
         if self.widget_errors.get("packagename"):
-            return 'error'
+            return "error"
         else:
-            return ''
+            return ""
 
     def validate(self, data):
         """Make sure the package name, if provided, exists in the distro."""
         # The comment field is only required if filing a new bug.
         if self.submit_bug_action.submitted():
-            comment = data.get('comment')
+            comment = data.get("comment")
             # The widget only exposes the error message. The private
             # attr contains the real error.
-            widget_error = self.widgets.get('comment')._error
+            widget_error = self.widgets.get("comment")._error
             if widget_error and isinstance(widget_error.errors, TooLong):
-                self.setFieldError('comment',
-                    'The description is too long. If you have lots of '
-                    'text to add, attach a file to the bug instead.')
+                self.setFieldError(
+                    "comment",
+                    "The description is too long. If you have lots of "
+                    "text to add, attach a file to the bug instead.",
+                )
             elif not comment or widget_error is not None:
                 self.setFieldError(
-                    'comment', "Provide details about the issue.")
+                    "comment", "Provide details about the issue."
+                )
             # Check if there is an extra description, and if it is too long.
             if self.extra_data.extra_description:
-                if len("%s\n\n%s" % (
-                    comment, self.extra_data.extra_description)) > 50000:
+                if (
+                    len(
+                        "%s\n\n%s"
+                        % (comment, self.extra_data.extra_description)
+                    )
+                    > 50000
+                ):
                     self.setFieldError(
-                        'comment', 'The description and the additional '
-                        'information is too long. If you have lots of text '
-                        'to add, attach a file to the bug instead.')
+                        "comment",
+                        "The description and the additional "
+                        "information is too long. If you have lots of text "
+                        "to add, attach a file to the bug instead.",
+                    )
         # Check a bug has been selected when the user wants to
         # subscribe to an existing bug.
         elif self.this_is_my_bug_action.submitted():
-            if not data.get('bug_already_reported_as'):
-                self.setFieldError('bug_already_reported_as',
-                                   "Please choose a bug.")
+            if not data.get("bug_already_reported_as"):
+                self.setFieldError(
+                    "bug_already_reported_as", "Please choose a bug."
+                )
         else:
             # We only care about those two actions.
             pass
@@ -424,7 +451,7 @@ class FileBugViewBase(LaunchpadFormView):
                     distribution = self.context.distribution
 
                 try:
-                    if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
+                    if bool(getFeatureFlag("disclosure.dsp_picker.enabled")):
                         dsp_vocab = self.widgets.get("packagename").vocabulary
                         dsp_vocab.setDistribution(distribution)
                         dsp_vocab.getTermByToken(packagename)
@@ -433,7 +460,8 @@ class FileBugViewBase(LaunchpadFormView):
                         # vocabulary was used, so it needs secondary
                         # verification.
                         distribution.guessPublishedSourcePackageName(
-                            packagename)
+                            packagename
+                        )
                 except (LookupError, NotFoundError):
                     if distribution.series:
                         # If a distribution doesn't have any series,
@@ -443,20 +471,23 @@ class FileBugViewBase(LaunchpadFormView):
                         packagename_error = (
                             '"%s" does not exist in %s. Please choose a '
                             "different package. If you're unsure, please "
-                            'select "I don\'t know"' % (
-                                packagename, distribution.displayname))
+                            'select "I don\'t know"'
+                            % (packagename, distribution.displayname)
+                        )
                         self.setFieldError("packagename", packagename_error)
             else:
-                self.setFieldError("packagename",
-                                   "Please enter a package name")
+                self.setFieldError(
+                    "packagename", "Please enter a package name"
+                )
 
     def setUpWidgets(self):
         """Customize the onKeyPress event of the package name chooser."""
         super().setUpWidgets()
 
         if "packagename" in self.field_names:
-            self.widgets["packagename"].onKeyPress = (
-                "selectWidget('choose', event)")
+            self.widgets[
+                "packagename"
+            ].onKeyPress = "selectWidget('choose', event)"
 
     def setUpFields(self):
         """Set up the form fields. See `LaunchpadFormView`."""
@@ -464,27 +495,33 @@ class FileBugViewBase(LaunchpadFormView):
 
         if self.is_bug_supervisor:
             info_type_vocab = InformationTypeVocabulary(
-                types=self.context.pillar.getAllowedBugInformationTypes())
+                types=self.context.pillar.getAllowedBugInformationTypes()
+            )
             information_type_field = copy_field(
-                IBug['information_type'], readonly=False,
-                vocabulary=info_type_vocab)
-            self.form_fields = self.form_fields.omit('information_type')
+                IBug["information_type"],
+                readonly=False,
+                vocabulary=info_type_vocab,
+            )
+            self.form_fields = self.form_fields.omit("information_type")
             self.form_fields += Fields(information_type_field)
         else:
             security_related_field = copy_field(
-                IBug['security_related'], readonly=False)
-            self.form_fields = self.form_fields.omit('security_related')
+                IBug["security_related"], readonly=False
+            )
+            self.form_fields = self.form_fields.omit("security_related")
             self.form_fields += Fields(security_related_field)
 
         # Override the vocabulary for the subscribe_to_existing_bug
         # field.
         subscribe_field = Choice(
-            __name__='subscribe_to_existing_bug',
-            title='Subscribe to this bug',
+            __name__="subscribe_to_existing_bug",
+            title="Subscribe to this bug",
             vocabulary=SUBSCRIBE_TO_BUG_VOCABULARY,
-            required=True, default=False)
+            required=True,
+            default=False,
+        )
 
-        self.form_fields = self.form_fields.omit('subscribe_to_existing_bug')
+        self.form_fields = self.form_fields.omit("subscribe_to_existing_bug")
         self.form_fields += Fields(subscribe_field)
 
     def contextUsesMalone(self):
@@ -493,20 +530,23 @@ class FileBugViewBase(LaunchpadFormView):
         return bug_tracking_usage == ServiceUsage.LAUNCHPAD
 
     def contextAllowsNewBugs(self):
-        return (self.contextUsesMalone() and
-                self.getMainContext().getAllowedBugInformationTypes())
+        return (
+            self.contextUsesMalone()
+            and self.getMainContext().getAllowedBugInformationTypes()
+        )
 
     def shouldSelectPackageName(self):
         """Should the radio button to select a package be selected?"""
-        return (
-            self.request.form.get("field.packagename") or
-            self.initial_values.get("packagename"))
+        return self.request.form.get(
+            "field.packagename"
+        ) or self.initial_values.get("packagename")
 
     def handleSubmitBugFailure(self, action, data, errors):
         return self.showFileBugForm()
 
-    @action("Submit Bug Report", name="submit_bug",
-            failure=handleSubmitBugFailure)
+    @action(
+        "Submit Bug Report", name="submit_bug", failure=handleSubmitBugFailure
+    )
     def submit_bug_action(self, action, data):
         """Add a bug to this IBugTarget."""
         title = data["title"]
@@ -532,16 +572,21 @@ class FileBugViewBase(LaunchpadFormView):
             if IDistributionSourcePackage.providedBy(context):
                 context = context.distribution
 
-        linkified_ack = structured(FormattersAPI(
-            self.getAcknowledgementMessage(self.context)).text_to_html(
-                last_paragraph_class="last"))
+        linkified_ack = structured(
+            FormattersAPI(
+                self.getAcknowledgementMessage(self.context)
+            ).text_to_html(last_paragraph_class="last")
+        )
         notifications = [linkified_ack]
         params = CreateBugParams(
-            title=title, comment=comment, owner=self.user,
+            title=title,
+            comment=comment,
+            owner=self.user,
             information_type=information_type,
-            tags=data.get('tags'))
+            tags=data.get("tags"),
+        )
         if IDistribution.providedBy(context) and packagename:
-            if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
+            if bool(getFeatureFlag("disclosure.dsp_picker.enabled")):
                 context = packagename
             else:
                 # We don't know if the package name we got was a source or
@@ -550,88 +595,105 @@ class FileBugViewBase(LaunchpadFormView):
                 packagename = str(packagename.name)
                 try:
                     sourcepackagename = (
-                        context.guessPublishedSourcePackageName(packagename))
+                        context.guessPublishedSourcePackageName(packagename)
+                    )
                 except NotFoundError:
                     notifications.append(
                         "The package %s is not published in %s; the "
                         "bug was targeted only to the distribution."
-                        % (packagename, context.displayname))
+                        % (packagename, context.displayname)
+                    )
                     params.comment += (
                         "\r\n\r\nNote: the original reporter indicated "
                         "the bug was in package %r; however, that package "
-                        "was not published in %s." % (
-                            packagename, context.displayname))
+                        "was not published in %s."
+                        % (packagename, context.displayname)
+                    )
                 else:
                     context = context.getSourcePackage(sourcepackagename.name)
 
         extra_data = self.extra_data
         if extra_data.extra_description:
             params.comment = "%s\n\n%s" % (
-                params.comment, extra_data.extra_description)
+                params.comment,
+                extra_data.extra_description,
+            )
             notifications.append(
-                'Additional information was added to the bug description.')
+                "Additional information was added to the bug description."
+            )
 
         # Apply any extra options given by privileged users.
         if self.is_bug_supervisor:
-            if 'assignee' in data:
-                params.assignee = data['assignee']
-            if 'status' in data:
-                params.status = data['status']
-            if 'importance' in data:
-                params.importance = data['importance']
-            if 'milestone' in data:
-                params.milestone = data['milestone']
+            if "assignee" in data:
+                params.assignee = data["assignee"]
+            if "status" in data:
+                params.status = data["status"]
+            if "importance" in data:
+                params.importance = data["importance"]
+            if "milestone" in data:
+                params.milestone = data["milestone"]
 
         self.added_bug = bug = context.createBug(params)
 
         for comment in extra_data.comments:
             bug.newMessage(self.user, bug.followup_subject(), comment)
             notifications.append(
-                'A comment with additional information was added to the'
-                ' bug report.')
+                "A comment with additional information was added to the"
+                " bug report."
+            )
 
         # XXX 2007-01-19 gmb:
         #     We need to have a proper FileUpload widget rather than
         #     this rather hackish solution.
-        attachment = self.request.form.get(self.widgets['filecontent'].name)
+        attachment = self.request.form.get(self.widgets["filecontent"].name)
         if attachment or extra_data.attachments:
             # Attach all the comments to a single empty comment.
             attachment_comment = bug.newMessage(
-                owner=self.user, subject=bug.followup_subject(), content=None,
-                send_notifications=False)
+                owner=self.user,
+                subject=bug.followup_subject(),
+                content=None,
+                send_notifications=False,
+            )
 
             # Deal with attachments added in the filebug form.
             if attachment:
                 # We convert slashes in filenames to hyphens to avoid
                 # problems.
-                filename = attachment.filename.replace('/', '-')
+                filename = attachment.filename.replace("/", "-")
 
                 # If the user hasn't entered a description for the
                 # attachment we use its name.
                 file_description = None
-                if 'attachment_description' in data:
-                    file_description = data['attachment_description']
+                if "attachment_description" in data:
+                    file_description = data["attachment_description"]
                 if file_description is None:
                     file_description = filename
 
                 bug.addAttachment(
-                    owner=self.user, data=BytesIO(data['filecontent']),
-                    filename=filename, description=file_description,
-                    comment=attachment_comment, is_patch=data['patch'])
+                    owner=self.user,
+                    data=BytesIO(data["filecontent"]),
+                    filename=filename,
+                    description=file_description,
+                    comment=attachment_comment,
+                    is_patch=data["patch"],
+                )
 
                 notifications.append(
-                    'The file "%s" was attached to the bug report.'
-                    % filename)
+                    'The file "%s" was attached to the bug report.' % filename
+                )
 
             for attachment in extra_data.attachments:
                 bug.linkAttachment(
-                    owner=self.user, file_alias=attachment['file_alias'],
-                    description=attachment['description'],
+                    owner=self.user,
+                    file_alias=attachment["file_alias"],
+                    description=attachment["description"],
                     comment=attachment_comment,
-                    send_notifications=False)
+                    send_notifications=False,
+                )
                 notifications.append(
                     'The file "%s" was attached to the bug report.'
-                    % attachment['file_alias'].filename)
+                    % attachment["file_alias"].filename
+                )
 
         if extra_data.subscribers:
             # Subscribe additional subscribers to this bug
@@ -639,7 +701,8 @@ class FileBugViewBase(LaunchpadFormView):
                 valid_person_vocabulary = ValidPersonOrTeamVocabulary()
                 try:
                     person = valid_person_vocabulary.getTermByToken(
-                        subscriber).value
+                        subscriber
+                    ).value
                 except LookupError:
                     # We cannot currently pass this error up to the user, so
                     # we'll just ignore it.
@@ -647,8 +710,9 @@ class FileBugViewBase(LaunchpadFormView):
                 else:
                     bug.subscribe(person, self.user)
                     notifications.append(
-                        '%s has been subscribed to this bug.' %
-                        person.displayname)
+                        "%s has been subscribed to this bug."
+                        % person.displayname
+                    )
 
         # Give the user some feedback on the bug just opened.
         for notification in notifications:
@@ -656,44 +720,55 @@ class FileBugViewBase(LaunchpadFormView):
         if bug.information_type == InformationType.PRIVATESECURITY:
             self.request.response.addNotification(
                 structured(
-                'Security-related bugs are by default private '
-                '(visible only to their direct subscribers). '
-                'You may choose to <a href="+secrecy">publicly '
-                'disclose</a> this bug.'))
+                    "Security-related bugs are by default private "
+                    "(visible only to their direct subscribers). "
+                    'You may choose to <a href="+secrecy">publicly '
+                    "disclose</a> this bug."
+                )
+            )
         elif bug.information_type in PRIVATE_INFORMATION_TYPES:
             self.request.response.addNotification(
                 structured(
-                'This bug report has been marked private '
-                '(visible only to its direct subscribers). '
-                'You may choose to <a href="+secrecy">change this</a>.'))
+                    "This bug report has been marked private "
+                    "(visible only to its direct subscribers). "
+                    'You may choose to <a href="+secrecy">change this</a>.'
+                )
+            )
 
         self.request.response.redirect(canonical_url(bug.bugtasks[0]))
 
-    @action("Yes, this is the bug I'm trying to report",
-            name="this_is_my_bug", failure=handleSubmitBugFailure)
+    @action(
+        "Yes, this is the bug I'm trying to report",
+        name="this_is_my_bug",
+        failure=handleSubmitBugFailure,
+    )
     def this_is_my_bug_action(self, action, data):
         """Subscribe to the bug suggested."""
-        bug = data.get('bug_already_reported_as')
-        subscribe = data.get('subscribe_to_existing_bug')
+        bug = data.get("bug_already_reported_as")
+        subscribe = data.get("subscribe_to_existing_bug")
 
         if bug.isUserAffected(self.user):
             self.request.response.addNotification(
-                "This bug is already marked as affecting you.")
+                "This bug is already marked as affecting you."
+            )
         else:
             bug.markUserAffected(self.user)
             self.request.response.addNotification(
-                "This bug has been marked as affecting you.")
+                "This bug has been marked as affecting you."
+            )
 
         # If the user wants to be subscribed, subscribe them, unless
         # they're already subscribed.
         if subscribe:
             if bug.isSubscribed(self.user):
                 self.request.response.addNotification(
-                    "You are already subscribed to this bug.")
+                    "You are already subscribed to this bug."
+                )
             else:
                 bug.subscribe(self.user, self.user)
                 self.request.response.addNotification(
-                    "You have subscribed to this bug report.")
+                    "You have subscribed to this bug report."
+                )
 
         self.next_url = canonical_url(bug.bugtasks[0])
 
@@ -708,7 +783,7 @@ class FileBugViewBase(LaunchpadFormView):
         If a token was passed to this view, it will be passed through
         to the inline bug filing form via the returned URL.
         """
-        url = canonical_url(self.context, view_name='+filebug-inline-form')
+        url = canonical_url(self.context, view_name="+filebug-inline-form")
         if self.extra_data_token is not None:
             url = urlappend(url, self.extra_data_token)
         return url
@@ -716,7 +791,7 @@ class FileBugViewBase(LaunchpadFormView):
     @property
     def duplicate_search_url(self):
         """Return the URL to the inline duplicate search view."""
-        url = canonical_url(self.context, view_name='+filebug-show-similar')
+        url = canonical_url(self.context, view_name="+filebug-show-similar")
         if self.extra_data_token is not None:
             url = urlappend(url, self.extra_data_token)
         return url
@@ -759,7 +834,7 @@ class FileBugViewBase(LaunchpadFormView):
         # required=True. Instead it's validated in
         # FileBugViewBase.validate. So... we need to suppress the
         # "(Optional)" marker.
-        if field_name == 'comment':
+        if field_name == "comment":
             return False
         else:
             return LaunchpadFormView.showOptionalMarker(self, field_name)
@@ -809,11 +884,13 @@ class FileBugViewBase(LaunchpadFormView):
         # bug_reported_acknowledgement from their IDistribution, so we
         # don't need to look up this property in IDistribution.
         # IDistribution and IProjectGroup don't have any parents.
-        elif (IDistribution.providedBy(context) or
-              IProjectGroup.providedBy(context) or
-              IDistroSeries.providedBy(context) or
-              ISourcePackage.providedBy(context) or
-              IOCIProject.providedBy(context)):
+        elif (
+            IDistribution.providedBy(context)
+            or IProjectGroup.providedBy(context)
+            or IDistroSeries.providedBy(context)
+            or ISourcePackage.providedBy(context)
+            or IOCIProject.providedBy(context)
+        ):
             pass
         else:
             raise TypeError("Unexpected bug target: %r" % context)
@@ -831,7 +908,8 @@ class FileBugViewBase(LaunchpadFormView):
 
         try:
             return getUtility(IProcessApportBlobJobSource).getByBlobUUID(
-                self.extra_data_token)
+                self.extra_data_token
+            )
         except NotFoundError:
             return None
 
@@ -856,10 +934,12 @@ class FileBugViewBase(LaunchpadFormView):
         guidelines = []
         content = self.context.bug_reporting_guidelines
         if content is not None and len(content) > 0:
-            guidelines.append({
-                "source": self.context.bugtargetdisplayname,
-                "content": content,
-                })
+            guidelines.append(
+                {
+                    "source": self.context.bugtargetdisplayname,
+                    "content": content,
+                }
+            )
         # Distribution source packages are shown with both their
         # own reporting guidelines and those of their
         # distribution.
@@ -867,10 +947,12 @@ class FileBugViewBase(LaunchpadFormView):
             distribution = self.context.distribution
             content = distribution.bug_reporting_guidelines
             if content is not None and len(content) > 0:
-                guidelines.append({
-                    "source": distribution.bugtargetdisplayname,
-                    "content": content,
-                    })
+                guidelines.append(
+                    {
+                        "source": distribution.bugtargetdisplayname,
+                        "content": content,
+                    }
+                )
         return guidelines
 
     def getMainContext(self):
@@ -881,10 +963,11 @@ class FileBugViewBase(LaunchpadFormView):
 
     @cachedproperty
     def is_bug_supervisor(self):
-        """ Return True if the logged in user is a bug supervisor."""
+        """Return True if the logged in user is a bug supervisor."""
         context = self.getMainContext()
         return BugTask.userHasBugSupervisorPrivilegesContext(
-            context, self.user)
+            context, self.user
+        )
 
 
 class FileBugAdvancedView(LaunchpadView):
@@ -895,15 +978,18 @@ class FileBugAdvancedView(LaunchpadView):
 
     def initialize(self):
         filebug_url = canonical_url(
-            self.context, rootsite='bugs', view_name='+filebug')
+            self.context, rootsite="bugs", view_name="+filebug"
+        )
         self.request.response.redirect(
-            filebug_url, status=http.client.MOVED_PERMANENTLY)
+            filebug_url, status=http.client.MOVED_PERMANENTLY
+        )
 
 
 class IDistroBugAddForm(IBugAddForm):
 
     packagename = copy_field(
-        IBugAddForm['packagename'], vocabularyName='DistributionSourcePackage')
+        IBugAddForm["packagename"], vocabularyName="DistributionSourcePackage"
+    )
 
 
 class FilebugShowSimilarBugsView(FileBugViewBase):
@@ -915,7 +1001,7 @@ class FilebugShowSimilarBugsView(FileBugViewBase):
 
     @property
     def schema(self):
-        if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
+        if bool(getFeatureFlag("disclosure.dsp_picker.enabled")):
             return IDistroBugAddForm
         else:
             return IBugAddForm
@@ -936,7 +1022,7 @@ class FilebugShowSimilarBugsView(FileBugViewBase):
         This enables better validation error handling,
         since the form is always used inline on the +filebug page.
         """
-        url = '%s/+filebug' % canonical_url(self.context)
+        url = "%s/+filebug" % canonical_url(self.context)
         if self.extra_data_token is not None:
             url = urlappend(url, self.extra_data_token)
         return url
@@ -944,7 +1030,7 @@ class FilebugShowSimilarBugsView(FileBugViewBase):
     @property
     def search_text(self):
         """Return the search string entered by the user."""
-        return self.request.get('title')
+        return self.request.get("title")
 
     @cachedproperty
     def similar_bugs(self):
@@ -953,20 +1039,24 @@ class FilebugShowSimilarBugsView(FileBugViewBase):
         if not title:
             return []
         elif IProduct.providedBy(self.context):
-            context_params = {'product': self.context}
+            context_params = {"product": self.context}
         elif IDistribution.providedBy(self.context):
-            context_params = {'distribution': self.context}
+            context_params = {"distribution": self.context}
         else:
             assert IDistributionSourcePackage.providedBy(self.context), (
-                    'Unknown search context: %r' % self.context)
+                "Unknown search context: %r" % self.context
+            )
             context_params = {
-                'distribution': self.context.distribution,
-                'sourcepackagename': self.context.sourcepackagename}
+                "distribution": self.context.distribution,
+                "sourcepackagename": self.context.sourcepackagename,
+            }
 
         matching_bugtasks = getUtility(IBugTaskSet).findSimilar(
-            self.user, title, **context_params)
+            self.user, title, **context_params
+        )
         matching_bugs = getUtility(IBugSet).getDistinctBugsForBugTasks(
-            matching_bugtasks, self.user, self._MATCHING_BUGS_LIMIT)
+            matching_bugtasks, self.user, self._MATCHING_BUGS_LIMIT
+        )
         return matching_bugs
 
     @property
@@ -979,21 +1069,25 @@ class FilebugShowSimilarBugsView(FileBugViewBase):
           - There are no widget errors.
         """
         return (
-            self.contextUsesMalone() and
-            len(self.similar_bugs) > 0 and
-            len(self.widget_errors) == 0)
+            self.contextUsesMalone()
+            and len(self.similar_bugs) > 0
+            and len(self.widget_errors) == 0
+        )
 
 
 class FileBugGuidedView(FilebugShowSimilarBugsView):
 
-    page_title = 'Report a bug'
+    page_title = "Report a bug"
 
     _SEARCH_FOR_DUPES = ViewPageTemplateFile(
-        "../templates/bugtarget-filebug-search.pt")
+        "../templates/bugtarget-filebug-search.pt"
+    )
     _PROJECTGROUP_SEARCH_FOR_DUPES = ViewPageTemplateFile(
-        "../templates/projectgroup-filebug-search.pt")
+        "../templates/projectgroup-filebug-search.pt"
+    )
     _FILEBUG_FORM = ViewPageTemplateFile(
-        "../templates/bugtarget-filebug-submit-bug.pt")
+        "../templates/bugtarget-filebug-submit-bug.pt"
+    )
 
     # XXX 2009-07-17 Graham Binns
     #     As above, this assignment to actions is to make sure that the
@@ -1002,7 +1096,7 @@ class FileBugGuidedView(FilebugShowSimilarBugsView):
     actions = FilebugShowSimilarBugsView.actions
     template = _SEARCH_FOR_DUPES
 
-    focused_element_id = 'field.title'
+    focused_element_id = "field.title"
     show_summary_in_results = True
 
     def initialize(self):
@@ -1011,8 +1105,7 @@ class FileBugGuidedView(FilebugShowSimilarBugsView):
             # The user is trying to file a new Ubuntu bug via the web
             # interface and without using apport. Redirect to a page
             # explaining the preferred bug-filing procedure.
-            self.request.response.redirect(
-                config.malone.ubuntu_bug_filing_url)
+            self.request.response.redirect(config.malone.ubuntu_bug_filing_url)
 
     @safe_action
     @action("Continue", name="search")
@@ -1027,15 +1120,14 @@ class FileBugGuidedView(FilebugShowSimilarBugsView):
     def search_text(self):
         """Return the search string entered by the user."""
         try:
-            return self.widgets['title'].getInputValue()
+            return self.widgets["title"].getInputValue()
         except InputErrors:
             return None
 
     def validate_no_dupe_found(self, action, data):
         return ()
 
-    @action("Continue", name="continue",
-            validator="validate_no_dupe_found")
+    @action("Continue", name="continue", validator="validate_no_dupe_found")
     def continue_action(self, action, data):
         """The same action as no-dupe-found, with a different label."""
         return self.showFileBugForm()
@@ -1047,7 +1139,7 @@ class FileBugGuidedView(FilebugShowSimilarBugsView):
 class ProjectGroupFileBugGuidedView(LaunchpadFormView):
     """Guided filebug pages for IProjectGroup."""
 
-    page_title = 'Report a bug'
+    page_title = "Report a bug"
 
     schema = IProjectGroupBugAddForm
 
@@ -1058,13 +1150,15 @@ class ProjectGroupFileBugGuidedView(LaunchpadFormView):
 
     @property
     def field_names(self):
-        return ['product', 'title', 'tags']
+        return ["product", "title", "tags"]
 
     @cachedproperty
     def products_using_malone(self):
         return [
-            product for product in self.context.products
-            if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]
+            product
+            for product in self.context.products
+            if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD
+        ]
 
     @property
     def default_product(self):
@@ -1093,13 +1187,16 @@ class ProjectGroupFileBugGuidedView(LaunchpadFormView):
     def projectgroup_search_action(self, action, data):
         """Redirect to the chosen product's form."""
         base = canonical_url(
-            data['product'], view_name='+filebug', rootsite='bugs')
-        title = data['title'].encode('utf8')
-        query = urlencode([
-            ('field.title', title),
-            ('field.tags', ' '.join(data['tags'])),
-            ])
-        url = '%s?%s' % (base, query)
+            data["product"], view_name="+filebug", rootsite="bugs"
+        )
+        title = data["title"].encode("utf8")
+        query = urlencode(
+            [
+                ("field.title", title),
+                ("field.tags", " ".join(data["tags"])),
+            ]
+        )
+        url = "%s?%s" % (base, query)
         self.request.response.redirect(url)
 
 
@@ -1131,8 +1228,7 @@ class BugTargetBugListingView(LaunchpadView):
         elif IProduct(self.context, None):
             milestone_resultset = self.context.milestones
         else:
-            raise AssertionError(
-                "milestones_list called with illegal context")
+            raise AssertionError("milestones_list called with illegal context")
         return list(milestone_resultset)
 
     @property
@@ -1146,6 +1242,7 @@ class BugTargetBugListingView(LaunchpadView):
         """
         # Circular fail.
         from lp.bugs.model.bugsummary import BugSummary
+
         series_buglistings = []
         bug_task_set = getUtility(IBugTaskSet)
         series_list = self.series_list
@@ -1171,7 +1268,8 @@ class BugTargetBugListingView(LaunchpadView):
                         title=series.name,
                         url=canonical_url(series) + "/+bugs",
                         count=series_bug_count,
-                        ))
+                    )
+                )
         return series_buglistings
 
     @property
@@ -1181,7 +1279,8 @@ class BugTargetBugListingView(LaunchpadView):
         from lp.bugs.model.bugsummary import (
             BugSummary,
             CombineBugSummaryConstraint,
-            )
+        )
+
         milestone_buglistings = []
         bug_task_set = getUtility(IBugTaskSet)
         milestones = self.milestones_list
@@ -1190,10 +1289,12 @@ class BugTargetBugListingView(LaunchpadView):
         # Note: this isn't totally optimal as a query, but its the simplest to
         # code; we can iterate if needed to provide one complex context to
         # countBugs.
-        query_milestones = map(partial(
-            CombineBugSummaryConstraint, self.context), milestones)
+        query_milestones = map(
+            partial(CombineBugSummaryConstraint, self.context), milestones
+        )
         counts = bug_task_set.countBugs(
-            self.user, query_milestones, (BugSummary.milestone_id,))
+            self.user, query_milestones, (BugSummary.milestone_id,)
+        )
         for milestone in milestones:
             milestone_bug_count = counts.get((milestone.id,), 0)
             if milestone_bug_count > 0:
@@ -1202,7 +1303,8 @@ class BugTargetBugListingView(LaunchpadView):
                         title=milestone.name,
                         url=canonical_url(milestone),
                         count=milestone_bug_count,
-                        ))
+                    )
+                )
         return milestone_buglistings
 
 
@@ -1219,22 +1321,27 @@ class BugTargetBugTagsView(LaunchpadView):
         """The data for rendering a tags cloud"""
         official_tags = self.context.official_bug_tags
         tags = self.context.getUsedBugTagsWithOpenCounts(
-            self.user, 10, official_tags)
+            self.user, 10, official_tags
+        )
 
         return sorted(
-            (dict(
-                tag=tag,
-                count=count,
-                url=self._getSearchURL(tag),
+            (
+                dict(
+                    tag=tag,
+                    count=count,
+                    url=self._getSearchURL(tag),
                 )
-            for (tag, count) in tags.items()),
-            key=lambda item: (-item['count'], item['tag']))
+                for (tag, count) in tags.items()
+            ),
+            key=lambda item: (-item["count"], item["tag"]),
+        )
 
     @property
     def show_manage_tags_link(self):
         """Should a link to a "manage official tags" page be shown?"""
-        return (IOfficialBugTagTargetRestricted.providedBy(self.context) and
-                check_permission('launchpad.BugSupervisor', self.context))
+        return IOfficialBugTagTargetRestricted.providedBy(
+            self.context
+        ) and check_permission("launchpad.BugSupervisor", self.context)
 
 
 class OfficialBugTagsManageView(LaunchpadEditFormView):
@@ -1243,12 +1350,12 @@ class OfficialBugTagsManageView(LaunchpadEditFormView):
     schema = IOfficialBugTagTargetPublic
     custom_widget_official_bug_tags = LargeBugTagsWidget
 
-    label = 'Manage official bug tags'
+    label = "Manage official bug tags"
 
-    @action('Save', name='save')
+    @action("Save", name="save")
     def save_action(self, action, data):
         """Action for saving new official bug tags."""
-        self.context.official_bug_tags = data['official_bug_tags']
+        self.context.official_bug_tags = data["official_bug_tags"]
         self.next_url = canonical_url(self.context)
 
     @property
@@ -1257,7 +1364,8 @@ class OfficialBugTagsManageView(LaunchpadEditFormView):
         # The model returns dict and list respectively but dumps blows up on
         # security proxied objects.
         used_tags = removeSecurityProxy(
-            self.context.getUsedBugTagsWithOpenCounts(self.user))
+            self.context.getUsedBugTagsWithOpenCounts(self.user)
+        )
         official_tags = removeSecurityProxy(self.context.official_bug_tags)
         return """<script type="text/javascript">
                       var used_bug_tags = %s;
@@ -1265,9 +1373,10 @@ class OfficialBugTagsManageView(LaunchpadEditFormView):
                       var valid_name_pattern = %s;
                   </script>
                """ % (
-               dumps(used_tags),
-               dumps(official_tags),
-               dumps(valid_name_pattern.pattern))
+            dumps(used_tags),
+            dumps(official_tags),
+            dumps(valid_name_pattern.pattern),
+        )
 
     @property
     def cancel_url(self):
@@ -1278,7 +1387,7 @@ class OfficialBugTagsManageView(LaunchpadEditFormView):
 class BugsPatchesView(LaunchpadView):
     """View list of patch attachments associated with bugs."""
 
-    page_title = 'Patch attachments'
+    page_title = "Patch attachments"
 
     @property
     def label(self):
@@ -1286,7 +1395,7 @@ class BugsPatchesView(LaunchpadView):
         if IHeadingContext.providedBy(self.context):
             return self.page_title
         else:
-            return 'Patch attachments in %s' % self.context.displayname
+            return "Patch attachments in %s" % self.context.displayname
 
     @property
     def patch_task_orderings(self):
@@ -1299,11 +1408,13 @@ class BugsPatchesView(LaunchpadView):
            ("Importance", "-importance"),
            ...]
         """
-        orderings = [("patch age", "-latest_patch_uploaded"),
-                     ("importance", "-importance"),
-                     ("status", "status"),
-                     ("oldest first", "datecreated"),
-                     ("newest first", "-datecreated")]
+        orderings = [
+            ("patch age", "-latest_patch_uploaded"),
+            ("importance", "-importance"),
+            ("status", "status"),
+            ("oldest first", "datecreated"),
+            ("newest first", "-datecreated"),
+        ]
         targetname = self.targetName()
         if targetname is not None:
             # Lower case for consistency with the other orderings.
@@ -1315,13 +1426,19 @@ class BugsPatchesView(LaunchpadView):
         orderby = self.request.get("orderby", "-latest_patch_uploaded")
         if orderby not in [x[1] for x in self.patch_task_orderings]:
             raise UnexpectedFormData(
-                "Unexpected value for field 'orderby': '%s'" % orderby)
+                "Unexpected value for field 'orderby': '%s'" % orderby
+            )
         return BatchNavigator(
             self.context.searchTasks(
-                None, user=self.user, order_by=orderby,
+                None,
+                user=self.user,
+                order_by=orderby,
                 status=UNRESOLVED_BUGTASK_STATUSES,
-                omit_duplicates=True, has_patch=True),
-            self.request)
+                omit_duplicates=True,
+                has_patch=True,
+            ),
+            self.request,
+        )
 
     def targetName(self):
         """Return the name of the current context's target type, or None.
@@ -1331,11 +1448,13 @@ class BugsPatchesView(LaunchpadView):
         name in a web page, for example.  If no target type is
         appropriate for the current context, then return None.
         """
-        if (IDistribution.providedBy(self.context) or
-            IDistroSeries.providedBy(self.context)):
+        if IDistribution.providedBy(self.context) or IDistroSeries.providedBy(
+            self.context
+        ):
             return "Package"
-        elif (IProjectGroup.providedBy(self.context) or
-              IPerson.providedBy(self.context)):
+        elif IProjectGroup.providedBy(self.context) or IPerson.providedBy(
+            self.context
+        ):
             # In the case of an IPerson, the target column can vary
             # row-by-row, showing both packages and products.  We
             # decided to go with the table header "Project" for both,
@@ -1350,7 +1469,7 @@ class BugsPatchesView(LaunchpadView):
 
     def patchAge(self, patch):
         """Return a timedelta object for the age of a patch attachment."""
-        now = datetime.now(timezone('UTC'))
+        now = datetime.now(timezone("UTC"))
         return now - patch.message.datecreated
 
     def proxiedUrlForLibraryFile(self, patch):
@@ -1361,7 +1480,7 @@ class BugsPatchesView(LaunchpadView):
 class TargetSubscriptionView(LaunchpadView):
     """A view to show all a person's structural subscriptions to a target."""
 
-    page_title = 'Your subscriptions'
+    page_title = "Your subscriptions"
 
     @property
     def label(self):
@@ -1373,9 +1492,9 @@ class TargetSubscriptionView(LaunchpadView):
     def initialize(self):
         super().initialize()
         expose_structural_subscription_data_to_js(
-            self.context, self.request, self.user, self.subscriptions)
+            self.context, self.request, self.user, self.subscriptions
+        )
 
     @property
     def subscriptions(self):
-        return get_structural_subscriptions_for_target(
-            self.context, self.user)
+        return get_structural_subscriptions_for_target(self.context, self.user)
diff --git a/lib/lp/bugs/browser/bugtask.py b/lib/lp/bugs/browser/bugtask.py
index 7228fc7..af99675 100644
--- a/lib/lp/bugs/browser/bugtask.py
+++ b/lib/lp/bugs/browser/bugtask.py
@@ -4,37 +4,35 @@
 """IBugTask-related browser views."""
 
 __all__ = [
-    'bugtarget_renderer',
-    'BugTargetTraversalMixin',
-    'BugTaskBreadcrumb',
-    'BugTaskContextMenu',
-    'BugTaskCreateQuestionView',
-    'BugTaskDeletionView',
-    'BugTaskEditView',
-    'BugTaskNavigation',
-    'BugTaskPrivacyAdapter',
-    'BugTaskRemoveQuestionView',
-    'BugTasksNominationsView',
-    'BugTasksTableView',
-    'BugTaskTableRowView',
-    'BugTaskTextView',
-    'BugTaskView',
-    'can_add_package_task_to_bug',
-    'can_add_project_task_to_bug',
-    'get_comments_for_bugtask',
-    'get_visible_comments',
-    ]
+    "bugtarget_renderer",
+    "BugTargetTraversalMixin",
+    "BugTaskBreadcrumb",
+    "BugTaskContextMenu",
+    "BugTaskCreateQuestionView",
+    "BugTaskDeletionView",
+    "BugTaskEditView",
+    "BugTaskNavigation",
+    "BugTaskPrivacyAdapter",
+    "BugTaskRemoveQuestionView",
+    "BugTasksNominationsView",
+    "BugTasksTableView",
+    "BugTaskTableRowView",
+    "BugTaskTextView",
+    "BugTaskView",
+    "can_add_package_task_to_bug",
+    "can_add_project_task_to_bug",
+    "get_comments_for_bugtask",
+    "get_visible_comments",
+]
 
+import re
 from collections import defaultdict
-from datetime import (
-    datetime,
-    timedelta,
-    )
+from datetime import datetime, timedelta
 from itertools import groupby
 from operator import attrgetter
-import re
 from urllib.parse import quote
 
+import transaction
 from lazr.delegates import delegate_to
 from lazr.lifecycle.event import ObjectModifiedEvent
 from lazr.lifecycle.snapshot import Snapshot
@@ -44,31 +42,19 @@ from lazr.restful.interfaces import (
     IJSONRequestCache,
     IReference,
     IWebServiceClientRequest,
-    )
+)
 from lazr.restful.utils import smartquote
 from pytz import utc
 from simplejson import dumps
-import transaction
 from zope import formlib
 from zope.browserpage import ViewPageTemplateFile
-from zope.component import (
-    adapter,
-    getAdapter,
-    getMultiAdapter,
-    getUtility,
-    )
+from zope.component import adapter, getAdapter, getMultiAdapter, getUtility
 from zope.event import notify
 from zope.formlib.widget import CustomWidgetFactory
-from zope.interface import (
-    implementer,
-    providedBy,
-    )
+from zope.interface import implementer, providedBy
 from zope.interface.interfaces import ComponentLookupError
 from zope.schema import Choice
-from zope.schema.vocabulary import (
-    getVocabularyRegistry,
-    SimpleVocabulary,
-    )
+from zope.schema.vocabulary import SimpleVocabulary, getVocabularyRegistry
 from zope.security.interfaces import Unauthorized
 from zope.security.proxy import removeSecurityProxy
 from zope.traversing.browser import absoluteURL
@@ -76,16 +62,16 @@ from zope.traversing.interfaces import IPathAdapter
 
 from lp import _
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
     ReturnToReferrerMixin,
-    )
+    action,
+)
 from lp.app.browser.lazrjs import (
     TextAreaEditorWidget,
     TextLineEditorWidget,
     vocabulary_to_choice_edit_items,
-    )
+)
 from lp.app.browser.stringformatter import FormattersAPI
 from lp.app.browser.tales import ObjectImageDisplayAPI
 from lp.app.browser.vocabulary import vocabulary_filters
@@ -93,15 +79,11 @@ from lp.app.enums import PROPRIETARY_INFORMATION_TYPES
 from lp.app.errors import NotFoundError
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.archivepublisher.debversion import Version
-from lp.bugs.browser.bug import (
-    BugContextMenu,
-    BugTextView,
-    BugViewMixin,
-    )
+from lp.bugs.browser.bug import BugContextMenu, BugTextView, BugViewMixin
 from lp.bugs.browser.bugcomment import (
     build_comments_from_chunks,
     group_comments_with_activity,
-    )
+)
 from lp.bugs.browser.widgets.bugtask import (
     AssigneeDisplayWidget,
     BugTaskAssigneeWidget,
@@ -109,21 +91,18 @@ from lp.bugs.browser.widgets.bugtask import (
     BugTaskSourcePackageNameWidget,
     BugTaskTargetWidget,
     DBItemDisplayWidget,
-    )
+)
 from lp.bugs.enums import BugLockStatus
-from lp.bugs.interfaces.bug import (
-    IBug,
-    IBugSet,
-    )
+from lp.bugs.interfaces.bug import IBug, IBugSet
 from lp.bugs.interfaces.bugactivity import IBugActivity
 from lp.bugs.interfaces.bugattachment import (
     BugAttachmentType,
     IBugAttachmentSet,
-    )
+)
 from lp.bugs.interfaces.bugnomination import (
     BugNominationStatus,
     IBugNominationSet,
-    )
+)
 from lp.bugs.interfaces.bugtarget import ISeriesBugTarget
 from lp.bugs.interfaces.bugtask import (
     BugTaskImportance,
@@ -135,24 +114,18 @@ from lp.bugs.interfaces.bugtask import (
     IllegalTarget,
     IRemoveQuestionFromBugTaskForm,
     UserCannotEditBugTaskStatus,
-    )
+)
 from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
 from lp.bugs.interfaces.bugtracker import BugTrackerType
 from lp.bugs.interfaces.bugwatch import BugWatchActivityStatus
 from lp.bugs.interfaces.cve import ICveSet
 from lp.bugs.vocabularies import BugTaskMilestoneVocabulary
 from lp.code.interfaces.branchcollection import IAllBranches
-from lp.registry.interfaces.distribution import (
-    IDistribution,
-    IDistributionSet,
-    )
+from lp.registry.interfaces.distribution import IDistribution, IDistributionSet
 from lp.registry.interfaces.distributionsourcepackage import (
     IDistributionSourcePackage,
-    )
-from lp.registry.interfaces.distroseries import (
-    IDistroSeries,
-    IDistroSeriesSet,
-    )
+)
+from lp.registry.interfaces.distroseries import IDistroSeries, IDistroSeriesSet
 from lp.registry.interfaces.ociproject import IOCIProject
 from lp.registry.interfaces.person import IPersonSet
 from lp.registry.interfaces.product import IProduct
@@ -165,29 +138,22 @@ from lp.services.feeds.browser import FeedsMixin
 from lp.services.fields import PersonChoice
 from lp.services.mail.notification import get_unified_diff
 from lp.services.privacy.interfaces import IObjectPrivacy
-from lp.services.propertycache import (
-    cachedproperty,
-    get_property_cache,
-    )
+from lp.services.propertycache import cachedproperty, get_property_cache
 from lp.services.webapp import (
-    canonical_url,
     LaunchpadView,
     Navigation,
+    canonical_url,
     redirection,
     stepthrough,
-    )
+)
 from lp.services.webapp.authorization import (
     check_permission,
     precache_permission_for_objects,
-    )
+)
 from lp.services.webapp.breadcrumb import Breadcrumb
-from lp.services.webapp.escaping import (
-    html_escape,
-    structured,
-    )
+from lp.services.webapp.escaping import html_escape, structured
 from lp.services.webapp.interfaces import ILaunchBag
 
-
 vocabulary_registry = getVocabularyRegistry()
 
 
@@ -203,8 +169,10 @@ def bugtarget_renderer(context, field, request):
             </span>""",
             href=canonical_url(context.target),
             css_class=ObjectImageDisplayAPI(context.target).sprite_css(),
-            displayname=context.bugtargetdisplayname).escapedtext
+            displayname=context.bugtargetdisplayname,
+        ).escapedtext
         return html
+
     return render
 
 
@@ -215,13 +183,19 @@ def unique_title(title):
     if title is None:
         return None
     title = title.lower()
-    if title.startswith('re:'):
+    if title.startswith("re:"):
         title = title[3:]
     return title.strip()
 
 
-def get_comments_for_bugtask(bugtask, truncate=False, for_display=False,
-    slice_info=None, show_spam_controls=False, user=None):
+def get_comments_for_bugtask(
+    bugtask,
+    truncate=False,
+    for_display=False,
+    slice_info=None,
+    show_spam_controls=False,
+    user=None,
+):
     """Return BugComments related to a bugtask.
 
     This code builds a sorted list of BugComments in one shot,
@@ -233,9 +207,14 @@ def get_comments_for_bugtask(bugtask, truncate=False, for_display=False,
     :param slice_info: If not None, defines a list of slices of the comments
         to retrieve.
     """
-    comments = build_comments_from_chunks(bugtask, truncate=truncate,
-        slice_info=slice_info, show_spam_controls=show_spam_controls,
-        user=user, hide_first=for_display)
+    comments = build_comments_from_chunks(
+        bugtask,
+        truncate=truncate,
+        slice_info=slice_info,
+        show_spam_controls=show_spam_controls,
+        user=user,
+        hide_first=for_display,
+    )
     # TODO: further fat can be shaved off here by limiting the attachments we
     # query to those that slice_info would include.
     for comment in comments.values():
@@ -253,10 +232,10 @@ def get_comments_for_bugtask(bugtask, truncate=False, for_display=False,
     comments = sorted(comments.values(), key=attrgetter("index"))
     current_title = bugtask.bug.title
     for comment in comments:
-        if not ((unique_title(comment.title) ==
-                 unique_title(current_title)) or
-                (unique_title(comment.title) ==
-                 unique_title(bugtask.bug.title))):
+        if not (
+            (unique_title(comment.title) == unique_title(current_title))
+            or (unique_title(comment.title) == unique_title(bugtask.bug.title))
+        ):
             # this comment has a new title, so make that the rolling focus
             current_title = comment.title
             comment.display_title = True
@@ -273,9 +252,11 @@ def get_visible_comments(comments, user=None):
         # double-submissions or user errors, and which don't add
         # anything useful to the bug itself.
         # Also omit comments with no body text or attachments to display.
-        if (comment.isEmpty() or
-            previous_comment and
-            previous_comment.isIdenticalTo(comment)):
+        if (
+            comment.isEmpty()
+            or previous_comment
+            and previous_comment.isIdenticalTo(comment)
+        ):
             continue
 
         visible_comments.append(comment)
@@ -298,8 +279,9 @@ def get_visible_comments(comments, user=None):
         role = PersonRoles(user)
         strip_invisible = not (role.in_admin or role.in_registry_experts)
     if strip_invisible:
-        visible_comments = [c for c in visible_comments
-                            if c.visible or c.owner == user]
+        visible_comments = [
+            c for c in visible_comments if c.visible or c.owner == user
+        ]
 
     return visible_comments
 
@@ -307,7 +289,7 @@ def get_visible_comments(comments, user=None):
 class BugTargetTraversalMixin:
     """Mix-in in class that provides .../+bug/NNN traversal."""
 
-    @stepthrough('+bug')
+    @stepthrough("+bug")
     def traverse_bug(self, name):
         """Traverses +bug portions of URLs."""
         return self._get_task_for_context(name)
@@ -328,7 +310,7 @@ class BugTargetTraversalMixin:
 
         # Get out now if the user cannot view the bug. Continuing may
         # reveal information about its context
-        if not check_permission('launchpad.View', bug):
+        if not check_permission("launchpad.View", bug):
             return None
 
         # Loop through this bug's tasks to try and find the appropriate task
@@ -351,29 +333,31 @@ class BugTargetTraversalMixin:
         if len(traversal_stack) > 0:
             raise NotFoundError
         return self.redirectSubTree(
-            canonical_url(bug.default_bugtask, request=self.request))
+            canonical_url(bug.default_bugtask, request=self.request)
+        )
 
-    @redirection('+bug')
+    @redirection("+bug")
     def redirect_bug(self):
         """If +bug traversal fails, redirect to +bugs."""
-        return '+bugs'
+        return "+bugs"
 
 
 class BugTaskNavigation(Navigation):
     """Navigation for the `IBugTask`."""
+
     usedfor = IBugTask
 
-    @stepthrough('attachments')
+    @stepthrough("attachments")
     def traverse_attachments(self, name):
         """traverse to an attachment by id."""
         if name.isdigit():
             attachment = getUtility(IBugAttachmentSet)[name]
             if attachment is not None and attachment.bug == self.context.bug:
                 return self.redirectSubTree(
-                    canonical_url(attachment, request=self.request),
-                    status=301)
+                    canonical_url(attachment, request=self.request), status=301
+                )
 
-    @stepthrough('+attachment')
+    @stepthrough("+attachment")
     def traverse_attachment(self, name):
         """traverse to an attachment by id."""
         if name.isdigit():
@@ -381,7 +365,7 @@ class BugTaskNavigation(Navigation):
             if attachment is not None and attachment.bug == self.context.bug:
                 return attachment
 
-    @stepthrough('comments')
+    @stepthrough("comments")
     def traverse_comments(self, name):
         """Traverse to a comment by index."""
         if not name.isdigit():
@@ -389,34 +373,40 @@ class BugTaskNavigation(Navigation):
         index = int(name)
         # Ask the DB to slice out just the comment that we need.
         comments = get_comments_for_bugtask(
-            self.context, slice_info=[slice(index, index + 1)])
+            self.context, slice_info=[slice(index, index + 1)]
+        )
         # XXX cjwatson 2015-09-15: Unify with
         # Bug.userCanSetCommentVisibility, which also allows
         # project-privileged users.
         user = getUtility(ILaunchBag).user
         roles = PersonRoles(user) if user else None
-        if (comments and (
-                comments[0].visible
-                or user and (
-                    comments[0].owner == user
-                    or roles.in_admin or roles.in_registry_experts))):
+        if comments and (
+            comments[0].visible
+            or user
+            and (
+                comments[0].owner == user
+                or roles.in_admin
+                or roles.in_registry_experts
+            )
+        ):
             return comments[0]
         return None
 
-    @stepthrough('nominations')
+    @stepthrough("nominations")
     def traverse_nominations(self, nomination_id):
         """Traverse to a nomination by id."""
         if not nomination_id.isdigit():
             return None
         return getUtility(IBugNominationSet).get(int(nomination_id))
 
-    @redirection('references')
+    @redirection("references")
     def redirect_references(self):
-        return '..'
+        return ".."
 
 
 class BugTaskContextMenu(BugContextMenu):
     """Context menu of actions that can be performed upon an `IBugTask`."""
+
     usedfor = IBugTask
 
 
@@ -443,8 +433,11 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
             self.context = getUtility(ILaunchBag).bugtask
         else:
             self.context = context
-        list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-            [self.context.bug.ownerID], need_validity=True))
+        list(
+            getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                [self.context.bug.ownerID], need_validity=True
+            )
+        )
 
     @property
     def page_title(self):
@@ -452,8 +445,10 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
 
     @property
     def label(self):
-        heading = 'Bug #%s in %s' % (
-            self.context.bug.id, self.context.bugtargetdisplayname)
+        heading = "Bug #%s in %s" % (
+            self.context.bug.id,
+            self.context.bugtargetdisplayname,
+        )
         title = FormattersAPI(self.context.bug.title).obfuscate_email()
         return smartquote('%s: "%s"') % (heading, title)
 
@@ -464,7 +459,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
     @property
     def next_url(self):
         """Provided so returning to the page they came from works."""
-        referer = self.request.getHeader('referer')
+        referer = self.request.getHeader("referer")
         if referer:
             next_url = referer
         else:
@@ -474,7 +469,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
     @property
     def cancel_url(self):
         """Provided so returning to the page they came from works."""
-        referer = self.request.getHeader('referer')
+        referer = self.request.getHeader("referer")
         if referer:
             cancel_url = referer
         else:
@@ -485,10 +480,10 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
     def is_duplicate_active(self):
         active = True
         if self.context.bug.duplicateof is not None:
-            naked_duplicate = removeSecurityProxy(
-                self.context.bug.duplicateof)
+            naked_duplicate = removeSecurityProxy(self.context.bug.duplicateof)
             active = getattr(
-                naked_duplicate.default_bugtask.target, 'active', True)
+                naked_duplicate.default_bugtask.target, "active", True
+            )
         return active
 
     @cachedproperty
@@ -497,7 +492,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
 
     @cachedproperty
     def recommended_canonical_url(self):
-        return canonical_url(self.context.bug, rootsite='bugs')
+        return canonical_url(self.context.bug, rootsite="bugs")
 
     @property
     def information_type(self):
@@ -507,26 +502,34 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         """Set up the needed widgets."""
         bug = self.context.bug
         cache = IJSONRequestCache(self.request)
-        cache.objects['bug'] = bug
+        cache.objects["bug"] = bug
         subscribers_url_data = {
-            'web_link': canonical_url(bug, rootsite='bugs'),
-            'self_link': absoluteURL(bug, self.api_request),
-            }
-        cache.objects['subscribers_portlet_url_data'] = subscribers_url_data
-        cache.objects['total_comments_and_activity'] = (
-            self.total_comments + self.total_activity)
-        cache.objects['initial_comment_batch_offset'] = (
-            self.visible_initial_comments + 1)
-        cache.objects['first visible_recent_comment'] = (
-            self.total_comments - self.visible_recent_comments)
+            "web_link": canonical_url(bug, rootsite="bugs"),
+            "self_link": absoluteURL(bug, self.api_request),
+        }
+        cache.objects["subscribers_portlet_url_data"] = subscribers_url_data
+        cache.objects["total_comments_and_activity"] = (
+            self.total_comments + self.total_activity
+        )
+        cache.objects["initial_comment_batch_offset"] = (
+            self.visible_initial_comments + 1
+        )
+        cache.objects["first visible_recent_comment"] = (
+            self.total_comments - self.visible_recent_comments
+        )
 
         # See render() for how this flag is used.
         self._redirecting_to_bug_list = False
 
         self.bug_title_edit_widget = TextLineEditorWidget(
-            bug, IBug['title'], "Edit this summary", 'h1',
-            edit_url=canonical_url(self.context, view_name='+edit'),
-            max_width='95%', truncate_lines=6)
+            bug,
+            IBug["title"],
+            "Edit this summary",
+            "h1",
+            edit_url=canonical_url(self.context, view_name="+edit"),
+            max_width="95%",
+            truncate_lines=6,
+        )
 
         # XXX 2010-10-05 gmb bug=655597:
         # This line of code keeps the view's query count down,
@@ -539,9 +542,9 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
 
     def userIsSubscribed(self):
         """Is the user subscribed to this bug?"""
-        return (
-            self.context.bug.isSubscribed(self.user) or
-            self.context.bug.isSubscribedToDupes(self.user))
+        return self.context.bug.isSubscribed(
+            self.user
+        ) or self.context.bug.isSubscribedToDupes(self.user)
 
     def render(self):
         """Render the bug list if the user has permission to see the bug."""
@@ -549,7 +552,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         # after unsubscribing from a private bug, because rendering the
         # bug page would raise Unauthorized errors!
         if self._redirecting_to_bug_list:
-            return ''
+            return ""
         else:
             return LaunchpadView.render(self)
 
@@ -557,8 +560,9 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         """Nominate the bug for the series and redirect to the bug page."""
         self.context.bug.addNomination(self.user, series)
         self.request.response.addInfoNotification(
-            'This bug has been nominated to be fixed in %s.' %
-                series.bugtargetdisplayname)
+            "This bug has been nominated to be fixed in %s."
+            % series.bugtargetdisplayname
+        )
         self.request.response.redirect(canonical_url(self.context))
 
     @cachedproperty
@@ -570,47 +574,65 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         bug = self.context.bug
         show_spam_controls = bug.userCanSetCommentVisibility(self.user)
         return get_comments_for_bugtask(
-            self.context, truncate=True, slice_info=slice_info,
-            for_display=True, show_spam_controls=show_spam_controls,
-            user=self.user)
+            self.context,
+            truncate=True,
+            slice_info=slice_info,
+            for_display=True,
+            show_spam_controls=show_spam_controls,
+            user=self.user,
+        )
 
     @cachedproperty
     def interesting_activity(self):
         return self._getInterestingActivity()
 
-    def _getInterestingActivity(self, earliest_activity_date=None,
-                                latest_activity_date=None):
+    def _getInterestingActivity(
+        self, earliest_activity_date=None, latest_activity_date=None
+    ):
         """A sequence of interesting bug activity."""
-        if (earliest_activity_date is not None and
-            latest_activity_date is not None):
+        if (
+            earliest_activity_date is not None
+            and latest_activity_date is not None
+        ):
             # Only get the activity for the date range that we're
             # interested in to save us from processing too much.
             activity = self.context.bug.getActivityForDateRange(
                 start_date=earliest_activity_date,
-                end_date=latest_activity_date)
+                end_date=latest_activity_date,
+            )
         else:
             activity = self.context.bug.activity
         bug_change_re = (
-            'affects|description|security vulnerability|information type|'
-            'summary|tags|visibility|bug task deleted|lock status|lock reason')
+            "affects|description|security vulnerability|information type|"
+            "summary|tags|visibility|bug task deleted|lock status|lock reason"
+        )
         bugtask_change_re = (
-            r'[a-z0-9][a-z0-9\+\.\-]+( \([A-Za-z0-9\s]+\))?: '
-            r'(assignee|importance explanation|importance|milestone|'
-            r'status explanation|status)')
+            r"[a-z0-9][a-z0-9\+\.\-]+( \([A-Za-z0-9\s]+\))?: "
+            r"(assignee|importance explanation|importance|milestone|"
+            r"status explanation|status)"
+        )
         interesting_match = re.compile(
-            "^(%s|%s)$" % (bug_change_re, bugtask_change_re)).match
+            "^(%s|%s)$" % (bug_change_re, bugtask_change_re)
+        ).match
 
         activity_items = [
-            activity_item for activity_item in activity
-            if interesting_match(activity_item.whatchanged) is not None]
+            activity_item
+            for activity_item in activity
+            if interesting_match(activity_item.whatchanged) is not None
+        ]
         # Pre-load the doers of the activities in one query.
         person_ids = {
-            activity_item.person_id for activity_item in activity_items}
-        list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-            person_ids, need_validity=True))
+            activity_item.person_id for activity_item in activity_items
+        }
+        list(
+            getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                person_ids, need_validity=True
+            )
+        )
 
         interesting_activity = tuple(
-            BugActivityItem(activity_item) for activity_item in activity_items)
+            BugActivityItem(activity_item) for activity_item in activity_items
+        )
 
         # This is a bit kludgy but it means that interesting_activity is
         # populated correctly for all subsequent calls.
@@ -619,12 +641,16 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
 
     def _getEventGroups(self, batch_size=None, offset=None):
         # Ensure truncation results in < max_length comments as expected
-        assert(config.malone.comments_list_truncate_oldest_to
-               + config.malone.comments_list_truncate_newest_to
-               < config.malone.comments_list_max_length)
-
-        if (not self.visible_comments_truncated_for_display and
-            batch_size is None):
+        assert (
+            config.malone.comments_list_truncate_oldest_to
+            + config.malone.comments_list_truncate_newest_to
+            < config.malone.comments_list_max_length
+        )
+
+        if (
+            not self.visible_comments_truncated_for_display
+            and batch_size is None
+        ):
             comments = self.comments
         elif batch_size is not None:
             # If we're limiting to a given set of comments, we work on
@@ -632,8 +658,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
             # on processing time a bit.
             if offset is None:
                 offset = self.visible_initial_comments
-            comments = self._getComments([
-                slice(offset, offset + batch_size)])
+            comments = self._getComments([slice(offset, offset + batch_size)])
         else:
             # the comment function takes 0-offset counts where comment 0 is
             # the initial description, so we need to add one to the limits
@@ -643,24 +668,23 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
             slice_info = [
                 slice(None, oldest_count),
                 slice(new_count, None),
-                ]
+            ]
             comments = self._getComments(slice_info)
 
-        visible_comments = get_visible_comments(
-            comments, user=self.user)
+        visible_comments = get_visible_comments(comments, user=self.user)
         if len(visible_comments) > 0 and batch_size is not None:
             first_comment = visible_comments[0]
             last_comment = visible_comments[-1]
-            interesting_activity = (
-                self._getInterestingActivity(
-                    earliest_activity_date=first_comment.datecreated,
-                    latest_activity_date=last_comment.datecreated))
+            interesting_activity = self._getInterestingActivity(
+                earliest_activity_date=first_comment.datecreated,
+                latest_activity_date=last_comment.datecreated,
+            )
         else:
             interesting_activity = self.interesting_activity
 
         event_groups = group_comments_with_activity(
-            comments=visible_comments,
-            activities=interesting_activity)
+            comments=visible_comments, activities=interesting_activity
+        )
         return event_groups
 
     @cachedproperty
@@ -694,15 +718,20 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         def activity_sort_key(activity):
             target = activity.target
             return (
-                activity.datechanged, 0 if target is None else 1, target,
-                activity.attribute)
+                activity.datechanged,
+                0 if target is None else 1,
+                target,
+                activity.attribute,
+            )
 
         def group_activities_by_target(activities):
             activities = sorted(activities, key=activity_sort_key)
             return [
                 {"target": target, "activity": list(activity)}
                 for target, activity in groupby(
-                    activities, attrgetter("target"))]
+                    activities, attrgetter("target")
+                )
+            ]
 
         def comment_event_dict(comment):
             actors = {activity.person for activity in comment.activity}
@@ -715,7 +744,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
                 "comment": comment,
                 "date": min(dates),
                 "person": actors.pop(),
-                }
+            }
 
         def activity_event_dict(activities):
             actors = {activity.person for activity in activities}
@@ -725,7 +754,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
                 "activity": group_activities_by_target(activities),
                 "date": min(dates),
                 "person": actors.pop(),
-                }
+            }
 
         def event_dict(event_group):
             if isinstance(event_group, list):
@@ -758,9 +787,9 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
                     # not 2 (their difference).
                     num_hidden = abs(comment.index - prev_comment.index) - 1
                     separator = {
-                        'date': prev_comment.datecreated,
-                        'num_hidden': num_hidden,
-                        }
+                        "date": prev_comment.datecreated,
+                        "num_hidden": num_hidden,
+                    }
                     events.insert(index, separator)
                     index += 1
                 prev_comment = comment
@@ -780,7 +809,7 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
     @cachedproperty
     def visible_comments_truncated_for_display(self):
         """Whether the visible comment list is truncated for display."""
-        show_all = (self.request.form_ng.getOne('comments') == 'all')
+        show_all = self.request.form_ng.getOne("comments") == "all"
         if show_all:
             return False
         max_comments = config.malone.comments_list_max_length
@@ -800,28 +829,39 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
 
     def wasDescriptionModified(self):
         """Return a boolean indicating whether the description was modified"""
-        return (self.context.bug._indexed_messages(
-            include_content=True, include_parents=False)[0].text_contents !=
-            self.context.bug.description)
+        return (
+            self.context.bug._indexed_messages(
+                include_content=True, include_parents=False
+            )[0].text_contents
+            != self.context.bug.description
+        )
 
     @cachedproperty
     def linked_branches(self):
         """Filter out the bug_branch links to non-visible private branches."""
         linked_branches = list(
             self.context.bug.getVisibleLinkedBranches(
-                self.user, eager_load=True))
+                self.user, eager_load=True
+            )
+        )
         # This is an optimization for when we look at the merge proposals.
         if linked_branches:
-            list(getUtility(IAllBranches).getMergeProposals(
-                for_branches=[link.branch for link in linked_branches],
-                eager_load=True))
+            list(
+                getUtility(IAllBranches).getMergeProposals(
+                    for_branches=[link.branch for link in linked_branches],
+                    eager_load=True,
+                )
+            )
         return linked_branches
 
     @cachedproperty
     def linked_merge_proposals(self):
         """Filter out the links to non-visible private MPs."""
-        return list(self.context.bug.getVisibleLinkedMergeProposals(
-            self.user, eager_load=True))
+        return list(
+            self.context.bug.getVisibleLinkedMergeProposals(
+                self.user, eager_load=True
+            )
+        )
 
     @property
     def days_to_expiration(self):
@@ -853,11 +893,14 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
             # We should always display a positive number to the user,
             # whether we're talking about the past or the future.
             days_to_expiration = -days_to_expiration
-            message = ("This bug report was marked for expiration %i days "
-                "ago.")
+            message = (
+                "This bug report was marked for expiration %i days " "ago."
+            )
         else:
-            message = ("This bug report will be marked for expiration in %i "
-                "days if no further activity occurs.")
+            message = (
+                "This bug report will be marked for expiration in %i "
+                "days if no further activity occurs."
+            )
 
         return message % days_to_expiration
 
@@ -868,9 +911,20 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         links = []
         for tag in self.context.bug.tags:
             if tag in target_official_tags:
-                links.append((tag, '%s?field.tag=%s' % (
-                    canonical_url(self.context.target, view_name='+bugs',
-                        force_local_path=True), quote(tag))))
+                links.append(
+                    (
+                        tag,
+                        "%s?field.tag=%s"
+                        % (
+                            canonical_url(
+                                self.context.target,
+                                view_name="+bugs",
+                                force_local_path=True,
+                            ),
+                            quote(tag),
+                        ),
+                    )
+                )
         return links
 
     @property
@@ -880,9 +934,20 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         links = []
         for tag in self.context.bug.tags:
             if tag not in target_official_tags:
-                links.append((tag, '%s?field.tag=%s' % (
-                    canonical_url(self.context.target, view_name='+bugs',
-                        force_local_path=True), quote(tag))))
+                links.append(
+                    (
+                        tag,
+                        "%s?field.tag=%s"
+                        % (
+                            canonical_url(
+                                self.context.target,
+                                view_name="+bugs",
+                                force_local_path=True,
+                            ),
+                            quote(tag),
+                        ),
+                    )
+                )
         return links
 
     @property
@@ -896,29 +961,29 @@ class BugTaskView(LaunchpadView, BugViewMixin, FeedsMixin):
         # Unwrap the security proxy. - official_tags is a security proxy
         # wrapped list.
         available_tags = list(self.context.bug.official_tags)
-        return 'var available_official_tags = %s;' % dumps(available_tags)
+        return "var available_official_tags = %s;" % dumps(available_tags)
 
     @property
     def user_is_admin(self):
         """Is the user a Launchpad admin?"""
-        return check_permission('launchpad.Admin', self.context)
+        return check_permission("launchpad.Admin", self.context)
 
     @property
     def bug_description_html(self):
         """The bug's description as HTML."""
         bug = self.context.bug
-        description = IBug['description']
+        description = IBug["description"]
         title = "Bug Description"
-        edit_url = canonical_url(self.context, view_name='+edit')
-        return TextAreaEditorWidget(
-            bug, description, title, edit_url=edit_url)
+        edit_url = canonical_url(self.context, view_name="+edit")
+        return TextAreaEditorWidget(bug, description, title, edit_url=edit_url)
 
     @property
     def bug_heat_html(self):
         """HTML representation of the bug heat."""
         return (
             '<span><a href="/+help-bugs/bug-heat.html" target="help" '
-            'class="sprite flame">%d</a></span>' % self.context.bug.heat)
+            'class="sprite flame">%d</a></span>' % self.context.bug.heat
+        )
 
 
 class BugTaskBatchedCommentsAndActivityView(BugTaskView):
@@ -930,7 +995,7 @@ class BugTaskBatchedCommentsAndActivityView(BugTaskView):
     @property
     def offset(self):
         try:
-            return int(self.request.form_ng.getOne('offset'))
+            return int(self.request.form_ng.getOne("offset"))
         except TypeError:
             # We return visible_initial_comments + 1, since otherwise we'd
             # end up repeating comments that are already visible on the
@@ -942,15 +1007,17 @@ class BugTaskBatchedCommentsAndActivityView(BugTaskView):
     @property
     def batch_size(self):
         try:
-            return int(self.request.form_ng.getOne('batch_size'))
+            return int(self.request.form_ng.getOne("batch_size"))
         except TypeError:
             return config.malone.comments_list_default_batch_size
 
     @property
     def next_batch_url(self):
         return "%s?offset=%s&batch_size=%s" % (
-            canonical_url(self.context, view_name='+batched-comments'),
-            self.next_offset, self.batch_size)
+            canonical_url(self.context, view_name="+batched-comments"),
+            self.next_offset,
+            self.batch_size,
+        )
 
     @property
     def next_offset(self):
@@ -960,8 +1027,10 @@ class BugTaskBatchedCommentsAndActivityView(BugTaskView):
     def _event_groups(self):
         """See `BugTaskView`."""
         batch_size = self.batch_size
-        if (batch_size > (self.total_comments) or
-            not self.has_more_comments_and_activity):
+        if (
+            batch_size > (self.total_comments)
+            or not self.has_more_comments_and_activity
+        ):
             # If the batch size is big enough to encompass all the
             # remaining comments and activity, trim it so that we don't
             # re-show things.
@@ -970,18 +1039,19 @@ class BugTaskBatchedCommentsAndActivityView(BugTaskView):
             else:
                 offset_to_remove = self.offset
             batch_size = (
-                self.total_comments - self.visible_recent_comments -
+                self.total_comments
+                - self.visible_recent_comments
+                -
                 # This last bit is to make sure that _getEventGroups()
                 # doesn't accidentally inflate the batch size later on.
-                offset_to_remove)
-        return self._getEventGroups(
-            batch_size=batch_size, offset=self.offset)
+                offset_to_remove
+            )
+        return self._getEventGroups(batch_size=batch_size, offset=self.offset)
 
     @cachedproperty
     def has_more_comments_and_activity(self):
         """Return True if there are more camments and activity to load."""
-        return (
-            self.next_offset < (self.total_comments + self.total_activity))
+        return self.next_offset < (self.total_comments + self.total_activity)
 
 
 def get_prefix(bugtask):
@@ -1002,15 +1072,15 @@ def get_prefix(bugtask):
     if bugtask.sourcepackagename is not None:
         parts.append(bugtask.sourcepackagename.name)
 
-    return '_'.join(parts)
+    return "_".join(parts)
 
 
 def get_assignee_vocabulary_info(context):
     """The vocabulary of bug task assignees the current user can set."""
     if context.userCanSetAnyAssignee(getUtility(ILaunchBag).user):
-        vocab_name = 'ValidAssignee'
+        vocab_name = "ValidAssignee"
     else:
-        vocab_name = 'AllUserTeamsParticipation'
+        vocab_name = "AllUserTeamsParticipation"
     vocab = vocabulary_registry.get(None, vocab_name)
     return vocab_name, vocab
 
@@ -1030,29 +1100,30 @@ class BugTaskBugWatchMixin:
 
         error_message_mapping = {
             BugWatchActivityStatus.BUG_NOT_FOUND: "%(bugtracker)s bug #"
-                "%(bug)s appears not to exist. Check that the bug "
-                "number is correct.",
+            "%(bug)s appears not to exist. Check that the bug "
+            "number is correct.",
             BugWatchActivityStatus.CONNECTION_ERROR: "Launchpad couldn't "
-                "connect to %(bugtracker)s.",
+            "connect to %(bugtracker)s.",
             BugWatchActivityStatus.INVALID_BUG_ID: "Bug ID %(bug)s isn't "
-                "valid on %(bugtracker)s. Check that the bug ID is "
-                "correct.",
+            "valid on %(bugtracker)s. Check that the bug ID is "
+            "correct.",
             BugWatchActivityStatus.TIMEOUT: "Launchpad's connection to "
-                "%(bugtracker)s timed out.",
+            "%(bugtracker)s timed out.",
             BugWatchActivityStatus.UNKNOWN: "Launchpad couldn't import bug "
-                "#%(bug)s from " "%(bugtracker)s.",
+            "#%(bug)s from "
+            "%(bugtracker)s.",
             BugWatchActivityStatus.UNPARSABLE_BUG: "Launchpad couldn't "
-                "extract a status from %(bug)s on %(bugtracker)s.",
+            "extract a status from %(bug)s on %(bugtracker)s.",
             BugWatchActivityStatus.UNPARSABLE_BUG_TRACKER: "Launchpad "
-                "couldn't determine the version of %(bugtrackertype)s "
-                "running on %(bugtracker)s.",
+            "couldn't determine the version of %(bugtrackertype)s "
+            "running on %(bugtracker)s.",
             BugWatchActivityStatus.UNSUPPORTED_BUG_TRACKER: "Launchpad "
-                "doesn't support importing bugs from %(bugtrackertype)s"
-                " bug trackers.",
+            "doesn't support importing bugs from %(bugtrackertype)s"
+            " bug trackers.",
             BugWatchActivityStatus.PRIVATE_REMOTE_BUG: "The bug is marked as "
-                "private on the remote bug tracker. Launchpad cannot import "
-                "the status of private remote bugs.",
-            }
+            "private on the remote bug tracker. Launchpad cannot import "
+            "the status of private remote bugs.",
+        }
 
         if bug_watch.last_error_type in error_message_mapping:
             message = error_message_mapping[bug_watch.last_error_type]
@@ -1060,20 +1131,22 @@ class BugTaskBugWatchMixin:
             message = bug_watch.last_error_type.description
 
         error_data = {
-            'bug': bug_watch.remotebug,
-            'bugtracker': bug_watch.bugtracker.title,
-            'bugtrackertype': bug_watch.bugtracker.bugtrackertype.title}
+            "bug": bug_watch.remotebug,
+            "bugtracker": bug_watch.bugtracker.title,
+            "bugtrackertype": bug_watch.bugtracker.bugtrackertype.title,
+        }
 
         return {
-            'message': message % error_data,
-            'help_url': '%s#%s' % (
+            "message": message % error_data,
+            "help_url": "%s#%s"
+            % (
                 canonical_url(bug_watch, view_name="+error-help"),
-                bug_watch.last_error_type.name),
-            }
+                bug_watch.last_error_type.name,
+            ),
+        }
 
 
 class BugTaskPrivilegeMixin:
-
     @cachedproperty
     def user_has_privileges(self):
         """Is the user privileged? That is, an admin, pillar owner, driver
@@ -1087,18 +1160,20 @@ class BugTaskPrivilegeMixin:
 class IBugTaskEditForm(IBugTask):
 
     sourcepackagename = copy_field(
-        IBugTask['sourcepackagename'],
-        vocabularyName='DistributionSourcePackage')
+        IBugTask["sourcepackagename"],
+        vocabularyName="DistributionSourcePackage",
+    )
 
 
-class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
-                      BugTaskPrivilegeMixin):
+class BugTaskEditView(
+    LaunchpadEditFormView, BugTaskBugWatchMixin, BugTaskPrivilegeMixin
+):
     """The view class used for the task +editstatus page."""
 
     @property
     def schema(self):
         """See `LaunchpadFormView`."""
-        if bool(getFeatureFlag('disclosure.dsp_picker.enabled')):
+        if bool(getFeatureFlag("disclosure.dsp_picker.enabled")):
             return IBugTaskEditForm
         else:
             return IBugTask
@@ -1110,15 +1185,20 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
 
     milestone_source = None
     user_is_subscribed = None
-    edit_form = ViewPageTemplateFile('../templates/bugtask-edit-form.pt')
+    edit_form = ViewPageTemplateFile("../templates/bugtask-edit-form.pt")
 
     _next_url_override = None
 
     # The field names that we use by default. This list will be mutated
     # depending on the current context and the permissions of the user viewing
     # the form.
-    default_field_names = ['assignee', 'bugwatch', 'importance', 'milestone',
-                           'status']
+    default_field_names = [
+        "assignee",
+        "bugwatch",
+        "importance",
+        "milestone",
+        "status",
+    ]
     custom_widget_target = BugTaskTargetWidget
     custom_widget_sourcepackagename = BugTaskSourcePackageNameWidget
     custom_widget_bugwatch = BugTaskBugWatchWidget
@@ -1130,7 +1210,7 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
             self.user_is_subscribed = self.context.bug.isSubscribed(self.user)
         super().initialize()
 
-    page_title = 'Edit status'
+    page_title = "Edit status"
 
     @property
     def show_target_widget(self):
@@ -1142,8 +1222,9 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
         # SourcePackage tasks can have only their sourcepackagename changed.
         # Conjoinment means we can't rely on editing the
         # DistributionSourcePackage task for this :(
-        return (IDistroSeries.providedBy(self.context.target) or
-                ISourcePackage.providedBy(self.context.target))
+        return IDistroSeries.providedBy(
+            self.context.target
+        ) or ISourcePackage.providedBy(self.context.target)
 
     @cachedproperty
     def field_names(self):
@@ -1165,34 +1246,38 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
             # Don't edit self.field_names directly, because it's shared by all
             # BugTaskEditView instances.
             editable_field_names = set(self.default_field_names)
-            editable_field_names.discard('bugwatch')
+            editable_field_names.discard("bugwatch")
 
             # XXX: Brad Bollenbach 2006-09-29 bug=63000: Permission checking
             # doesn't belong here!
             if not self.user_has_privileges:
-                if 'milestone' in editable_field_names:
+                if "milestone" in editable_field_names:
                     editable_field_names.remove("milestone")
-                if 'importance' in editable_field_names:
+                if "importance" in editable_field_names:
                     editable_field_names.remove("importance")
         else:
-            editable_field_names = {'bugwatch'}
+            editable_field_names = {"bugwatch"}
             if self.context.bugwatch is None:
-                editable_field_names.update(('status', 'assignee'))
-                if ('importance' in self.default_field_names
-                    and self.user_has_privileges):
-                    editable_field_names.add('importance')
+                editable_field_names.update(("status", "assignee"))
+                if (
+                    "importance" in self.default_field_names
+                    and self.user_has_privileges
+                ):
+                    editable_field_names.add("importance")
             else:
                 bugtracker = self.context.bugwatch.bugtracker
                 if bugtracker.bugtrackertype == BugTrackerType.EMAILADDRESS:
-                    editable_field_names.add('status')
-                    if ('importance' in self.default_field_names
-                        and self.user_has_privileges):
-                        editable_field_names.add('importance')
+                    editable_field_names.add("status")
+                    if (
+                        "importance" in self.default_field_names
+                        and self.user_has_privileges
+                    ):
+                        editable_field_names.add("importance")
 
         if self.show_target_widget:
-            editable_field_names.add('target')
+            editable_field_names.add("target")
         elif self.show_sourcepackagename_widget:
-            editable_field_names.add('sourcepackagename')
+            editable_field_names.add("sourcepackagename")
 
         # To help with caching, return an immutable object.
         return frozenset(editable_field_names)
@@ -1241,24 +1326,29 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
         super().setUpFields()
         read_only_field_names = self._getReadOnlyFieldNames()
 
-        if 'target' in self.editable_field_names:
-            self.form_fields = self.form_fields.omit('target')
-            target_field = copy_field(IBugTask['target'], readonly=False)
+        if "target" in self.editable_field_names:
+            self.form_fields = self.form_fields.omit("target")
+            target_field = copy_field(IBugTask["target"], readonly=False)
             self.form_fields += formlib.form.Fields(target_field)
 
         # The status field is a special case because we alter the vocabulary
         # it uses based on the permissions of the user viewing form.
-        if 'status' in self.editable_field_names:
+        if "status" in self.editable_field_names:
             if self.user is None:
                 status_noshow = set(BugTaskStatus.items)
             else:
                 status_noshow = {
-                    BugTaskStatus.UNKNOWN, BugTaskStatus.EXPIRED,
-                    BugTaskStatus.DOESNOTEXIST}
+                    BugTaskStatus.UNKNOWN,
+                    BugTaskStatus.EXPIRED,
+                    BugTaskStatus.DOESNOTEXIST,
+                }
                 status_noshow.update(
-                    status for status in BugTaskStatus.items
+                    status
+                    for status in BugTaskStatus.items
                     if not self.context.canTransitionToStatus(
-                        status, self.user))
+                        status, self.user
+                    )
+                )
 
             if self.context.status in status_noshow:
                 # The user has to be able to see the current value.
@@ -1270,13 +1360,17 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
             # by EnumeratedType have their name as the token and here we need
             # the title as the token for backwards compatibility.
             status_items = [
-                (item.title, item) for item in BugTaskStatus.items
-                if item not in status_noshow]
+                (item.title, item)
+                for item in BugTaskStatus.items
+                if item not in status_noshow
+            ]
             status_field = Choice(
-                __name__='status', title=self.schema['status'].title,
-                vocabulary=SimpleVocabulary.fromItems(status_items))
+                __name__="status",
+                title=self.schema["status"].title,
+                vocabulary=SimpleVocabulary.fromItems(status_items),
+            )
 
-            self.form_fields = self.form_fields.omit('status')
+            self.form_fields = self.form_fields.omit("status")
             self.form_fields += formlib.form.Fields(status_field)
 
         # If we have a milestone vocabulary already, create a new field
@@ -1284,14 +1378,15 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
         if self.milestone_source is not None:
             milestone_source = self.milestone_source
             milestone_field = Choice(
-                __name__='milestone',
-                title=self.schema['milestone'].title,
-                source=milestone_source, required=False)
+                __name__="milestone",
+                title=self.schema["milestone"].title,
+                source=milestone_source,
+                required=False,
+            )
         else:
-            milestone_field = copy_field(
-                IBugTask['milestone'], readonly=False)
+            milestone_field = copy_field(IBugTask["milestone"], readonly=False)
 
-        self.form_fields = self.form_fields.omit('milestone')
+        self.form_fields = self.form_fields.omit("milestone")
         self.form_fields += formlib.form.Fields(milestone_field)
 
         for field in read_only_field_names:
@@ -1299,42 +1394,58 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
 
         # In cases where the status or importance fields are read only we give
         # them a custom widget so that they are rendered correctly.
-        for field in ['status', 'importance']:
+        for field in ["status", "importance"]:
             if field in read_only_field_names:
                 self.form_fields[field].custom_widget = CustomWidgetFactory(
-                    DBItemDisplayWidget)
+                    DBItemDisplayWidget
+                )
 
-        if 'importance' not in read_only_field_names:
+        if "importance" not in read_only_field_names:
             # Users shouldn't be able to set a bugtask's importance to
             # `UNKNOWN`, only bug watches do that.
             importance_vocab_items = [
-                item for item in BugTaskImportance.items.items
-                if item != BugTaskImportance.UNKNOWN]
-            self.form_fields = self.form_fields.omit('importance')
+                item
+                for item in BugTaskImportance.items.items
+                if item != BugTaskImportance.UNKNOWN
+            ]
+            self.form_fields = self.form_fields.omit("importance")
             self.form_fields += formlib.form.Fields(
-                Choice(__name__='importance',
-                       title=_('Importance'),
-                       values=importance_vocab_items,
-                       default=BugTaskImportance.UNDECIDED))
+                Choice(
+                    __name__="importance",
+                    title=_("Importance"),
+                    values=importance_vocab_items,
+                    default=BugTaskImportance.UNDECIDED,
+                )
+            )
 
         if self.context.pillar.official_malone:
-            self.form_fields = self.form_fields.omit('bugwatch')
+            self.form_fields = self.form_fields.omit("bugwatch")
 
-        elif (self.context.bugwatch is not None and
-            self.form_fields.get('assignee', False)):
-            self.form_fields['assignee'].custom_widget = CustomWidgetFactory(
-                AssigneeDisplayWidget)
+        elif self.context.bugwatch is not None and self.form_fields.get(
+            "assignee", False
+        ):
+            self.form_fields["assignee"].custom_widget = CustomWidgetFactory(
+                AssigneeDisplayWidget
+            )
 
-        if (self.context.bugwatch is None and
-            self.form_fields.get('assignee', False)):
+        if self.context.bugwatch is None and self.form_fields.get(
+            "assignee", False
+        ):
             # Make the assignee field editable
-            self.form_fields = self.form_fields.omit('assignee')
+            self.form_fields = self.form_fields.omit("assignee")
             vocabulary, ignored = get_assignee_vocabulary_info(self.context)
-            self.form_fields += formlib.form.Fields(PersonChoice(
-                __name__='assignee', title=_('Assigned to'), required=False,
-                vocabulary=vocabulary, readonly=False))
-            self.form_fields['assignee'].custom_widget = CustomWidgetFactory(
-                BugTaskAssigneeWidget)
+            self.form_fields += formlib.form.Fields(
+                PersonChoice(
+                    __name__="assignee",
+                    title=_("Assigned to"),
+                    required=False,
+                    vocabulary=vocabulary,
+                    readonly=False,
+                )
+            )
+            self.form_fields["assignee"].custom_widget = CustomWidgetFactory(
+                BugTaskAssigneeWidget
+            )
 
     def _getReadOnlyFieldNames(self):
         """Return the names of fields that will be rendered read only."""
@@ -1347,33 +1458,36 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
         else:
             editable_field_names = self.editable_field_names
             read_only_field_names = [
-                field_name for field_name in self.field_names
-                if field_name not in editable_field_names]
+                field_name
+                for field_name in self.field_names
+                if field_name not in editable_field_names
+            ]
 
         return read_only_field_names
 
     def validate(self, data):
-        if self.show_sourcepackagename_widget and 'sourcepackagename' in data:
-            data['target'] = self.context.distroseries
-            spn_or_dsp = data.get('sourcepackagename')
+        if self.show_sourcepackagename_widget and "sourcepackagename" in data:
+            data["target"] = self.context.distroseries
+            spn_or_dsp = data.get("sourcepackagename")
             if spn_or_dsp:
                 if IDistributionSourcePackage.providedBy(spn_or_dsp):
                     spn = spn_or_dsp.sourcepackagename
                 else:
                     spn = spn_or_dsp
-                data['target'] = data['target'].getSourcePackage(spn)
-            del data['sourcepackagename']
-            error_field = 'sourcepackagename'
+                data["target"] = data["target"].getSourcePackage(spn)
+            del data["sourcepackagename"]
+            error_field = "sourcepackagename"
         else:
-            error_field = 'target'
+            error_field = "target"
 
-        new_target = data.get('target')
+        new_target = data.get("target")
         if new_target and new_target != self.context.target:
             try:
                 # The validity of the source package has already been checked
                 # by the bug target widget.
                 self.context.validateTransitionToTarget(
-                    new_target, check_source_package=False)
+                    new_target, check_source_package=False
+                )
             except IllegalTarget as e:
                 self.setFieldError(error_field, e.args[0])
 
@@ -1388,10 +1502,11 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
             context = self.context
         bugtask = context
 
-        if self.request.form.get('subscribe', False):
+        if self.request.form.get("subscribe", False):
             bugtask.bug.subscribe(self.user, self.user)
             self.request.response.addNotification(
-                "You have subscribed to this bug report.")
+                "You have subscribed to this bug report."
+            )
 
         # Save the field names we extract from the form in a separate
         # list, because we modify this list of names later if the
@@ -1401,7 +1516,8 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
         data_to_apply = data.copy()
 
         bugtask_before_modification = Snapshot(
-            bugtask, providing=providedBy(bugtask))
+            bugtask, providing=providedBy(bugtask)
+        )
 
         # If the user is reassigning an upstream task to a different
         # product, we'll clear out the milestone value, to avoid
@@ -1414,15 +1530,17 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
         milestone_ignored = False
         missing = object()
         new_target = new_values.pop("target", missing)
-        if (new_target is not missing and
-            bugtask.target.pillar != new_target.pillar):
+        if (
+            new_target is not missing
+            and bugtask.target.pillar != new_target.pillar
+        ):
             # We clear the milestone value if one was already set. We ignore
             # the milestone value if it was currently None, and the user tried
             # to set a milestone value while also changing the product. This
             # allows us to provide slightly clearer feedback messages.
             if bugtask.milestone:
                 milestone_cleared = bugtask.milestone
-            elif new_values.get('milestone') is not None:
+            elif new_values.get("milestone") is not None:
                 milestone_ignored = True
 
             # Regardless of the user's permission, the milestone
@@ -1432,21 +1550,23 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
             # whose changes we want to apply, because we don't want
             # the form machinery to try and set this value back to
             # what it was!
-            data_to_apply.pop('milestone', None)
+            data_to_apply.pop("milestone", None)
 
         # We special case setting target, status and assignee, because
         # there's a workflow associated with changes to these fields.
-        for manual_field in ('target', 'status', 'assignee'):
+        for manual_field in ("target", "status", "assignee"):
             data_to_apply.pop(manual_field, None)
 
         # We grab the comment_on_change field before we update bugtask so as
         # to avoid problems accessing the field if the user has changed the
         # product of the BugTask.
         comment_on_change = self.request.form.get(
-            "%s.comment_on_change" % self.prefix)
+            "%s.comment_on_change" % self.prefix
+        )
 
         changed = formlib.form.applyChanges(
-            bugtask, self.form_fields, data_to_apply, self.adapters)
+            bugtask, self.form_fields, data_to_apply, self.adapters
+        )
 
         # Set the "changed" flag properly, just in case status and/or assignee
         # happen to be the only values that changed. We explicitly verify that
@@ -1464,20 +1584,21 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
         if milestone_cleared:
             self.request.response.addWarningNotification(
                 "The %s milestone setting has been removed because "
-                "you reassigned the bug to %s." % (
-                    milestone_cleared.displayname,
-                    bugtask.bugtargetdisplayname))
+                "you reassigned the bug to %s."
+                % (milestone_cleared.displayname, bugtask.bugtargetdisplayname)
+            )
         elif milestone_ignored:
             self.request.response.addWarningNotification(
                 "The milestone setting was ignored because "
-                "you reassigned the bug to %s." %
-                bugtask.bugtargetdisplayname)
+                "you reassigned the bug to %s." % bugtask.bugtargetdisplayname
+            )
 
         if comment_on_change:
             bugtask.bug.newMessage(
                 owner=getUtility(ILaunchBag).user,
                 subject=bugtask.bug.followup_subject(),
-                content=comment_on_change)
+                content=comment_on_change,
+            )
 
         new_status = new_values.pop("status", missing)
         new_assignee = new_values.pop("assignee", missing)
@@ -1490,34 +1611,38 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
                 # since other changes may have been made.
                 transaction.abort()
                 self.setFieldError(
-                    'status',
+                    "status",
                     "Only the Bug Supervisor for %s can set the bug's "
-                    "status to %s" %
-                    (bugtask.target.displayname, new_status.title))
+                    "status to %s"
+                    % (bugtask.target.displayname, new_status.title),
+                )
                 return
 
         if new_assignee is not missing and bugtask.assignee != new_assignee:
             if new_assignee is not None and new_assignee != self.user:
                 is_contributor = new_assignee.isBugContributorInTarget(
-                    user=self.user, target=bugtask.pillar)
+                    user=self.user, target=bugtask.pillar
+                )
                 if not is_contributor:
                     # If we have a new assignee who isn't a bug
                     # contributor in this pillar, we display a warning
                     # to the user, in case they made a mistake.
                     self.request.response.addWarningNotification(
                         structured(
-                        """<a href="%s">%s</a>
+                            """<a href="%s">%s</a>
                         did not previously have any assigned bugs in
                         <a href="%s">%s</a>.
                         <br /><br />
                         If this bug was assigned by mistake,
                         you may <a href="%s/+editstatus"
                         >change the assignment</a>.""",
-                        canonical_url(new_assignee),
-                        new_assignee.displayname,
-                        canonical_url(bugtask.pillar),
-                        bugtask.pillar.title,
-                        canonical_url(bugtask)))
+                            canonical_url(new_assignee),
+                            new_assignee.displayname,
+                            canonical_url(bugtask.pillar),
+                            bugtask.pillar.title,
+                            canonical_url(bugtask),
+                        )
+                    )
             changed = True
             bugtask.transitionToAssignee(new_assignee)
 
@@ -1527,21 +1652,21 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
                 # Reset the status and importance to the default values,
                 # since Unknown isn't selectable in the UI.
                 bugtask.transitionToStatus(
-                    IBugTask['status'].default, bug_importer)
+                    IBugTask["status"].default, bug_importer
+                )
                 bugtask.transitionToImportance(
-                    IBugTask['importance'].default, bug_importer)
+                    IBugTask["importance"].default, bug_importer
+                )
             else:
-                #XXX: Bjorn Tillenius 2006-03-01:
+                # XXX: Bjorn Tillenius 2006-03-01:
                 #     Reset the bug task's status information. The right
                 #     thing would be to convert the bug watch's status to a
                 #     Launchpad status, but it's not trivial to do at the
                 #     moment. I will fix this later.
-                bugtask.transitionToStatus(
-                    BugTaskStatus.UNKNOWN,
-                    bug_importer)
+                bugtask.transitionToStatus(BugTaskStatus.UNKNOWN, bug_importer)
                 bugtask.transitionToImportance(
-                    BugTaskImportance.UNKNOWN,
-                    bug_importer)
+                    BugTaskImportance.UNKNOWN, bug_importer
+                )
                 bugtask.transitionToAssignee(None)
 
         if changed:
@@ -1549,7 +1674,9 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
                 ObjectModifiedEvent(
                     object=bugtask,
                     object_before_modification=bugtask_before_modification,
-                    edited_fields=field_names))
+                    edited_fields=field_names,
+                )
+            )
 
             # We clear the known views cache because the bug may not be
             # viewable anymore by the current user. If the bug is not
@@ -1560,23 +1687,25 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
                 self.request.response.addWarningNotification(
                     "The bug you have just updated is now a private bug for "
                     "%s. You do not have permission to view such bugs."
-                    % bugtask.pillar.displayname)
+                    % bugtask.pillar.displayname
+                )
                 self._next_url_override = canonical_url(
-                    new_target.pillar, rootsite='bugs')
+                    new_target.pillar, rootsite="bugs"
+                )
 
-        if (bugtask.sourcepackagename and (
-            self.widgets.get('target') or
-            self.widgets.get('sourcepackagename'))):
+        if bugtask.sourcepackagename and (
+            self.widgets.get("target") or self.widgets.get("sourcepackagename")
+        ):
             real_package_name = bugtask.sourcepackagename.name
 
             # We get entered_package_name directly from the form here, since
             # validating the sourcepackagename field mutates its value in to
             # the one already in real_package_name, which makes our comparison
             # of the two below useless.
-            if self.widgets.get('sourcepackagename'):
-                field_name = self.widgets['sourcepackagename'].name
+            if self.widgets.get("sourcepackagename"):
+                field_name = self.widgets["sourcepackagename"].name
             else:
-                field_name = self.widgets['target'].package_widget.name
+                field_name = self.widgets["target"].package_widget.name
             entered_package_name = self.request.form.get(field_name)
 
             if real_package_name != entered_package_name:
@@ -1585,11 +1714,14 @@ class BugTaskEditView(LaunchpadEditFormView, BugTaskBugWatchMixin,
                 self.request.response.addNotification(
                     "'%(entered_package)s' is a binary package. This bug has"
                     " been assigned to its source package '%(real_package)s'"
-                    " instead." %
-                    {'entered_package': entered_package_name,
-                     'real_package': real_package_name})
+                    " instead."
+                    % {
+                        "entered_package": entered_package_name,
+                        "real_package": real_package_name,
+                    }
+                )
 
-    @action('Save Changes', name='save')
+    @action("Save Changes", name="save")
     def save_action(self, action, data):
         """Update the bugtask with the form data."""
         self.updateContextFromData(data)
@@ -1601,7 +1733,7 @@ class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView):
     schema = IBugTask
     field_names = []
 
-    label = 'Remove bug task'
+    label = "Remove bug task"
     page_title = label
 
     @property
@@ -1611,13 +1743,14 @@ class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView):
             return self._next_url or self._return_url
         return None
 
-    @action('Delete', name='delete_bugtask')
+    @action("Delete", name="delete_bugtask")
     def delete_bugtask_action(self, action, data):
         bugtask = self.context
         bug = bugtask.bug
-        deleted_bugtask_url = canonical_url(self.context, rootsite='bugs')
-        success_message = ("This bug no longer affects %s."
-                    % bugtask.bugtargetdisplayname)
+        deleted_bugtask_url = canonical_url(self.context, rootsite="bugs")
+        success_message = (
+            "This bug no longer affects %s." % bugtask.bugtargetdisplayname
+        )
         error_message = None
         # We set the next_url here before the bugtask is deleted since later
         # the bugtask will not be available if required to construct the url.
@@ -1631,8 +1764,9 @@ class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView):
             self.request.response.addErrorNotification(error_message)
         if self.request.is_ajax:
             if error_message:
-                self.request.response.setHeader('Content-type',
-                    'application/json')
+                self.request.response.setHeader(
+                    "Content-type", "application/json"
+                )
                 return dumps(None)
             launchbag = getUtility(ILaunchBag)
             launchbag.add(bug.default_bugtask)
@@ -1642,15 +1776,15 @@ class BugTaskDeletionView(ReturnToReferrerMixin, LaunchpadFormView):
             # We can't do the redirect here since the XHR caller won't see it
             # so we return the URL to go to and let the caller do it.
             if self._return_url == deleted_bugtask_url:
-                next_url = canonical_url(
-                    bug.default_bugtask, rootsite='bugs')
-                self.request.response.setHeader('Content-type',
-                    'application/json')
+                next_url = canonical_url(bug.default_bugtask, rootsite="bugs")
+                self.request.response.setHeader(
+                    "Content-type", "application/json"
+                )
                 return dumps(dict(bugtask_url=next_url))
             # No redirect required so return the new bugtask table HTML.
             view = getMultiAdapter(
-                (bug, self.request),
-                name='+bugtasks-and-nominations-table')
+                (bug, self.request), name="+bugtasks-and-nominations-table"
+            )
             view.initialize()
             return view.render()
 
@@ -1664,23 +1798,42 @@ def bugtask_sort_key(bugtask):
         key = (None, bugtask.target.displayname, None, None, None, None)
     elif IDistroSeries.providedBy(bugtask.target):
         key = (
-            None, bugtask.target.distribution.displayname,
-            bugtask.target.name, None, None, None)
+            None,
+            bugtask.target.distribution.displayname,
+            bugtask.target.name,
+            None,
+            None,
+            None,
+        )
     elif IDistributionSourcePackage.providedBy(bugtask.target):
         key = (
             bugtask.target.sourcepackagename.name,
-            bugtask.target.distribution.displayname, None, None, None, None)
+            bugtask.target.distribution.displayname,
+            None,
+            None,
+            None,
+            None,
+        )
     elif ISourcePackage.providedBy(bugtask.target):
         key = (
             bugtask.target.sourcepackagename.name,
             bugtask.target.distribution.displayname,
-            Version(bugtask.target.distroseries.version), None, None, None)
+            Version(bugtask.target.distroseries.version),
+            None,
+            None,
+            None,
+        )
     elif IProduct.providedBy(bugtask.target):
         key = (None, None, None, bugtask.target.displayname, None, None)
     elif IProductSeries.providedBy(bugtask.target):
         key = (
-            None, None, None, bugtask.target.product.displayname,
-            bugtask.target.name, None)
+            None,
+            None,
+            None,
+            bugtask.target.product.displayname,
+            bugtask.target.name,
+            None,
+        )
     elif IOCIProject.providedBy(bugtask.target):
         ociproject = bugtask.target
         pillar = ociproject.pillar
@@ -1715,9 +1868,11 @@ class BugTasksNominationsView(LaunchpadView):
     def displayAlsoAffectsLinks(self):
         """Return True if the Also Affects links should be displayed."""
         return (
-            check_permission('launchpad.Edit', self.context) and
+            check_permission("launchpad.Edit", self.context)
+            and
             # Hide the links when the bug is viewed in a CVE context.
-            self.request.getNearest(ICveSet) == (None, None))
+            self.request.getNearest(ICveSet) == (None, None)
+        )
 
     @cachedproperty
     def current_user_affected_status(self):
@@ -1729,17 +1884,16 @@ class BugTasksNominationsView(LaunchpadView):
         """A javascript literal indicating if the user is affected."""
         affected = self.current_user_affected_status
         if affected is None:
-            return 'null'
+            return "null"
         elif affected:
-            return 'true'
+            return "true"
         else:
-            return 'false'
+            return "false"
 
     @cachedproperty
     def other_users_affected_count(self):
-        """The number of other users affected by this bug.
-        """
-        if getFeatureFlag('bugs.affected_count_includes_dupes.disabled'):
+        """The number of other users affected by this bug."""
+        if getFeatureFlag("bugs.affected_count_includes_dupes.disabled"):
             if self.current_user_affected_status:
                 return self.context.users_affected_count - 1
             else:
@@ -1753,7 +1907,7 @@ class BugTasksNominationsView(LaunchpadView):
 
         Counting across duplicates may be disabled at run time.
         """
-        if getFeatureFlag('bugs.affected_count_includes_dupes.disabled'):
+        if getFeatureFlag("bugs.affected_count_includes_dupes.disabled"):
             return self.context.users_affected_count
         else:
             return self.context.users_affected_count_with_dupes
@@ -1773,7 +1927,8 @@ class BugTasksNominationsView(LaunchpadView):
             elif other_affected > 1:
                 return (
                     "This bug affects %d people. Does this bug "
-                    "affect you?" % other_affected)
+                    "affect you?" % other_affected
+                )
             else:
                 return "Does this bug affect you?"
         elif me_affected is True:
@@ -1783,7 +1938,8 @@ class BugTasksNominationsView(LaunchpadView):
                 return "This bug affects you and 1 other person"
             else:
                 return "This bug affects you and %d other people" % (
-                    other_affected)
+                    other_affected
+                )
         else:
             if other_affected == 0:
                 return "This bug doesn't affect you"
@@ -1791,7 +1947,8 @@ class BugTasksNominationsView(LaunchpadView):
                 return "This bug affects 1 person, but not you"
             elif other_affected > 1:
                 return "This bug affects %d people, but not you" % (
-                    other_affected)
+                    other_affected
+                )
 
     @cachedproperty
     def anon_affected_statement(self):
@@ -1881,10 +2038,14 @@ class BugTasksTableView(LaunchpadView):
         # bug, hence they can see any assignees.
         # The security adaptor will do the job also but we don't want or need
         # the expense of running several complex SQL queries.
-        authorised_people = [task.assignee for task in self.bugtasks
-                             if task.assignee is not None]
+        authorised_people = [
+            task.assignee
+            for task in self.bugtasks
+            if task.assignee is not None
+        ]
         precache_permission_for_objects(
-            self.request, 'launchpad.LimitedView', authorised_people)
+            self.request, "launchpad.LimitedView", authorised_people
+        )
 
         distro_packages = defaultdict(list)
         distro_series_packages = defaultdict(list)
@@ -1892,20 +2053,25 @@ class BugTasksTableView(LaunchpadView):
             target = bugtask.target
             if IDistributionSourcePackage.providedBy(target):
                 distro_packages[target.distribution].append(
-                    target.sourcepackagename)
+                    target.sourcepackagename
+                )
             if ISourcePackage.providedBy(target):
                 distro_series_packages[target.distroseries].append(
-                    target.sourcepackagename)
+                    target.sourcepackagename
+                )
         distro_set = getUtility(IDistributionSet)
-        self.target_releases = dict(distro_set.getCurrentSourceReleases(
-            distro_packages))
+        self.target_releases = dict(
+            distro_set.getCurrentSourceReleases(distro_packages)
+        )
         distro_series_set = getUtility(IDistroSeriesSet)
         self.target_releases.update(
-            distro_series_set.getCurrentSourceReleases(
-                distro_series_packages))
+            distro_series_set.getCurrentSourceReleases(distro_series_packages)
+        )
         ids = set()
-        for release_person_ids in map(attrgetter('creatorID', 'maintainerID'),
-            self.target_releases.values()):
+        for release_person_ids in map(
+            attrgetter("creatorID", "maintainerID"),
+            self.target_releases.values(),
+        ):
             ids.update(release_person_ids)
         ids.discard(None)
         if ids:
@@ -1919,53 +2085,60 @@ class BugTasksTableView(LaunchpadView):
     def milestones(self):
         if self.bugtasks:
             bugtask_set = getUtility(IBugTaskSet)
-            return list(
-                bugtask_set.getBugTaskTargetMilestones(self.bugtasks))
+            return list(bugtask_set.getBugTaskTargetMilestones(self.bugtasks))
         else:
             return []
 
     def getTargetLinkTitle(self, target):
         """Return text to put as the title for the link to the target."""
-        if not (IDistributionSourcePackage.providedBy(target) or
-                ISourcePackage.providedBy(target)):
+        if not (
+            IDistributionSourcePackage.providedBy(target)
+            or ISourcePackage.providedBy(target)
+        ):
             return None
         current_release = self.target_releases.get(target)
         if current_release is None:
             return "No current release for this source package in %s" % (
-                target.distribution.displayname)
+                target.distribution.displayname
+            )
         uploader = current_release.creator
         maintainer = current_release.maintainer
         return (
             "Latest release: %(version)s, uploaded to %(component)s"
             " on %(date_uploaded)s by %(uploader)s,"
-            " maintained by %(maintainer)s" % dict(
+            " maintained by %(maintainer)s"
+            % dict(
                 version=current_release.version,
                 component=current_release.component.name,
                 date_uploaded=current_release.dateuploaded,
                 uploader=uploader.unique_displayname,
                 maintainer=maintainer.unique_displayname,
-                ))
+            )
+        )
 
-    def _getTableRowView(self, context, is_converted_to_question,
-                         is_conjoined_replica):
+    def _getTableRowView(
+        self, context, is_converted_to_question, is_conjoined_replica
+    ):
         """Get the view for the context, and initialize it.
 
         The view's is_conjoined_replica and is_converted_to_question
         attributes are set, as well as the edit view.
         """
         view = getMultiAdapter(
-            (context, self.request),
-            name='+bugtasks-and-nominations-table-row')
+            (context, self.request), name="+bugtasks-and-nominations-table-row"
+        )
         view.is_converted_to_question = is_converted_to_question
         view.is_conjoined_replica = is_conjoined_replica
 
         view.edit_view = getMultiAdapter(
-            (context, self.request), name='+edit-form')
+            (context, self.request), name="+edit-form"
+        )
         view.milestone_source = self.caching_milestone_vocabulary
         if IBugTask.providedBy(context):
             view.target_link_title = self.getTargetLinkTitle(context.target)
-            view.edit_view.milestone_source = (
-                BugTaskMilestoneVocabulary(context, self.milestones))
+            view.edit_view.milestone_source = BugTaskMilestoneVocabulary(
+                context, self.milestones
+            )
         view.edit_view.user_is_subscribed = self.user_is_subscribed
         # Hint to optimize when there are many bugtasks.
         view.many_bugtasks = self.many_bugtasks
@@ -1995,11 +2168,14 @@ class BugTasksTableView(LaunchpadView):
         nominations = list(bug.getNominations())
         # Eager load validity for all the persons we know of that will be
         # displayed.
-        ids = set(map(attrgetter('owner_id'), nominations))
+        ids = set(map(attrgetter("owner_id"), nominations))
         ids.discard(None)
         if ids:
-            list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-                ids, need_validity=True))
+            list(
+                getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                    ids, need_validity=True
+                )
+            )
 
         # Build a cache we can pass on to getConjoinedPrimary(), so that
         # it doesn't have to iterate over all the bug tasks in each loop
@@ -2025,31 +2201,40 @@ class BugTasksTableView(LaunchpadView):
                 bugtask_and_nomination_views.append(
                     getMultiAdapter(
                         (parent, self.request),
-                        name='+bugtasks-and-nominations-table-row'))
+                        name="+bugtasks-and-nominations-table-row",
+                    )
+                )
 
             conjoined_primary = bugtask.getConjoinedPrimary(
-                all_bugtasks, bugtasks_by_package)
+                all_bugtasks, bugtasks_by_package
+            )
             view = self._getTableRowView(
-                bugtask, is_converted_to_question,
-                conjoined_primary is not None)
+                bugtask,
+                is_converted_to_question,
+                conjoined_primary is not None,
+            )
             bugtask_and_nomination_views.append(view)
             target = bugtask.product or bugtask.distribution
             if not target:
                 continue
 
             target_nominations = bug.getNominations(
-                target, nominations=nominations)
+                target, nominations=nominations
+            )
             bugtask_and_nomination_views.extend(
                 self._getTableRowView(
-                    nomination, is_converted_to_question, False)
+                    nomination, is_converted_to_question, False
+                )
                 for nomination in target_nominations
-                if nomination.status != BugNominationStatus.APPROVED)
+                if nomination.status != BugNominationStatus.APPROVED
+            )
 
         return bugtask_and_nomination_views
 
 
-class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
-                          BugTaskPrivilegeMixin):
+class BugTaskTableRowView(
+    LaunchpadView, BugTaskBugWatchMixin, BugTaskPrivilegeMixin
+):
     """Browser class for rendering a bugtask row on the bug page."""
 
     is_conjoined_replica = None
@@ -2058,7 +2243,8 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
     many_bugtasks = False
 
     template = ViewPageTemplateFile(
-        '../templates/bugtask-tasks-and-nominations-table-row.pt')
+        "../templates/bugtask-tasks-and-nominations-table-row.pt"
+    )
 
     def __init__(self, context, request):
         super().__init__(context, request)
@@ -2072,8 +2258,9 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         super().initialize()
         link = canonical_url(self.context)
         task_link = edit_link = canonical_url(
-                                    self.context, view_name='+editstatus')
-        delete_link = canonical_url(self.context, view_name='+delete')
+            self.context, view_name="+editstatus"
+        )
+        delete_link = canonical_url(self.context, view_name="+delete")
         bugtask_id = self.context.id
         launchbag = getUtility(ILaunchBag)
         is_primary = self.context.id == launchbag.bugtask.id
@@ -2082,9 +2269,10 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             # 150+ bugtasks, it can save three or four seconds of rendering
             # time.
             expandable=(
-                not self.many_bugtasks and
-                self.user_can_edit and
-                self.canSeeTaskDetails()),
+                not self.many_bugtasks
+                and self.user_can_edit
+                and self.canSeeTaskDetails()
+            ),
             indent_task=ISeriesBugTarget.providedBy(self.context.target),
             is_conjoined_replica=self.is_conjoined_replica,
             task_link=task_link,
@@ -2092,28 +2280,28 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             can_edit=self.user_can_edit,
             link=link,
             id=bugtask_id,
-            row_id='tasksummary%d' % bugtask_id,
-            form_row_id='task%d' % bugtask_id,
-            row_css_class='highlight' if is_primary else None,
+            row_id="tasksummary%d" % bugtask_id,
+            form_row_id="task%d" % bugtask_id,
+            row_css_class="highlight" if is_primary else None,
             target_link=canonical_url(self.context.target),
             target_link_title=self.target_link_title,
             user_can_delete=self.user_can_delete_bugtask,
             delete_link=delete_link,
             user_can_edit_importance=self.user_has_privileges,
-            importance_css_class='importance' + self.context.importance.name,
+            importance_css_class="importance" + self.context.importance.name,
             importance_title=self.context.importance.title,
             # We always look up all milestones, so there's no harm
             # using len on the list here and avoid the COUNT query.
             target_has_milestones=len(self._visible_milestones) > 0,
             user_can_edit_status=self.user_can_edit_status,
-            )
+        )
 
         if not self.many_bugtasks:
             cache = IJSONRequestCache(self.request)
-            bugtask_data = cache.objects.get('bugtask_data', None)
+            bugtask_data = cache.objects.get("bugtask_data", None)
             if bugtask_data is None:
                 bugtask_data = dict()
-                cache.objects['bugtask_data'] = bugtask_data
+                cache.objects["bugtask_data"] = bugtask_data
             bugtask_data[bugtask_id] = self.bugtask_config()
 
     def canSeeTaskDetails(self):
@@ -2124,15 +2312,19 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         It is independent of whether they can *change* the status; you
         need to expand the details to see any milestone set.
         """
-        assert self.is_conjoined_replica is not None, (
-            'is_conjoined_replica should be set before rendering the page.')
+        assert (
+            self.is_conjoined_replica is not None
+        ), "is_conjoined_replica should be set before rendering the page."
         assert self.is_converted_to_question is not None, (
-            'is_converted_to_question should be set before rendering the'
-            ' page.')
-        return (self.displayEditForm() and
-                not self.is_conjoined_replica and
-                self.context.bug.duplicateof is None and
-                not self.is_converted_to_question)
+            "is_converted_to_question should be set before rendering the"
+            " page."
+        )
+        return (
+            self.displayEditForm()
+            and not self.is_conjoined_replica
+            and self.context.bug.duplicateof is None
+            and not self.is_converted_to_question
+        )
 
     def _getSeriesTargetNameHelper(self, bugtask):
         """Return the short name of bugtask's targeted series."""
@@ -2152,14 +2344,16 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
     @property
     def bugtask_icon(self):
         """Which icon should be shown for the task, if any?"""
-        return getAdapter(self.context, IPathAdapter, 'image').sprite_css()
+        return getAdapter(self.context, IPathAdapter, "image").sprite_css()
 
     def displayEditForm(self):
         """Return true if the BugTask edit form should be shown."""
         return (
-            check_permission('launchpad.Edit', self.context) and
+            check_permission("launchpad.Edit", self.context)
+            and
             # Hide the edit form when the bug is viewed in a CVE context.
-            self.request.getNearest(ICveSet) == (None, None))
+            self.request.getNearest(ICveSet) == (None, None)
+        )
 
     @property
     def status_widget_items(self):
@@ -2171,15 +2365,23 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             # by EnumeratedType have their name as the token and here we need
             # the title as the token for backwards compatibility.
             status_items = [
-                (item.title, item) for item in BugTaskStatus.items
-                if item not in (BugTaskStatus.UNKNOWN,
-                                BugTaskStatus.EXPIRED,
-                                BugTaskStatus.DOESNOTEXIST)
-                   or item == self.context.status]
+                (item.title, item)
+                for item in BugTaskStatus.items
+                if item
+                not in (
+                    BugTaskStatus.UNKNOWN,
+                    BugTaskStatus.EXPIRED,
+                    BugTaskStatus.DOESNOTEXIST,
+                )
+                or item == self.context.status
+            ]
 
-            disabled_items = [status for status in BugTaskStatus.items
+            disabled_items = [
+                status
+                for status in BugTaskStatus.items
                 if not self.context.canTransitionToStatus(status, self.user)
-                   and status != self.context.status]
+                and status != self.context.status
+            ]
         else:
             status_items = [(self.context.status.title, self.context.status)]
             disabled_items = []
@@ -2187,8 +2389,9 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         items = vocabulary_to_choice_edit_items(
             SimpleVocabulary.fromItems(status_items),
             include_description=True,
-            css_class_prefix='status',
-            disabled_items=disabled_items)
+            css_class_prefix="status",
+            disabled_items=disabled_items,
+        )
 
         return items
 
@@ -2202,15 +2405,18 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             # by EnumeratedType have their name as the token and here we need
             # the title as the token for backwards compatibility.
             importance_items = [
-                (item.title, item) for item in BugTaskImportance.items
-                if item != BugTaskImportance.UNKNOWN]
+                (item.title, item)
+                for item in BugTaskImportance.items
+                if item != BugTaskImportance.UNKNOWN
+            ]
 
             items = vocabulary_to_choice_edit_items(
                 SimpleVocabulary.fromItems(importance_items),
                 include_description=True,
-                css_class_prefix='importance')
+                css_class_prefix="importance",
+            )
         else:
-            items = '[]'
+            items = "[]"
 
         return items
 
@@ -2226,13 +2432,14 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             items = vocabulary_to_choice_edit_items(
                 self._visible_milestones,
                 value_fn=lambda item: canonical_url(
-                    item, request=self.api_request))
-            items.append({
-                "name": "Remove milestone",
-                "disabled": False,
-                "value": None})
+                    item, request=self.api_request
+                ),
+            )
+            items.append(
+                {"name": "Remove milestone", "disabled": False, "value": None}
+            )
         else:
-            items = '[]'
+            items = "[]"
 
         return items
 
@@ -2243,7 +2450,7 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
     @cachedproperty
     def user_can_edit(self):
         """Can the user edit the task at all?"""
-        return check_permission('launchpad.Edit', self.context)
+        return check_permission("launchpad.Edit", self.context)
 
     @cachedproperty
     def user_can_edit_importance(self):
@@ -2252,9 +2459,10 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         If yes, return True, otherwise return False.
         """
         return (
-            self.user_can_edit and
-            self.user_can_edit_status and
-            self.user_has_privileges)
+            self.user_can_edit
+            and self.user_can_edit_status
+            and self.user_has_privileges
+        )
 
     @cachedproperty
     def user_can_edit_status(self):
@@ -2269,7 +2477,8 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         if bugtask.bugwatch:
             bugtracker = bugtask.bugwatch.bugtracker
             edit_allowed = (
-                bugtracker.bugtrackertype == BugTrackerType.EMAILADDRESS)
+                bugtracker.bugtrackertype == BugTrackerType.EMAILADDRESS
+            )
         return edit_allowed
 
     @property
@@ -2287,40 +2496,44 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
         If yes, return True, otherwise return False.
         """
         bugtask = self.context
-        return (check_permission('launchpad.Delete', bugtask)
-                and bugtask.canBeDeleted())
+        return (
+            check_permission("launchpad.Delete", bugtask)
+            and bugtask.canBeDeleted()
+        )
 
     @property
     def style_for_add_milestone(self):
         if self.context.milestone is None:
-            return ''
+            return ""
         else:
-            return 'hidden'
+            return "hidden"
 
     @property
     def style_for_edit_milestone(self):
         if self.context.milestone is None:
-            return 'hidden'
+            return "hidden"
         else:
-            return ''
+            return ""
 
     def bugtask_config(self):
         """Configuration for the bugtask JS widgets on the row."""
-        assignee_vocabulary_name, assignee_vocabulary = (
-            get_assignee_vocabulary_info(self.context))
+        (
+            assignee_vocabulary_name,
+            assignee_vocabulary,
+        ) = get_assignee_vocabulary_info(self.context)
         filter_details = vocabulary_filters(assignee_vocabulary)
         # Display the search field only if the user can set any person
         # or team
         user = self.user
-        hide_assignee_team_selection = (
-            not self.context.userCanSetAnyAssignee(user) and
-            (user is None or user.teams_participated_in.count() == 0))
+        hide_assignee_team_selection = not self.context.userCanSetAnyAssignee(
+            user
+        ) and (user is None or user.teams_participated_in.count() == 0)
         cx = self.context
         return dict(
             id=cx.id,
-            row_id=self.data['row_id'],
-            form_row_id=self.data['form_row_id'],
-            bugtask_path='/'.join([''] + self.data['link'].split('/')[3:]),
+            row_id=self.data["row_id"],
+            form_row_id=self.data["form_row_id"],
+            bugtask_path="/".join([""] + self.data["link"].split("/")[3:]),
             prefix=get_prefix(cx),
             targetname=cx.bugtargetdisplayname,
             bug_title=cx.bug.title,
@@ -2331,7 +2544,7 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             hide_assignee_team_selection=hide_assignee_team_selection,
             user_can_unassign=cx.userCanUnassign(user),
             user_can_delete=self.user_can_delete_bugtask,
-            delete_link=self.data['delete_link'],
+            delete_link=self.data["delete_link"],
             target_is_product=IProduct.providedBy(cx.target),
             status_widget_items=self.status_widget_items,
             status_value=cx.status.title,
@@ -2339,15 +2552,15 @@ class BugTaskTableRowView(LaunchpadView, BugTaskBugWatchMixin,
             importance_value=cx.importance.title,
             milestone_widget_items=self.milestone_widget_items,
             milestone_value=(
-                canonical_url(
-                    cx.milestone,
-                    request=self.api_request)
-                if cx.milestone else None),
+                canonical_url(cx.milestone, request=self.api_request)
+                if cx.milestone
+                else None
+            ),
             user_can_edit_assignee=self.user_can_edit_assignee,
             user_can_edit_milestone=self.user_has_privileges,
             user_can_edit_status=self.user_can_edit_status,
             user_can_edit_importance=self.user_has_privileges,
-            )
+        )
 
 
 @implementer(IObjectPrivacy)
@@ -2365,13 +2578,14 @@ class BugTaskPrivacyAdapter:
 
 class BugTaskCreateQuestionView(LaunchpadFormView):
     """View for creating a question from a bug."""
+
     schema = ICreateQuestionFromBugTaskForm
 
     def setUpFields(self):
         """See `LaunchpadFormView`."""
         LaunchpadFormView.setUpFields(self)
         if not self.can_be_a_question:
-            self.form_fields = self.form_fields.omit('comment')
+            self.form_fields = self.form_fields.omit("comment")
 
     @property
     def next_url(self):
@@ -2383,7 +2597,7 @@ class BugTaskCreateQuestionView(LaunchpadFormView):
         """Return True if this bug can become a question, otherwise False."""
         return self.context.bug.canBeAQuestion()
 
-    @action('Convert this Bug into a Question', name='create')
+    @action("Convert this Bug into a Question", name="create")
     def create_action(self, action, data):
         """Create a question from this bug and set this bug to Invalid.
 
@@ -2396,26 +2610,28 @@ class BugTaskCreateQuestionView(LaunchpadFormView):
         """
         if not self.context.bug.canBeAQuestion():
             self.request.response.addNotification(
-                'This bug could not be converted into a question.')
+                "This bug could not be converted into a question."
+            )
             return
 
-        comment = data.get('comment', None)
+        comment = data.get("comment", None)
         self.context.bug.convertToQuestion(self.user, comment=comment)
 
-    label = 'Convert this bug to a question'
+    label = "Convert this bug to a question"
 
     page_title = label
 
 
 class BugTaskRemoveQuestionView(LaunchpadFormView):
     """View for creating a question from a bug."""
+
     schema = IRemoveQuestionFromBugTaskForm
 
     def setUpFields(self):
         """See `LaunchpadFormView`."""
         LaunchpadFormView.setUpFields(self)
         if not self.has_question:
-            self.form_fields = self.form_fields.omit('comment')
+            self.form_fields = self.form_fields.omit("comment")
 
     @property
     def next_url(self):
@@ -2427,7 +2643,7 @@ class BugTaskRemoveQuestionView(LaunchpadFormView):
         """Return True if a question was created from this bug, or False."""
         return self.context.bug.getQuestionCreatedFromBug() is not None
 
-    @action('Convert Back to Bug', name='remove')
+    @action("Convert Back to Bug", name="remove")
     def remove_action(self, action, data):
         """Remove a question from this bug.
 
@@ -2439,7 +2655,8 @@ class BugTaskRemoveQuestionView(LaunchpadFormView):
         question = self.context.bug.getQuestionCreatedFromBug()
         if question is None:
             self.request.response.addNotification(
-                'This bug does not have a question to remove')
+                "This bug does not have a question to remove"
+            )
             return
 
         owner_is_subscribed = question.isSubscribed(self.context.bug.owner)
@@ -2453,24 +2670,29 @@ class BugTaskRemoveQuestionView(LaunchpadFormView):
                 'Removed Question #%s: <a href="%s">%s<a>.',
                 str(question.id),
                 canonical_url(question),
-                question.title))
+                question.title,
+            )
+        )
 
-        comment = data.get('comment', None)
+        comment = data.get("comment", None)
         if comment is not None:
             self.context.bug.newMessage(
                 owner=getUtility(ILaunchBag).user,
                 subject=self.context.bug.followup_subject(),
-                content=comment)
+                content=comment,
+            )
 
     @property
     def label(self):
-        return ('Bug #%i - Convert this question back to a bug'
-                % self.context.bug.id)
+        return (
+            "Bug #%i - Convert this question back to a bug"
+            % self.context.bug.id
+        )
 
     page_title = label
 
 
-@delegate_to(IBugActivity, context='activity')
+@delegate_to(IBugActivity, context="activity")
 class BugActivityItem:
     """A decorated BugActivity."""
 
@@ -2495,42 +2717,44 @@ class BugActivityItem:
         Some event names are more descriptive as data, but less relevant to
         users, who are unfamiliar with the lp code."""
         better_summaries = {
-            'bug task deleted': 'no longer affects',
-            }
+            "bug task deleted": "no longer affects",
+        }
         return better_summaries.get(summary, summary)
 
     @property
     def _formatted_tags_change(self):
         """Return a tags change as lists of added and removed tags."""
-        assert self.whatchanged == 'tags', (
+        assert self.whatchanged == "tags", (
             "Can't return a formatted tags change for a change in %s."
-            % self.whatchanged)
+            % self.whatchanged
+        )
 
         # Turn the strings of newvalue and oldvalue into sets so we
         # can work out the differences.
-        if self.newvalue != '':
-            new_tags = set(re.split(r'\s+', self.newvalue))
+        if self.newvalue != "":
+            new_tags = set(re.split(r"\s+", self.newvalue))
         else:
             new_tags = set()
 
-        if self.oldvalue != '':
-            old_tags = set(re.split(r'\s+', self.oldvalue))
+        if self.oldvalue != "":
+            old_tags = set(re.split(r"\s+", self.oldvalue))
         else:
             old_tags = set()
 
         added_tags = sorted(new_tags.difference(old_tags))
         removed_tags = sorted(old_tags.difference(new_tags))
 
-        return_string = ''
+        return_string = ""
         if len(added_tags) > 0:
-            return_string = "added: %s\n" % ' '.join(added_tags)
+            return_string = "added: %s\n" % " ".join(added_tags)
         if len(removed_tags) > 0:
-            return_string = (
-                return_string + "removed: %s" % ' '.join(removed_tags))
+            return_string = return_string + "removed: %s" % " ".join(
+                removed_tags
+            )
 
         # Trim any leading or trailing \ns and then convert the to
         # <br />s so they're displayed correctly.
-        return return_string.strip('\n')
+        return return_string.strip("\n")
 
     @property
     def change_details(self):
@@ -2538,49 +2762,50 @@ class BugActivityItem:
         # Our default return dict. We may mutate this depending on
         # what's changed.
         return_dict = {
-            'old_value': self.oldvalue,
-            'new_value': self.newvalue,
-            }
+            "old_value": self.oldvalue,
+            "new_value": self.newvalue,
+        }
         attribute = self.attribute
-        if attribute == 'title':
+        if attribute == "title":
             # We display summary changes as a unified diff, replacing
             # \ns with <br />s so that the lines are separated properly.
             diff = html_escape(
-                get_unified_diff(self.oldvalue, self.newvalue, 72))
+                get_unified_diff(self.oldvalue, self.newvalue, 72)
+            )
             return diff.replace("\n", "<br />")
 
-        elif attribute == 'description':
+        elif attribute == "description":
             # Description changes can be quite long, so we just return
             # 'updated' rather than returning the whole new description
             # or a diff.
-            return 'updated'
+            return "updated"
 
-        elif attribute == 'tags':
+        elif attribute == "tags":
             # We special-case tags because we can work out what's been
             # added and what's been removed.
             return html_escape(self._formatted_tags_change).replace(
-                '\n', '<br />')
+                "\n", "<br />"
+            )
 
-        elif attribute == 'assignee':
+        elif attribute == "assignee":
             for key in return_dict:
                 if return_dict[key] is None:
-                    return_dict[key] = 'nobody'
+                    return_dict[key] = "nobody"
                 else:
                     return_dict[key] = html_escape(return_dict[key])
 
-        elif attribute == 'lock status':
+        elif attribute == "lock status":
             if self.newvalue == str(BugLockStatus.COMMENT_ONLY):
                 reason = " "
                 detail = (
-                    'Metadata changes locked{}and '
-                    'limited to project staff'
+                    "Metadata changes locked{}and " "limited to project staff"
                 )
                 if self.message:
                     reason = " ({}) ".format(html_escape(self.message))
                 return detail.format(reason)
             else:
-                return 'Metadata changes unlocked'
-        elif attribute == 'lock reason':
+                return "Metadata changes unlocked"
+        elif attribute == "lock reason":
             if self.newvalue != "unset" and self.oldvalue != "unset":
                 # return a proper message with old and new values
                 return "{} &rarr; {}".format(
@@ -2591,14 +2816,14 @@ class BugActivityItem:
                 return "{}".format(self.newvalue)
             else:
                 return "Unset"
-        elif attribute == 'milestone':
+        elif attribute == "milestone":
             for key in return_dict:
                 if return_dict[key] is None:
-                    return_dict[key] = 'none'
+                    return_dict[key] = "none"
                 else:
                     return_dict[key] = html_escape(return_dict[key])
 
-        elif attribute == 'bug task deleted':
+        elif attribute == "bug task deleted":
             return self.oldvalue
 
         else:
@@ -2631,4 +2856,4 @@ class BugTaskBreadcrumb(Breadcrumb):
     def detail(self):
         bug = self.context.bug
         title = smartquote('"%s"' % bug.title)
-        return '%s %s' % (bug.displayname, title)
+        return "%s %s" % (bug.displayname, title)
diff --git a/lib/lp/bugs/browser/bugtracker.py b/lib/lp/bugs/browser/bugtracker.py
index 49cfb21..2002ba3 100644
--- a/lib/lp/bugs/browser/bugtracker.py
+++ b/lib/lp/bugs/browser/bugtracker.py
@@ -4,19 +4,19 @@
 """Bug tracker views."""
 
 __all__ = [
-    'BugTrackerAddView',
-    'BugTrackerComponentGroupNavigation',
-    'BugTrackerEditView',
-    'BugTrackerEditComponentView',
-    'BugTrackerNavigation',
-    'BugTrackerNavigationMenu',
-    'BugTrackerSetBreadcrumb',
-    'BugTrackerSetContextMenu',
-    'BugTrackerSetNavigation',
-    'BugTrackerSetView',
-    'BugTrackerView',
-    'RemoteBug',
-    ]
+    "BugTrackerAddView",
+    "BugTrackerComponentGroupNavigation",
+    "BugTrackerEditView",
+    "BugTrackerEditComponentView",
+    "BugTrackerNavigation",
+    "BugTrackerNavigationMenu",
+    "BugTrackerSetBreadcrumb",
+    "BugTrackerSetContextMenu",
+    "BugTrackerSetNavigation",
+    "BugTrackerSetView",
+    "BugTrackerView",
+    "RemoteBug",
+]
 
 from itertools import chain
 
@@ -31,10 +31,10 @@ from zope.schema.vocabulary import SimpleVocabulary
 
 from lp import _
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
-    )
+    action,
+)
 from lp.app.interfaces.launchpad import ILaunchpadCelebrities
 from lp.app.validators import LaunchpadValidationError
 from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
@@ -47,48 +47,40 @@ from lp.bugs.interfaces.bugtracker import (
     IBugTrackerComponentGroup,
     IBugTrackerSet,
     IRemoteBug,
-    )
+)
 from lp.services.database.sqlbase import flush_database_updates
-from lp.services.helpers import (
-    english_list,
-    shortlist,
-    )
+from lp.services.helpers import english_list, shortlist
 from lp.services.propertycache import cachedproperty
 from lp.services.webapp import (
-    canonical_url,
     ContextMenu,
     GetitemNavigation,
     LaunchpadView,
     Link,
     Navigation,
+    canonical_url,
     redirection,
     stepthrough,
     structured,
-    )
+)
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.batching import (
     ActiveBatchNavigator,
     BatchNavigator,
     InactiveBatchNavigator,
-    )
+)
 from lp.services.webapp.breadcrumb import Breadcrumb
 from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.webapp.menu import NavigationMenu
 
-
 # A set of bug tracker types for which there can only ever be one bug
 # tracker.
-SINGLE_INSTANCE_TRACKERS = (
-    BugTrackerType.DEBBUGS,
-    )
+SINGLE_INSTANCE_TRACKERS = (BugTrackerType.DEBBUGS,)
 
 # A set of bug tracker types that we should not allow direct creation
 # of.
-NO_DIRECT_CREATION_TRACKERS = (
-    SINGLE_INSTANCE_TRACKERS + (
-        BugTrackerType.EMAILADDRESS,
-        )
-    )
+NO_DIRECT_CREATION_TRACKERS = SINGLE_INSTANCE_TRACKERS + (
+    BugTrackerType.EMAILADDRESS,
+)
 
 
 class BugTrackerSetNavigation(GetitemNavigation):
@@ -100,11 +92,11 @@ class BugTrackerSetContextMenu(ContextMenu):
 
     usedfor = IBugTrackerSet
 
-    links = ['newbugtracker']
+    links = ["newbugtracker"]
 
     def newbugtracker(self):
-        text = 'Register another bug tracker'
-        return Link('+newbugtracker', text, icon='add')
+        text = "Register another bug tracker"
+        return Link("+newbugtracker", text, icon="add")
 
 
 class BugTrackerAddView(LaunchpadFormView):
@@ -112,40 +104,54 @@ class BugTrackerAddView(LaunchpadFormView):
     page_title = "Register an external bug tracker"
     schema = IBugTracker
     label = page_title
-    field_names = ['bugtrackertype', 'name', 'title', 'baseurl', 'summary',
-                   'contactdetails']
+    field_names = [
+        "bugtrackertype",
+        "name",
+        "title",
+        "baseurl",
+        "summary",
+        "contactdetails",
+    ]
 
     def setUpWidgets(self, context=None):
         # We only show those bug tracker types for which there can be
         # multiple instances in the bugtrackertype Choice widget.
         vocab_items = [
-            item for item in BugTrackerType.items.items
-                if item not in NO_DIRECT_CREATION_TRACKERS]
+            item
+            for item in BugTrackerType.items.items
+            if item not in NO_DIRECT_CREATION_TRACKERS
+        ]
         fields = []
         for field_name in self.field_names:
-            if field_name == 'bugtrackertype':
-                fields.append(form.FormField(
-                    Choice(__name__='bugtrackertype',
-                           title=_('Bug Tracker Type'),
-                           values=vocab_items,
-                           default=BugTrackerType.BUGZILLA)))
+            if field_name == "bugtrackertype":
+                fields.append(
+                    form.FormField(
+                        Choice(
+                            __name__="bugtrackertype",
+                            title=_("Bug Tracker Type"),
+                            values=vocab_items,
+                            default=BugTrackerType.BUGZILLA,
+                        )
+                    )
+                )
             else:
                 fields.append(self.form_fields[field_name])
         self.form_fields = form.Fields(*fields)
         super().setUpWidgets(context=context)
 
-    @action(_('Add'), name='add')
+    @action(_("Add"), name="add")
     def add(self, action, data):
         """Create the IBugTracker."""
         btset = getUtility(IBugTrackerSet)
         bugtracker = btset.ensureBugTracker(
-            name=data['name'],
-            bugtrackertype=data['bugtrackertype'],
-            title=data['title'],
-            summary=data['summary'],
-            baseurl=data['baseurl'],
-            contactdetails=data['contactdetails'],
-            owner=getUtility(ILaunchBag).user)
+            name=data["name"],
+            bugtrackertype=data["bugtrackertype"],
+            title=data["title"],
+            summary=data["summary"],
+            baseurl=data["baseurl"],
+            contactdetails=data["contactdetails"],
+            owner=getUtility(ILaunchBag).user,
+        )
         self.next_url = canonical_url(bugtracker)
 
     @property
@@ -165,7 +171,8 @@ class BugTrackerSetView(LaunchpadView):
         # bug watch counts per tracker. However the batching makes
         # the inefficiency tolerable for now. Robert Collins 20100919.
         self._pillar_cache = self.context.getPillarsForBugtrackers(
-            list(self.context.getAllTrackers()), self.user)
+            list(self.context.getAllTrackers()), self.user
+        )
 
     @property
     def inactive_tracker_count(self):
@@ -175,14 +182,14 @@ class BugTrackerSetView(LaunchpadView):
     def active_trackers(self):
         results = self.context.getAllTrackers(active=True)
         navigator = ActiveBatchNavigator(results, self.request)
-        navigator.setHeadings('tracker', 'trackers')
+        navigator.setHeadings("tracker", "trackers")
         return navigator
 
     @cachedproperty
     def inactive_trackers(self):
         results = self.context.getAllTrackers(active=False)
         navigator = InactiveBatchNavigator(results, self.request)
-        navigator.setHeadings('tracker', 'trackers')
+        navigator.setHeadings("tracker", "trackers")
         return navigator
 
     def getPillarData(self, bugtracker):
@@ -202,8 +209,9 @@ class BugTrackerSetView(LaunchpadView):
         else:
             has_more_pillars = False
         return {
-            'pillars': pillars[:self.pillar_limit],
-            'has_more_pillars': has_more_pillars}
+            "pillars": pillars[: self.pillar_limit],
+            "has_more_pillars": has_more_pillars,
+        }
 
 
 class BugTrackerView(LaunchpadView):
@@ -213,7 +221,8 @@ class BugTrackerView(LaunchpadView):
     @property
     def page_title(self):
         return smartquote(
-            'The "%s" bug tracker in Launchpad' % self.context.title)
+            'The "%s" bug tracker in Launchpad' % self.context.title
+        )
 
     def initialize(self):
         self.batchnav = BatchNavigator(self.context.watches, self.request)
@@ -235,7 +244,8 @@ class BugTrackerView(LaunchpadView):
 
 
 BUG_TRACKER_ACTIVE_VOCABULARY = SimpleVocabulary.fromItems(
-    [('On', True), ('Off', False)])
+    [("On", True), ("Off", False)]
+)
 
 
 class BugTrackerEditView(LaunchpadEditFormView):
@@ -243,30 +253,33 @@ class BugTrackerEditView(LaunchpadEditFormView):
     schema = IBugTracker
 
     custom_widget_summary = CustomWidgetFactory(
-        TextAreaWidget, width=30, height=5)
+        TextAreaWidget, width=30, height=5
+    )
     custom_widget_aliases = CustomWidgetFactory(DelimitedListWidget, height=3)
     custom_widget_active = CustomWidgetFactory(
-        LaunchpadRadioWidget, orientation='vertical')
+        LaunchpadRadioWidget, orientation="vertical"
+    )
 
     @property
     def page_title(self):
         return smartquote(
-            'Change details for the "%s" bug tracker' % self.context.title)
+            'Change details for the "%s" bug tracker' % self.context.title
+        )
 
     @cachedproperty
     def field_names(self):
         field_names = [
-            'name',
-            'title',
-            'bugtrackertype',
-            'summary',
-            'baseurl',
-            'aliases',
-            'contactdetails',
-            ]
+            "name",
+            "title",
+            "bugtrackertype",
+            "summary",
+            "baseurl",
+            "aliases",
+            "contactdetails",
+        ]
 
         if check_permission("launchpad.Admin", self.context):
-            field_names.append('active')
+            field_names.append("active")
 
         return field_names
 
@@ -283,48 +296,56 @@ class BugTrackerEditView(LaunchpadEditFormView):
 
         # If we're displaying the 'active' field we need to swap it out
         # and replace it with a field that uses our custom vocabulary.
-        if 'active' in self.field_names:
+        if "active" in self.field_names:
             active_field = Choice(
-                __name__='active',
-                title=_('Updates for this bug tracker are'),
+                __name__="active",
+                title=_("Updates for this bug tracker are"),
                 vocabulary=BUG_TRACKER_ACTIVE_VOCABULARY,
-                required=True, default=self.context.active)
+                required=True,
+                default=self.context.active,
+            )
 
-            self.form_fields = self.form_fields.omit('active')
+            self.form_fields = self.form_fields.omit("active")
             self.form_fields += form.Fields(active_field)
 
     def validate(self, data):
         # Normalise aliases to an empty list if it's None.
-        if data.get('aliases') is None:
-            data['aliases'] = []
+        if data.get("aliases") is None:
+            data["aliases"] = []
 
         # If aliases has an error, unwrap the Dantean exception from
         # Zope so that we can tell the user something useful.
-        if self.getFieldError('aliases'):
+        if self.getFieldError("aliases"):
             # XXX: wgrant 2008-04-02 bug=210901: The error
             # messages may have already been escaped by
             # LaunchpadValidationError, so wrap them in structured() to
             # avoid double-escaping them. It's possible that non-LVEs
             # could also be escaped, but I can't think of any cases so
             # let's just escape them anyway.
-            aliases_errors = self.widgets['aliases']._error.errors.args[0]
+            aliases_errors = self.widgets["aliases"]._error.errors.args[0]
             maybe_structured_errors = [
                 structured(error)
-                if isinstance(error, LaunchpadValidationError) else error
-                for error in aliases_errors]
-            self.setFieldError('aliases', structured(
-                    '<br />'.join(['%s'] * len(maybe_structured_errors)),
-                    *maybe_structured_errors))
-
-    @action('Change', name='change')
+                if isinstance(error, LaunchpadValidationError)
+                else error
+                for error in aliases_errors
+            ]
+            self.setFieldError(
+                "aliases",
+                structured(
+                    "<br />".join(["%s"] * len(maybe_structured_errors)),
+                    *maybe_structured_errors,
+                ),
+            )
+
+    @action("Change", name="change")
     def change_action(self, action, data):
         # If the baseurl is going to change, save the current baseurl
         # as an alias. Users attempting to use this URL, which is
         # presumably incorrect or out-of-date, will be captured.
         current_baseurl = self.context.baseurl
-        requested_baseurl = data['baseurl']
+        requested_baseurl = data["baseurl"]
         if requested_baseurl != current_baseurl:
-            data['aliases'].append(current_baseurl)
+            data["aliases"].append(current_baseurl)
 
         self.updateContextFromData(data)
         self.next_url = canonical_url(self.context)
@@ -348,12 +369,15 @@ class BugTrackerEditView(LaunchpadEditFormView):
 
         # Check that no products or projects use this bugtracker.
         pillars = (
-            getUtility(IBugTrackerSet).getPillarsForBugtrackers(
-                [self.context]).get(self.context, []))
+            getUtility(IBugTrackerSet)
+            .getPillarsForBugtrackers([self.context])
+            .get(self.context, [])
+        )
         if len(pillars) > 0:
             reasons.append(
-                'This is the bug tracker for %s.' % english_list(
-                    sorted(pillar.title for pillar in pillars)))
+                "This is the bug tracker for %s."
+                % english_list(sorted(pillar.title for pillar in pillars))
+            )
 
         # Only admins and registry experts can delete bug watches en
         # masse.
@@ -364,30 +388,32 @@ class BugTrackerEditView(LaunchpadEditFormView):
                     break
             else:
                 reasons.append(
-                    'There are linked bug watches and only members of %s '
-                    'can delete them en masse.' % english_list(
-                        sorted(team.title for team in admin_teams)))
+                    "There are linked bug watches and only members of %s "
+                    "can delete them en masse."
+                    % english_list(sorted(team.title for team in admin_teams))
+                )
 
         # Bugtrackers with imported messages cannot be deleted.
         if not self.context.imported_bug_messages.is_empty():
             reasons.append(
-                'Bug comments have been imported via this bug tracker.')
+                "Bug comments have been imported via this bug tracker."
+            )
 
         # If the bugtracker is a celebrity then we protect it from
         # deletion.
         celebrities_set = {
             getattr(celebrities, name)
-            for name in ILaunchpadCelebrities.names()}
+            for name in ILaunchpadCelebrities.names()
+        }
         if self.context in celebrities_set:
-            reasons.append(
-                'This bug tracker is protected from deletion.')
+            reasons.append("This bug tracker is protected from deletion.")
 
         return reasons
 
     def delete_condition(self, action):
         return len(self.delete_not_possible_reasons) == 0
 
-    @action('Delete', name='delete', condition=delete_condition)
+    @action("Delete", name="delete", condition=delete_condition)
     def delete_action(self, action, data):
         # First unlink bug watches from all bugtasks, flush updates,
         # then delete the watches themselves.
@@ -395,9 +421,10 @@ class BugTrackerEditView(LaunchpadEditFormView):
             for bugtask in watch.bugtasks:
                 if len(bugtask.bug.bugtasks) < 2:
                     raise AssertionError(
-                        'There should be more than one bugtask for a bug '
-                        'when one of them is linked to the original bug via '
-                        'a bug watch.')
+                        "There should be more than one bugtask for a bug "
+                        "when one of them is linked to the original bug via "
+                        "a bug watch."
+                    )
                 bugtask.bugwatch = None
         flush_database_updates()
         for watch in self.context.watches:
@@ -409,7 +436,8 @@ class BugTrackerEditView(LaunchpadEditFormView):
 
         # Hey, it worked! Tell the user.
         self.request.response.addInfoNotification(
-            '%s has been deleted.' % (self.context.title,))
+            "%s has been deleted." % (self.context.title,)
+        )
 
         # Go back to the bug tracker listing.
         self.next_url = canonical_url(getUtility(IBugTrackerSet))
@@ -421,18 +449,21 @@ class BugTrackerEditView(LaunchpadEditFormView):
     def reschedule_action_condition(self, action):
         """Return True if the user can see the reschedule action."""
         user_can_reset_watches = check_permission(
-            "launchpad.Admin", self.context)
+            "launchpad.Admin", self.context
+        )
         return user_can_reset_watches and not self.context.watches.is_empty()
 
     @action(
-        'Reschedule all watches', name='reschedule',
-        condition=reschedule_action_condition)
+        "Reschedule all watches",
+        name="reschedule",
+        condition=reschedule_action_condition,
+    )
     def rescheduleAction(self, action, data):
         """Reschedule all the watches for the bugtracker."""
         self.context.resetWatches()
         self.request.response.addInfoNotification(
-            "All bug watches on %s have been rescheduled." %
-            self.context.title)
+            "All bug watches on %s have been rescheduled." % self.context.title
+        )
         self.next_url = canonical_url(self.context)
 
 
@@ -464,24 +495,26 @@ class BugTrackerEditComponentView(LaunchpadEditFormView):
     This class assumes that bug tracker components are always
     linked to source packages in the Ubuntu distribution.
     """
+
     schema = IBugTrackerComponent
     custom_widget_source_package_name = UbuntuSourcePackageNameWidget
-    field_names = ['source_package_name']
-    page_title = 'Link component'
+    field_names = ["source_package_name"]
+    page_title = "Link component"
 
     @property
     def label(self):
         return (
-            'Link a distribution source package to %s component' %
-            self.context.name)
+            "Link a distribution source package to %s component"
+            % self.context.name
+        )
 
     @property
     def initial_values(self):
         """See `LaunchpadFormView.`"""
-        field_values = dict(source_package_name='')
+        field_values = dict(source_package_name="")
         dsp = self.context.distro_source_package
         if dsp is not None:
-            field_values['source_package_name'] = dsp.name
+            field_values["source_package_name"] = dsp.name
         return field_values
 
     @property
@@ -497,34 +530,45 @@ class BugTrackerEditComponentView(LaunchpadEditFormView):
         look it up in Ubuntu to retrieve the distro_source_package
         object, and link it to this component.
         """
-        source_package_name = data['source_package_name']
-        distribution = self.widgets['source_package_name'].getDistribution()
+        source_package_name = data["source_package_name"]
+        distribution = self.widgets["source_package_name"].getDistribution()
         dsp = distribution.getSourcePackage(source_package_name)
         bug_tracker = self.context.component_group.bug_tracker
         # Has this source package already been assigned to a component?
         component = bug_tracker.getRemoteComponentForDistroSourcePackageName(
-            distribution, source_package_name)
+            distribution, source_package_name
+        )
         if component is not None:
             self.request.response.addNotification(
-                "The %s source package is already linked to %s:%s in %s." % (
+                "The %s source package is already linked to %s:%s in %s."
+                % (
                     source_package_name.name,
                     component.component_group.name,
-                    component.name, distribution.name))
+                    component.name,
+                    distribution.name,
+                )
+            )
             return
         # The submitted component can be linked to the distro source package.
         component = context or self.context
         component.distro_source_package = dsp
         if source_package_name is None:
             self.request.response.addNotification(
-                "%s:%s is now unlinked." % (
-                    component.component_group.name, component.name))
+                "%s:%s is now unlinked."
+                % (component.component_group.name, component.name)
+            )
         else:
             self.request.response.addNotification(
-                "%s:%s is now linked to the %s source package in %s." % (
-                    component.component_group.name, component.name,
-                    source_package_name.name, distribution.name))
+                "%s:%s is now linked to the %s source package in %s."
+                % (
+                    component.component_group.name,
+                    component.name,
+                    source_package_name.name,
+                    distribution.name,
+                )
+            )
 
-    @action('Save Changes', name='save')
+    @action("Save Changes", name="save")
     def save_action(self, action, data):
         """Update the component with the form data."""
         self.updateContextFromData(data)
@@ -559,8 +603,7 @@ class RemoteBug:
 
     @property
     def title(self):
-        return 'Remote Bug #%s in %s' % (self.remotebug,
-                                         self.bugtracker.title)
+        return "Remote Bug #%s in %s" % (self.remotebug, self.bugtracker.title)
 
 
 class RemoteBugView(LaunchpadView):
@@ -574,9 +617,9 @@ class RemoteBugView(LaunchpadView):
 class BugTrackerNavigationMenu(NavigationMenu):
 
     usedfor = BugTrackerView
-    facet = 'bugs'
-    links = ['edit']
+    facet = "bugs"
+    links = ["edit"]
 
     def edit(self):
-        text = 'Change details'
-        return Link('+edit', text, icon='edit')
+        text = "Change details"
+        return Link("+edit", text, icon="edit")
diff --git a/lib/lp/bugs/browser/bugwatch.py b/lib/lp/bugs/browser/bugwatch.py
index 516adad..4d67816 100644
--- a/lib/lp/bugs/browser/bugwatch.py
+++ b/lib/lp/bugs/browser/bugwatch.py
@@ -4,20 +4,17 @@
 """IBugWatch-related browser views."""
 
 __all__ = [
-    'BugWatchSetNavigation',
-    'BugWatchActivityPortletView',
-    'BugWatchEditView',
-    'BugWatchView'
-    ]
+    "BugWatchSetNavigation",
+    "BugWatchActivityPortletView",
+    "

Follow ups