← Back to team overview

launchpad-reviewers team mailing list archive

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

 

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

Commit message:
lp.blueprints: Apply black

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/425109
-- 
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-blueprints into launchpad:master.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 639fef0..5030b87 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -62,3 +62,5 @@ c606443bdb2f342593c9a7c9437cb70c01f85f29
 8885e7977012e4f376e23f52125784567aefebe4
 # apply black to lp.archiveuploader
 01c7f7112b20dab5c48373339d530a39f0dc859b
+# apply black to lp.blueprints
+6178b1869f90f9bf1eb9ed050154888b523c53c5
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a8e3f73..dbcf289 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -45,6 +45,7 @@ repos:
             |app
             |archivepublisher
             |archiveuploader
+            |blueprints
           )/
 -   repo: https://github.com/PyCQA/isort
     rev: 5.9.2
@@ -66,6 +67,7 @@ repos:
             |app
             |archivepublisher
             |archiveuploader
+            |blueprints
           )/
     -   id: isort
         alias: isort-black
@@ -77,6 +79,7 @@ repos:
             |app
             |archivepublisher
             |archiveuploader
+            |blueprints
           )/
 -   repo: https://github.com/PyCQA/flake8
     rev: 3.9.2
diff --git a/lib/lp/blueprints/adapters.py b/lib/lp/blueprints/adapters.py
index ebd21f0..6525014 100644
--- a/lib/lp/blueprints/adapters.py
+++ b/lib/lp/blueprints/adapters.py
@@ -12,12 +12,28 @@ from lp.blueprints.interfaces.specification import ISpecificationDelta
 class SpecificationDelta:
     """See lp.blueprints.interfaces.specification.ISpecificationDelta."""
 
-    def __init__(self, specification, user, title=None,
-        summary=None, whiteboard=None, specurl=None, productseries=None,
-        distroseries=None, milestone=None, name=None, priority=None,
-        definition_status=None, target=None, bugs_linked=None,
-        bugs_unlinked=None, approver=None, assignee=None, drafter=None,
-        workitems_text=None):
+    def __init__(
+        self,
+        specification,
+        user,
+        title=None,
+        summary=None,
+        whiteboard=None,
+        specurl=None,
+        productseries=None,
+        distroseries=None,
+        milestone=None,
+        name=None,
+        priority=None,
+        definition_status=None,
+        target=None,
+        bugs_linked=None,
+        bugs_unlinked=None,
+        approver=None,
+        assignee=None,
+        drafter=None,
+        workitems_text=None,
+    ):
         self.specification = specification
         self.user = user
         self.title = title
diff --git a/lib/lp/blueprints/browser/person.py b/lib/lp/blueprints/browser/person.py
index 6cef1e5..4efa613 100644
--- a/lib/lp/blueprints/browser/person.py
+++ b/lib/lp/blueprints/browser/person.py
@@ -2,17 +2,14 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'PersonSpecsMenu',
-    'PersonSpecWorkloadTableView',
-    'PersonSpecWorkloadView',
-    ]
+    "PersonSpecsMenu",
+    "PersonSpecWorkloadTableView",
+    "PersonSpecWorkloadView",
+]
 
 from lp.registry.interfaces.person import IPerson
 from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
-    Link,
-    NavigationMenu,
-    )
+from lp.services.webapp import Link, NavigationMenu
 from lp.services.webapp.batching import BatchNavigator
 from lp.services.webapp.publisher import LaunchpadView
 
@@ -20,40 +17,48 @@ from lp.services.webapp.publisher import LaunchpadView
 class PersonSpecsMenu(NavigationMenu):
 
     usedfor = IPerson
-    facet = 'specifications'
-    links = ['assignee', 'drafter', 'approver',
-             'subscriber', 'registrant', 'workload']
+    facet = "specifications"
+    links = [
+        "assignee",
+        "drafter",
+        "approver",
+        "subscriber",
+        "registrant",
+        "workload",
+    ]
 
     def registrant(self):
-        text = 'Registrant'
-        summary = 'List specs registered by %s' % self.context.displayname
-        return Link('+specs?role=registrant', text, summary, icon='blueprint')
+        text = "Registrant"
+        summary = "List specs registered by %s" % self.context.displayname
+        return Link("+specs?role=registrant", text, summary, icon="blueprint")
 
     def approver(self):
-        text = 'Approver'
-        summary = 'List specs with %s is supposed to approve' % (
-            self.context.displayname)
-        return Link('+specs?role=approver', text, summary, icon='blueprint')
+        text = "Approver"
+        summary = "List specs with %s is supposed to approve" % (
+            self.context.displayname
+        )
+        return Link("+specs?role=approver", text, summary, icon="blueprint")
 
     def assignee(self):
-        text = 'Assignee'
-        summary = 'List specs for which %s is the assignee' % (
-            self.context.displayname)
-        return Link('+specs?role=assignee', text, summary, icon='blueprint')
+        text = "Assignee"
+        summary = "List specs for which %s is the assignee" % (
+            self.context.displayname
+        )
+        return Link("+specs?role=assignee", text, summary, icon="blueprint")
 
     def drafter(self):
-        text = 'Drafter'
-        summary = 'List specs drafted by %s' % self.context.displayname
-        return Link('+specs?role=drafter', text, summary, icon='blueprint')
+        text = "Drafter"
+        summary = "List specs drafted by %s" % self.context.displayname
+        return Link("+specs?role=drafter", text, summary, icon="blueprint")
 
     def subscriber(self):
-        text = 'Subscriber'
-        return Link('+specs?role=subscriber', text, icon='blueprint')
+        text = "Subscriber"
+        return Link("+specs?role=subscriber", text, icon="blueprint")
 
     def workload(self):
-        text = 'Workload'
-        summary = 'Show all specification work assigned'
-        return Link('+specworkload', text, summary, icon='info')
+        text = "Workload"
+        summary = "Show all specification work assigned"
+        return Link("+specworkload", text, summary, icon="info")
 
 
 class PersonSpecWorkloadView(LaunchpadView):
@@ -64,7 +69,7 @@ class PersonSpecWorkloadView(LaunchpadView):
     batching with their individual specifications.
     """
 
-    label = 'Blueprint workload'
+    label = "Blueprint workload"
 
     @cachedproperty
     def members(self):
@@ -88,7 +93,7 @@ class PersonSpecWorkloadTableView(LaunchpadView):
     in a single table.
     """
 
-    page_title = 'Blueprint workload'
+    page_title = "Blueprint workload"
 
     class PersonSpec:
         """One record from the workload list."""
@@ -107,5 +112,7 @@ class PersonSpecWorkloadTableView(LaunchpadView):
         Return a structure that lists the specs for which this person is the
         approver, the assignee or the drafter.
         """
-        return [PersonSpecWorkloadTableView.PersonSpec(spec, self.context)
-                for spec in self.context.specifications(self.user)]
+        return [
+            PersonSpecWorkloadTableView.PersonSpec(spec, self.context)
+            for spec in self.context.specifications(self.user)
+        ]
diff --git a/lib/lp/blueprints/browser/person_upcomingwork.py b/lib/lp/blueprints/browser/person_upcomingwork.py
index e4b24bb..e2cbf3e 100644
--- a/lib/lp/blueprints/browser/person_upcomingwork.py
+++ b/lib/lp/blueprints/browser/person_upcomingwork.py
@@ -5,17 +5,11 @@
 
 __meta__ = type
 __all__ = [
-    'PersonUpcomingWorkView',
-    ]
+    "PersonUpcomingWorkView",
+]
 
-from datetime import (
-    datetime,
-    timedelta,
-    )
-from operator import (
-    attrgetter,
-    itemgetter,
-    )
+from datetime import datetime, timedelta
+from operator import attrgetter, itemgetter
 
 from lp.app.browser.tales import format_link
 from lp.blueprints.enums import SpecificationWorkItemStatus
@@ -56,13 +50,14 @@ class PersonUpcomingWorkView(LaunchpadView):
                 for item in container.items:
                     milestones.add(item.milestone)
             self.milestones_per_date[date] = sorted(
-                milestones, key=attrgetter('displayname'))
+                milestones, key=attrgetter("displayname")
+            )
 
             percent_done = 0
             if total_items > 0:
                 done_or_postponed = total_done + total_postponed
                 percent_done = 100.0 * done_or_postponed / total_items
-            self.progress_per_date[date] = '{:.0f}'.format(percent_done)
+            self.progress_per_date[date] = "{:.0f}".format(percent_done)
 
     @property
     def label(self):
@@ -113,24 +108,29 @@ class WorkItemContainer:
 
     @property
     def postponed_items(self):
-        return [item for item in self._items
-                if item.status == SpecificationWorkItemStatus.POSTPONED]
+        return [
+            item
+            for item in self._items
+            if item.status == SpecificationWorkItemStatus.POSTPONED
+        ]
 
     @property
     def percent_done_or_postponed(self):
         """Returns % of work items to be worked on."""
         percent_done = 0
         if len(self._items) > 0:
-            done_or_postponed = (len(self.done_items) +
-                                 len(self.postponed_items))
+            done_or_postponed = len(self.done_items) + len(
+                self.postponed_items
+            )
             percent_done = 100.0 * done_or_postponed / len(self._items)
-        return '{:.0f}'.format(percent_done)
+        return "{:.0f}".format(percent_done)
 
     @property
     def has_incomplete_work(self):
         """Return True if there are incomplete work items."""
-        return (len(self.done_items) + len(self.postponed_items) <
-                len(self._items))
+        return len(self.done_items) + len(self.postponed_items) < len(
+            self._items
+        )
 
     def append(self, item):
         self._items.append(item)
@@ -161,7 +161,7 @@ class SpecWorkItemContainer(WorkItemContainer):
     @property
     def assignee_link(self):
         if self.assignee is None:
-            return 'Nobody'
+            return "Nobody"
         return format_link(self.assignee)
 
     @property
@@ -175,8 +175,9 @@ class SpecWorkItemContainer(WorkItemContainer):
                 SpecificationWorkItemStatus.INPROGRESS: 3,
                 SpecificationWorkItemStatus.TODO: 2,
                 SpecificationWorkItemStatus.BLOCKED: 1,
-                }
+            }
             return status_order[item.status]
+
         return sorted(self._items, key=sort_key)
 
 
@@ -185,24 +186,25 @@ class AggregatedBugsContainer(WorkItemContainer):
 
     @property
     def html_link(self):
-        return 'Bugs targeted to a milestone on this date'
+        return "Bugs targeted to a milestone on this date"
 
     @property
     def assignee_link(self):
-        return 'N/A'
+        return "N/A"
 
     @property
     def target_link(self):
-        return 'N/A'
+        return "N/A"
 
     @property
     def priority_title(self):
-        return 'N/A'
+        return "N/A"
 
     @property
     def items(self):
         def sort_key(item):
             return (item.status.value, item.priority.value)
+
         # Sort by (status, priority) in reverse order because the biggest the
         # status/priority the more interesting it is to us.
         return sorted(self._items, key=sort_key, reverse=True)
@@ -216,8 +218,16 @@ class GenericWorkItem:
     work item it's dealing with.
     """
 
-    def __init__(self, assignee, status, priority, target, title,
-                 bugtask=None, work_item=None):
+    def __init__(
+        self,
+        assignee,
+        status,
+        priority,
+        target,
+        title,
+        bugtask=None,
+        work_item=None,
+    ):
         self.assignee = assignee
         self.status = status
         self.priority = priority
@@ -229,8 +239,13 @@ class GenericWorkItem:
     @classmethod
     def from_bugtask(cls, bugtask):
         return cls(
-            bugtask.assignee, bugtask.status, bugtask.importance,
-            bugtask.target, bugtask.bug.description, bugtask=bugtask)
+            bugtask.assignee,
+            bugtask.status,
+            bugtask.importance,
+            bugtask.target,
+            bugtask.bug.description,
+            bugtask=bugtask,
+        )
 
     @classmethod
     def from_workitem(cls, work_item):
@@ -238,16 +253,21 @@ class GenericWorkItem:
         if assignee is None:
             assignee = work_item.specification.assignee
         return cls(
-            assignee, work_item.status, work_item.specification.priority,
-            work_item.specification.target, work_item.title,
-            work_item=work_item)
+            assignee,
+            work_item.status,
+            work_item.specification.priority,
+            work_item.specification.target,
+            work_item.title,
+            work_item=work_item,
+        )
 
     @property
     def milestone(self):
         milestone = self.actual_workitem.milestone
         if milestone is None:
-            assert self._work_item is not None, (
-                "BugTaks without a milestone must not be here.")
+            assert (
+                self._work_item is not None
+            ), "BugTaks without a milestone must not be here."
             milestone = self._work_item.specification.milestone
         return milestone
 
@@ -277,8 +297,9 @@ def getWorkItemsDueBefore(person, cutoff_date, user):
     Only work items whose milestone have a due date between today and the
     given cut-off date are included in the results.
     """
-    workitems = person.getAssignedSpecificationWorkItemsDueBefore(cutoff_date,
-                                                                  user)
+    workitems = person.getAssignedSpecificationWorkItemsDueBefore(
+        cutoff_date, user
+    )
     # For every specification that has work items in the list above, create
     # one SpecWorkItemContainer holding the work items from that spec that are
     # targeted to the same milestone and assigned to this person (or its
@@ -301,8 +322,7 @@ def getWorkItemsDueBefore(person, cutoff_date, user):
 
     # Sort our containers by priority.
     for date in containers_by_date:
-        containers_by_date[date].sort(
-            key=attrgetter('priority'), reverse=True)
+        containers_by_date[date].sort(key=attrgetter("priority"), reverse=True)
 
     bugtasks = person.getAssignedBugTasksDueBefore(cutoff_date, user)
     bug_containers_by_date = {}
diff --git a/lib/lp/blueprints/browser/specification.py b/lib/lp/blueprints/browser/specification.py
index a4b9cf8..82f9fb5 100644
--- a/lib/lp/blueprints/browser/specification.py
+++ b/lib/lp/blueprints/browser/specification.py
@@ -4,99 +4,77 @@
 """Specification views."""
 
 __all__ = [
-    'NewSpecificationFromDistributionView',
-    'NewSpecificationFromDistroSeriesView',
-    'NewSpecificationFromProductView',
-    'NewSpecificationFromProductSeriesView',
-    'NewSpecificationFromProjectView',
-    'NewSpecificationFromRootView',
-    'NewSpecificationFromSprintView',
-    'SpecificationActionMenu',
-    'SpecificationContextMenu',
-    'SpecificationEditMilestoneView',
-    'SpecificationEditPeopleView',
-    'SpecificationEditPriorityView',
-    'SpecificationEditStatusView',
-    'SpecificationEditView',
-    'SpecificationEditWhiteboardView',
-    'SpecificationEditWorkItemsView',
-    'SpecificationGoalDecideView',
-    'SpecificationGoalProposeView',
-    'SpecificationLinkBranchView',
-    'SpecificationNavigation',
-    'SpecificationProductSeriesGoalProposeView',
-    'SpecificationRetargetingView',
-    'SpecificationSetView',
-    'SpecificationSimpleView',
-    'SpecificationSprintAddView',
-    'SpecificationSupersedingView',
-    'SpecificationTreePNGView',
-    'SpecificationTreeImageTag',
-    'SpecificationTreeDotOutput',
-    'SpecificationView',
-    ]
+    "NewSpecificationFromDistributionView",
+    "NewSpecificationFromDistroSeriesView",
+    "NewSpecificationFromProductView",
+    "NewSpecificationFromProductSeriesView",
+    "NewSpecificationFromProjectView",
+    "NewSpecificationFromRootView",
+    "NewSpecificationFromSprintView",
+    "SpecificationActionMenu",
+    "SpecificationContextMenu",
+    "SpecificationEditMilestoneView",
+    "SpecificationEditPeopleView",
+    "SpecificationEditPriorityView",
+    "SpecificationEditStatusView",
+    "SpecificationEditView",
+    "SpecificationEditWhiteboardView",
+    "SpecificationEditWorkItemsView",
+    "SpecificationGoalDecideView",
+    "SpecificationGoalProposeView",
+    "SpecificationLinkBranchView",
+    "SpecificationNavigation",
+    "SpecificationProductSeriesGoalProposeView",
+    "SpecificationRetargetingView",
+    "SpecificationSetView",
+    "SpecificationSimpleView",
+    "SpecificationSprintAddView",
+    "SpecificationSupersedingView",
+    "SpecificationTreePNGView",
+    "SpecificationTreeImageTag",
+    "SpecificationTreeDotOutput",
+    "SpecificationView",
+]
 
-from operator import attrgetter
 import os
-from subprocess import (
-    PIPE,
-    Popen,
-    )
+from operator import attrgetter
+from subprocess import PIPE, Popen
 
-from lazr.restful.interface import (
-    copy_field,
-    use_template,
-    )
+import six
+from lazr.restful.interface import copy_field, use_template
 from lazr.restful.interfaces import (
     IFieldHTMLRenderer,
     IJSONRequestCache,
     IWebServiceClientRequest,
-    )
-import six
+)
 from zope import component
 from zope.component import getUtility
 from zope.error.interfaces import IErrorReportingUtility
 from zope.formlib import form
 from zope.formlib.form import Fields
 from zope.formlib.widget import CustomWidgetFactory
-from zope.formlib.widgets import (
-    TextAreaWidget,
-    TextWidget,
-    )
-from zope.interface import (
-    implementer,
-    Interface,
-    )
-from zope.schema import (
-    Bool,
-    Choice,
-    TextLine,
-    )
+from zope.formlib.widgets import TextAreaWidget, TextWidget
+from zope.interface import Interface, implementer
+from zope.schema import Bool, Choice, TextLine
 
 from lp import _
 from lp.app.browser.informationtype import InformationTypePortletMixin
 from lp.app.browser.launchpad import AppFrontPageSearchView
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
+    action,
     safe_action,
-    )
+)
 from lp.app.browser.lazrjs import (
     BooleanChoiceWidget,
     EnumChoiceWidget,
     InlinePersonEditPickerWidget,
     TextAreaEditorWidget,
     TextLineEditorWidget,
-    )
-from lp.app.browser.tales import (
-    DateTimeFormatterAPI,
-    format_link,
-    )
-from lp.app.enums import (
-    InformationType,
-    PUBLIC_PROPRIETARY_INFORMATION_TYPES,
-    )
+)
+from lp.app.browser.tales import DateTimeFormatterAPI, format_link
+from lp.app.enums import PUBLIC_PROPRIETARY_INFORMATION_TYPES, InformationType
 from lp.app.utilities import json_dump_information_types
 from lp.app.vocabularies import InformationTypeVocabulary
 from lp.app.widgets.itemswidgets import LaunchpadRadioWidgetWithDescription
@@ -107,12 +85,12 @@ from lp.blueprints.enums import (
     SpecificationFilter,
     SpecificationImplementationStatus,
     SpecificationSort,
-    )
+)
 from lp.blueprints.errors import TargetAlreadyHasSpecification
 from lp.blueprints.interfaces.specification import (
     ISpecification,
     ISpecificationSet,
-    )
+)
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
 from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
 from lp.code.interfaces.branchnamespace import IBranchNamespaceSet
@@ -123,36 +101,47 @@ from lp.services.config import config
 from lp.services.fields import WorkItemsText
 from lp.services.propertycache import cachedproperty
 from lp.services.webapp import (
-    canonical_url,
     LaunchpadView,
     Navigation,
+    canonical_url,
     stepthrough,
     stepto,
-    )
+)
 from lp.services.webapp.authorization import check_permission
 from lp.services.webapp.menu import (
     ContextMenu,
-    enabled_with_permission,
     Link,
     NavigationMenu,
-    )
+    enabled_with_permission,
+)
 from lp.services.webapp.snapshot import notify_modified
 
 
 class INewSpecification(Interface):
     """A schema for a new specification."""
 
-    use_template(ISpecification, include=[
-        'name', 'title', 'specurl', 'summary', 'assignee', 'drafter',
-        'approver'])
+    use_template(
+        ISpecification,
+        include=[
+            "name",
+            "title",
+            "specurl",
+            "summary",
+            "assignee",
+            "drafter",
+            "approver",
+        ],
+    )
 
     definition_status = Choice(
-        title=_('Definition Status'),
+        title=_("Definition Status"),
         vocabulary=NewSpecificationDefinitionStatus,
         default=NewSpecificationDefinitionStatus.NEW,
         description=_(
             "The current status of the process to define the "
-            "feature and get approval for the implementation plan."))
+            "feature and get approval for the implementation plan."
+        ),
+    )
 
 
 class INewSpecificationProjectTarget(Interface):
@@ -160,10 +149,13 @@ class INewSpecificationProjectTarget(Interface):
 
     Requires the user to specify a product from a given project.
     """
+
     target = Choice(
         title=_("For"),
         description=_("The project for which this proposal is being made."),
-        required=True, vocabulary='ProjectProducts')
+        required=True,
+        vocabulary="ProjectProducts",
+    )
 
 
 class INewSpecificationSeriesGoal(Interface):
@@ -171,10 +163,16 @@ class INewSpecificationSeriesGoal(Interface):
 
     Allows the user to propose the specification as a series goal.
     """
-    goal = Bool(title=_('Propose for series goal'),
-                description=_("Check this to indicate that you wish to "
-                              "propose this blueprint as a series goal."),
-                required=True, default=False)
+
+    goal = Bool(
+        title=_("Propose for series goal"),
+        description=_(
+            "Check this to indicate that you wish to "
+            "propose this blueprint as a series goal."
+        ),
+        required=True,
+        default=False,
+    )
 
 
 class INewSpecificationSprint(Interface):
@@ -182,10 +180,15 @@ class INewSpecificationSprint(Interface):
 
     Allows the user to propose the specification for discussion at a sprint.
     """
-    sprint = Choice(title=_("Propose for sprint"),
-                    description=_("The sprint to which agenda this "
-                                  "blueprint is being suggested."),
-                    required=False, vocabulary='FutureSprint')
+
+    sprint = Choice(
+        title=_("Propose for sprint"),
+        description=_(
+            "The sprint to which agenda this " "blueprint is being suggested."
+        ),
+        required=False,
+        vocabulary="FutureSprint",
+    )
 
 
 class INewSpecificationTarget(Interface):
@@ -193,13 +196,14 @@ class INewSpecificationTarget(Interface):
 
     Requires the user to specify a distribution or a product as a target.
     """
-    target = copy_field(ISpecification['target'], readonly=False)
+
+    target = copy_field(ISpecification["target"], readonly=False)
 
 
 class NewSpecificationView(LaunchpadFormView):
     """An abstract view for creating a new specification."""
 
-    page_title = 'Register a blueprint in Launchpad'
+    page_title = "Register a blueprint in Launchpad"
     label = "Register a new blueprint"
 
     custom_widget_specurl = CustomWidgetFactory(TextWidget, displayWidth=60)
@@ -213,9 +217,11 @@ class NewSpecificationView(LaunchpadFormView):
         """
         if len(self.info_types) < 2:
             return fields
-        info_type_field = copy_field(ISpecification['information_type'],
+        info_type_field = copy_field(
+            ISpecification["information_type"],
             readonly=False,
-            vocabulary=InformationTypeVocabulary(types=self.info_types))
+            vocabulary=InformationTypeVocabulary(types=self.info_types),
+        )
         return fields + Fields(info_type_field)
 
     def initialize(self):
@@ -223,34 +229,37 @@ class NewSpecificationView(LaunchpadFormView):
         json_dump_information_types(cache, self.info_types)
         super().initialize()
 
-    @action(_('Register Blueprint'), name='register')
+    @action(_("Register Blueprint"), name="register")
     def register(self, action, data):
         """Registers a new specification."""
         self.transform(data)
-        information_type = data.get('information_type')
+        information_type = data.get("information_type")
         if information_type is None and (
-            IProduct.providedBy(self.context) or
-            IProductSeries.providedBy(self.context)):
+            IProduct.providedBy(self.context)
+            or IProductSeries.providedBy(self.context)
+        ):
             information_type = (
-                self.context.getDefaultSpecificationInformationType())
+                self.context.getDefaultSpecificationInformationType()
+            )
         spec = getUtility(ISpecificationSet).new(
             owner=self.user,
-            name=data.get('name'),
-            title=data.get('title'),
-            specurl=data.get('specurl'),
-            summary=data.get('summary'),
-            target=data.get('product') or data.get('distribution'),
-            drafter=data.get('drafter'),
-            assignee=data.get('assignee'),
-            approver=data.get('approver'),
-            definition_status=data.get('definition_status'),
-            information_type=information_type)
+            name=data.get("name"),
+            title=data.get("title"),
+            specurl=data.get("specurl"),
+            summary=data.get("summary"),
+            target=data.get("product") or data.get("distribution"),
+            drafter=data.get("drafter"),
+            assignee=data.get("assignee"),
+            approver=data.get("approver"),
+            definition_status=data.get("definition_status"),
+            information_type=information_type,
+        )
         # Propose the specification as a series goal, if specified.
-        series = data.get('series')
+        series = data.get("series")
         if series is not None:
             spec.proposeGoal(series, self.user)
         # Propose the specification as a sprint topic, if specified.
-        sprint = data.get('sprint')
+        sprint = data.get("sprint")
         if sprint is not None:
             spec.linkSprint(sprint, self.user)
         # Set the default value for the next URL.
@@ -291,27 +300,30 @@ class NewSpecificationView(LaunchpadFormView):
     def initial_values(self):
         """Set initial values to honor sharing policy default value."""
         information_type = InformationType.PUBLIC
-        if (IProduct.providedBy(self.context) or
-            IProductSeries.providedBy(self.context)):
+        if IProduct.providedBy(self.context) or IProductSeries.providedBy(
+            self.context
+        ):
             information_type = (
-                self.context.getDefaultSpecificationInformationType())
-        values = {'information_type': information_type}
+                self.context.getDefaultSpecificationInformationType()
+            )
+        values = {"information_type": information_type}
         return values
 
     def validate(self, data):
         """See `LaunchpadFormView`.`"""
         super().validate(data)
-        information_type = data.get('information_type', None)
+        information_type = data.get("information_type", None)
         if information_type is None:
             # We rely on the model to set the correct default value.
             return
         else:
             # In the case of views outside a target context it's part of the
             # form.
-            product = IProduct(data.get('target', None), None)
+            product = IProduct(data.get("target", None), None)
 
-            if (IProduct.providedBy(self.context) or
-                IProductSeries.providedBy(self.context)):
+            if IProduct.providedBy(self.context) or IProductSeries.providedBy(
+                self.context
+            ):
                 product = self.context
 
             if product:
@@ -319,13 +331,15 @@ class NewSpecificationView(LaunchpadFormView):
                 # Check that the information type is a valid one for this
                 # Product.
                 if information_type not in allowed:
-                    error = ('This information type is not permitted for '
-                             'this product')
-                    self.setFieldError('information_type', error)
+                    error = (
+                        "This information type is not permitted for "
+                        "this product"
+                    )
+                    self.setFieldError("information_type", error)
 
     def setUpWidgets(self):
         super().setUpWidgets()
-        widget = self.widgets['drafter']
+        widget = self.widgets["drafter"]
         widget.setRenderedValue(self.user)
 
 
@@ -349,14 +363,14 @@ class NewSpecificationFromDistributionView(NewSpecificationFromTargetView):
     """A view for creating a specification from a distribution."""
 
     def transform(self, data):
-        data['distribution'] = self.context
+        data["distribution"] = self.context
 
 
 class NewSpecificationFromProductView(NewSpecificationFromTargetView):
     """A view for creating a specification from a product."""
 
     def transform(self, data):
-        data['product'] = self.context
+        data["product"] = self.context
 
 
 class NewSpecificationFromSeriesView(NewSpecificationFromTargetView):
@@ -364,14 +378,16 @@ class NewSpecificationFromSeriesView(NewSpecificationFromTargetView):
 
     @property
     def schema(self):
-        fields = Fields(INewSpecification,
-                        INewSpecificationSprint,
-                        INewSpecificationSeriesGoal)
+        fields = Fields(
+            INewSpecification,
+            INewSpecificationSprint,
+            INewSpecificationSeriesGoal,
+        )
         return self.append_info_type(fields)
 
     def transform(self, data):
-        if data['goal']:
-            data['series'] = self.context
+        if data["goal"]:
+            data["series"] = self.context
 
 
 class NewSpecificationFromDistroSeriesView(NewSpecificationFromSeriesView):
@@ -379,7 +395,7 @@ class NewSpecificationFromDistroSeriesView(NewSpecificationFromSeriesView):
 
     def transform(self, data):
         super().transform(data)
-        data['distribution'] = self.context.distribution
+        data["distribution"] = self.context.distribution
 
 
 class NewSpecificationFromProductSeriesView(NewSpecificationFromSeriesView):
@@ -387,7 +403,7 @@ class NewSpecificationFromProductSeriesView(NewSpecificationFromSeriesView):
 
     def transform(self, data):
         super().transform(data)
-        data['product'] = self.context.product
+        data["product"] = self.context.product
 
 
 class NewSpecificationFromNonTargetView(NewSpecificationView):
@@ -396,11 +412,12 @@ class NewSpecificationFromNonTargetView(NewSpecificationView):
     The context may not correspond to a unique specification target. Hence
     sub-classes must define a schema requiring the user to specify a target.
     """
+
     info_types = PUBLIC_PROPRIETARY_INFORMATION_TYPES
 
     def transform(self, data):
-        data['distribution'] = IDistribution(data['target'], None)
-        data['product'] = IProduct(data['target'], None)
+        data["distribution"] = IDistribution(data["target"], None)
+        data["product"] = IProduct(data["target"], None)
 
     def validate(self, data):
         """Ensures that the name for the new specification is unique.
@@ -408,12 +425,12 @@ class NewSpecificationFromNonTargetView(NewSpecificationView):
         The name must be unique within the context of the chosen target.
         """
         super().validate(data)
-        name = data.get('name')
-        target = data.get('target')
+        name = data.get("name")
+        target = data.get("target")
         if name is not None and target is not None:
             if target.getSpecification(name):
-                errormessage = INewSpecification['name'].errormessage
-                self.setFieldError('name', errormessage % name)
+                errormessage = INewSpecification["name"].errormessage
+                self.setFieldError("name", errormessage % name)
 
 
 class NewSpecificationFromProjectView(NewSpecificationFromNonTargetView):
@@ -421,9 +438,11 @@ class NewSpecificationFromProjectView(NewSpecificationFromNonTargetView):
 
     @property
     def schema(self):
-        fields = Fields(INewSpecificationProjectTarget,
-                        INewSpecification,
-                        INewSpecificationSprint)
+        fields = Fields(
+            INewSpecificationProjectTarget,
+            INewSpecification,
+            INewSpecificationSprint,
+        )
         return self.append_info_type(fields)
 
 
@@ -432,9 +451,9 @@ class NewSpecificationFromRootView(NewSpecificationFromNonTargetView):
 
     @property
     def schema(self):
-        fields = Fields(INewSpecificationTarget,
-                        INewSpecification,
-                        INewSpecificationSprint)
+        fields = Fields(
+            INewSpecificationTarget, INewSpecification, INewSpecificationSprint
+        )
         return self.append_info_type(fields)
 
 
@@ -448,7 +467,7 @@ class NewSpecificationFromSprintView(NewSpecificationFromNonTargetView):
 
     def transform(self, data):
         super().transform(data)
-        data['sprint'] = self.context
+        data["sprint"] = self.context
 
     @property
     def next_url(self):
@@ -459,14 +478,15 @@ class SpecificationNavigation(Navigation):
 
     usedfor = ISpecification
 
-    @stepthrough('+subscription')
+    @stepthrough("+subscription")
     def traverse_subscriptions(self, name):
         return self.context.getSubscriptionByName(name)
 
-    @stepto('+branch')
+    @stepto("+branch")
     def traverse_branch(self):
         branch = getUtility(IBranchNamespaceSet).traverse(
-            iter(self.request.stepstogo))
+            iter(self.request.stepstogo)
+        )
         return self.context.getBranchLink(branch)
 
     def traverse(self, name):
@@ -476,147 +496,167 @@ class SpecificationNavigation(Navigation):
 
 
 class SpecificationEditLinksMixin:
-
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def edit(self):
-        text = 'Change details'
-        return Link('+edit', text, icon='edit')
+        text = "Change details"
+        return Link("+edit", text, icon="edit")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def supersede(self):
-        text = 'Mark superseded'
-        return Link('+supersede', text, icon='edit')
+        text = "Mark superseded"
+        return Link("+supersede", text, icon="edit")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def retarget(self):
-        text = 'Re-target blueprint'
-        return Link('+retarget', text, icon='edit')
+        text = "Re-target blueprint"
+        return Link("+retarget", text, icon="edit")
 
 
 class SpecificationActionMenu(NavigationMenu, SpecificationEditLinksMixin):
 
     usedfor = ISpecification
-    facet = 'specifications'
-    links = ('edit', 'supersede', 'retarget')
+    facet = "specifications"
+    links = ("edit", "supersede", "retarget")
 
 
 class SpecificationContextMenu(ContextMenu, SpecificationEditLinksMixin):
 
     usedfor = ISpecification
-    links = ['edit', 'people', 'status', 'priority', 'whiteboard',
-             'proposegoal', 'workitems', 'milestone', 'subscription',
-             'addsubscriber', 'linkbug', 'unlinkbug', 'linkbranch',
-             'adddependency', 'removedependency', 'dependencytree',
-             'linksprint', 'supersede', 'retarget', 'information_type']
+    links = [
+        "edit",
+        "people",
+        "status",
+        "priority",
+        "whiteboard",
+        "proposegoal",
+        "workitems",
+        "milestone",
+        "subscription",
+        "addsubscriber",
+        "linkbug",
+        "unlinkbug",
+        "linkbranch",
+        "adddependency",
+        "removedependency",
+        "dependencytree",
+        "linksprint",
+        "supersede",
+        "retarget",
+        "information_type",
+    ]
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def milestone(self):
-        text = 'Target milestone'
-        return Link('+milestone', text, icon='edit')
+        text = "Target milestone"
+        return Link("+milestone", text, icon="edit")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def people(self):
-        text = 'Change people'
-        return Link('+people', text, icon='edit')
+        text = "Change people"
+        return Link("+people", text, icon="edit")
 
-    @enabled_with_permission('launchpad.Admin')
+    @enabled_with_permission("launchpad.Admin")
     def priority(self):
-        text = 'Change priority'
-        return Link('+priority', text, icon='edit')
+        text = "Change priority"
+        return Link("+priority", text, icon="edit")
 
-    @enabled_with_permission('launchpad.AnyPerson')
+    @enabled_with_permission("launchpad.AnyPerson")
     def proposegoal(self):
-        text = 'Propose as goal'
+        text = "Propose as goal"
         if self.context.goal is not None:
-            text = 'Modify goal'
+            text = "Modify goal"
         if self.context.distribution is not None:
-            link = '+setdistroseries'
+            link = "+setdistroseries"
         elif self.context.product is not None:
-            link = '+setproductseries'
+            link = "+setproductseries"
         else:
             raise AssertionError(
-                'Unknown target on specification "%s".' % self.context.name)
-        return Link(link, text, icon='edit')
+                'Unknown target on specification "%s".' % self.context.name
+            )
+        return Link(link, text, icon="edit")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def status(self):
-        text = 'Change status'
-        return Link('+status', text, icon='edit')
+        text = "Change status"
+        return Link("+status", text, icon="edit")
 
     def addsubscriber(self):
         """Return the 'Subscribe someone else' Link."""
-        text = 'Subscribe someone else'
-        return Link('+addsubscriber', text, icon='add')
+        text = "Subscribe someone else"
+        return Link("+addsubscriber", text, icon="add")
 
     def subscription(self):
         """Return the 'Edit Subscription' Link."""
         user = self.user
         if user is None:
-            return Link('+subscribe', 'Edit subscription', icon='edit')
+            return Link("+subscribe", "Edit subscription", icon="edit")
 
         if self.context.isSubscribed(user):
             return Link(
-                '+subscription/%s' % user.name,
-                'Update subscription', icon='edit')
+                "+subscription/%s" % user.name,
+                "Update subscription",
+                icon="edit",
+            )
         else:
-            return Link('+subscribe', 'Subscribe', icon='add')
+            return Link("+subscribe", "Subscribe", icon="add")
 
-    @enabled_with_permission('launchpad.AnyPerson')
+    @enabled_with_permission("launchpad.AnyPerson")
     def linkbug(self):
-        text = 'Link a bug report'
-        return Link('+linkbug', text, icon='add')
+        text = "Link a bug report"
+        return Link("+linkbug", text, icon="add")
 
-    @enabled_with_permission('launchpad.AnyPerson')
+    @enabled_with_permission("launchpad.AnyPerson")
     def unlinkbug(self):
-        text = 'Unlink a bug'
+        text = "Unlink a bug"
         enabled = bool(self.context.bugs)
-        return Link('+unlinkbug', text, icon='remove', enabled=enabled)
+        return Link("+unlinkbug", text, icon="remove", enabled=enabled)
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def adddependency(self):
-        text = 'Add dependency'
-        return Link('+linkdependency', text, icon='add')
+        text = "Add dependency"
+        return Link("+linkdependency", text, icon="add")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def removedependency(self):
-        text = 'Remove dependency'
+        text = "Remove dependency"
         enabled = bool(self.context.getDependencies(self.user))
-        return Link('+removedependency', text, icon='remove', enabled=enabled)
+        return Link("+removedependency", text, icon="remove", enabled=enabled)
 
     def dependencytree(self):
-        text = 'Show dependencies'
-        enabled = (bool(self.context.getDependencies(self.user)) or
-                   bool(self.context.getBlockedSpecs(self.user)))
-        return Link('+deptree', text, icon='info', enabled=enabled)
+        text = "Show dependencies"
+        enabled = bool(self.context.getDependencies(self.user)) or bool(
+            self.context.getBlockedSpecs(self.user)
+        )
+        return Link("+deptree", text, icon="info", enabled=enabled)
 
-    @enabled_with_permission('launchpad.AnyPerson')
+    @enabled_with_permission("launchpad.AnyPerson")
     def linksprint(self):
-        text = 'Propose for sprint'
-        return Link('+linksprint', text, icon='add')
+        text = "Propose for sprint"
+        return Link("+linksprint", text, icon="add")
 
-    @enabled_with_permission('launchpad.AnyPerson')
+    @enabled_with_permission("launchpad.AnyPerson")
     def whiteboard(self):
-        text = 'Edit whiteboard'
-        return Link('+whiteboard', text, icon='edit')
+        text = "Edit whiteboard"
+        return Link("+whiteboard", text, icon="edit")
 
-    @enabled_with_permission('launchpad.AnyPerson')
+    @enabled_with_permission("launchpad.AnyPerson")
     def workitems(self):
-        text = 'Edit work items'
-        return Link('+workitems', text, icon='edit')
+        text = "Edit work items"
+        return Link("+workitems", text, icon="edit")
 
-    @enabled_with_permission('launchpad.AnyPerson')
+    @enabled_with_permission("launchpad.AnyPerson")
     def linkbranch(self):
         if len(self.context.linked_branches) > 0:
-            text = 'Link to another branch'
+            text = "Link to another branch"
         else:
-            text = 'Link a related branch'
-        return Link('+linkbranch', text, icon='add')
+            text = "Link a related branch"
+        return Link("+linkbranch", text, icon="add")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def information_type(self):
         """Return the 'Set privacy/security' Link."""
-        text = 'Change privacy/security'
-        return Link('#', text)
+        text = "Change privacy/security"
+        return Link("#", text)
 
 
 class SpecificationSimpleView(InformationTypePortletMixin, LaunchpadView):
@@ -624,13 +664,17 @@ class SpecificationSimpleView(InformationTypePortletMixin, LaunchpadView):
 
     @cachedproperty
     def has_dep_tree(self):
-        return (self.context.getDependencies(self.user) or
-            self.context.getBlockedSpecs(self.user))
+        return self.context.getDependencies(
+            self.user
+        ) or self.context.getBlockedSpecs(self.user)
 
     @cachedproperty
     def linked_branches(self):
-        return [branch_link for branch_link in self.context.linked_branches
-                if check_permission('launchpad.View', branch_link.branch)]
+        return [
+            branch_link
+            for branch_link in self.context.linked_branches
+            if check_permission("launchpad.View", branch_link.branch)
+        ]
 
     @cachedproperty
     def bug_links(self):
@@ -639,9 +683,9 @@ class SpecificationSimpleView(InformationTypePortletMixin, LaunchpadView):
     @cachedproperty
     def privacy_portlet_css(self):
         if self.context.private:
-            return 'portlet private'
+            return "portlet private"
         else:
-            return 'portlet public'
+            return "portlet public"
 
 
 class SpecificationView(SpecificationSimpleView):
@@ -670,122 +714,160 @@ class SpecificationView(SpecificationSimpleView):
     @property
     def approver_widget(self):
         return InlinePersonEditPickerWidget(
-            self.context, ISpecification['approver'],
+            self.context,
+            ISpecification["approver"],
             format_link(self.context.approver),
-            header='Change approver', edit_view='+people',
-            step_title='Select a new approver')
+            header="Change approver",
+            edit_view="+people",
+            step_title="Select a new approver",
+        )
 
     @property
     def drafter_widget(self):
         return InlinePersonEditPickerWidget(
-            self.context, ISpecification['drafter'],
+            self.context,
+            ISpecification["drafter"],
             format_link(self.context.drafter),
-            header='Change drafter', edit_view='+people',
-            step_title='Select a new drafter')
+            header="Change drafter",
+            edit_view="+people",
+            step_title="Select a new drafter",
+        )
 
     @property
     def assignee_widget(self):
         return InlinePersonEditPickerWidget(
-            self.context, ISpecification['assignee'],
+            self.context,
+            ISpecification["assignee"],
             format_link(self.context.assignee),
-            header='Change assignee', edit_view='+people',
-            step_title='Select a new assignee')
+            header="Change assignee",
+            edit_view="+people",
+            step_title="Select a new assignee",
+        )
 
     @property
     def definition_status_widget(self):
         return EnumChoiceWidget(
-            self.context, ISpecification['definition_status'],
-            header='Change definition status to', edit_view='+status',
-            edit_title='Change definition status',
-            css_class_prefix='specstatus')
+            self.context,
+            ISpecification["definition_status"],
+            header="Change definition status to",
+            edit_view="+status",
+            edit_title="Change definition status",
+            css_class_prefix="specstatus",
+        )
 
     @property
     def implementation_status_widget(self):
         return EnumChoiceWidget(
-            self.context, ISpecification['implementation_status'],
-            header='Change implementation status to', edit_view='+status',
-            edit_title='Change implementation status',
-            css_class_prefix='specdelivery')
+            self.context,
+            ISpecification["implementation_status"],
+            header="Change implementation status to",
+            edit_view="+status",
+            edit_title="Change implementation status",
+            css_class_prefix="specdelivery",
+        )
 
     @property
     def priority_widget(self):
         return EnumChoiceWidget(
-            self.context, ISpecification['priority'],
-            header='Change priority to', edit_view='+priority',
-            edit_title='Change priority',
-            css_class_prefix='specpriority')
+            self.context,
+            ISpecification["priority"],
+            header="Change priority to",
+            edit_view="+priority",
+            edit_title="Change priority",
+            css_class_prefix="specpriority",
+        )
 
     @property
     def title_widget(self):
-        field = ISpecification['title']
+        field = ISpecification["title"]
         title = "Edit the blueprint title"
         return TextLineEditorWidget(
-            self.context, field, title, 'h1', max_width='95%',
-            truncate_lines=2)
+            self.context, field, title, "h1", max_width="95%", truncate_lines=2
+        )
 
     @property
     def summary_widget(self):
         """The summary as a widget."""
         return TextAreaEditorWidget(
-            self.context, ISpecification['summary'], title="")
+            self.context, ISpecification["summary"], title=""
+        )
 
     @property
     def whiteboard_widget(self):
         """The description as a widget."""
         return TextAreaEditorWidget(
-            self.context, ISpecification['whiteboard'], title="Whiteboard",
-            edit_view='+whiteboard', edit_title='Edit whiteboard',
-            hide_empty=False)
+            self.context,
+            ISpecification["whiteboard"],
+            title="Whiteboard",
+            edit_view="+whiteboard",
+            edit_title="Edit whiteboard",
+            hide_empty=False,
+        )
 
     @property
     def workitems_text_widget(self):
         """The Work Items text as a widget."""
         return TextAreaEditorWidget(
-            self.context, ISpecification['workitems_text'], title="Work Items",
-            edit_view='+workitems', edit_title='Edit work items',
-            hide_empty=False)
+            self.context,
+            ISpecification["workitems_text"],
+            title="Work Items",
+            edit_view="+workitems",
+            edit_title="Edit work items",
+            hide_empty=False,
+        )
 
     @property
     def direction_widget(self):
         return BooleanChoiceWidget(
-            self.context, ISpecification['direction_approved'],
-            tag='span',
-            false_text='Needs approval',
-            true_text='Approved',
-            header='Change approval of basic direction')
+            self.context,
+            ISpecification["direction_approved"],
+            tag="span",
+            false_text="Needs approval",
+            true_text="Approved",
+            header="Change approval of basic direction",
+        )
 
 
 class SpecificationEditSchema(ISpecification):
     """Provide overrides for the implementaion and definition status."""
 
     definition_status = Choice(
-        title=_('Definition Status'), required=True,
+        title=_("Definition Status"),
+        required=True,
         vocabulary=SpecificationDefinitionStatus,
         default=SpecificationDefinitionStatus.NEW,
         description=_(
             "The current status of the process to define the "
-            "feature and get approval for the implementation plan."))
+            "feature and get approval for the implementation plan."
+        ),
+    )
 
     implementation_status = Choice(
-        title=_("Implementation Status"), required=True,
+        title=_("Implementation Status"),
+        required=True,
         default=SpecificationImplementationStatus.UNKNOWN,
         vocabulary=SpecificationImplementationStatus,
         description=_(
             "The state of progress being made on the actual "
-            "implementation or delivery of this feature."))
+            "implementation or delivery of this feature."
+        ),
+    )
 
     workitems_text = WorkItemsText(
-        title=_('Work Items'), required=True,
+        title=_("Work Items"),
+        required=True,
         description=_(
             "Work items for this specification input in a text format. "
-            "Your changes will override the current work items."))
+            "Your changes will override the current work items."
+        ),
+    )
 
 
 class SpecificationEditView(LaunchpadEditFormView):
 
     schema = SpecificationEditSchema
-    field_names = ['name', 'title', 'specurl', 'summary', 'whiteboard']
-    label = 'Edit specification'
+    field_names = ["name", "title", "specurl", "summary", "whiteboard"]
+    label = "Edit specification"
     custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5)
     custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=10)
     custom_widget_specurl = CustomWidgetFactory(TextWidget, displayWidth=60)
@@ -795,7 +877,7 @@ class SpecificationEditView(LaunchpadEditFormView):
         """See `LaunchpadFormView`"""
         return {SpecificationEditSchema: self.context}
 
-    @action(_('Change'), name='change')
+    @action(_("Change"), name="change")
     def change_action(self, action, data):
         old_status = self.context.lifecycle_status
         self.updateContextFromData(data)
@@ -804,47 +886,51 @@ class SpecificationEditView(LaunchpadEditFormView):
         new_status = self.context.lifecycle_status
         if new_status != old_status:
             self.request.response.addNotification(
-                'Blueprint is now considered "%s".' % new_status.title)
+                'Blueprint is now considered "%s".' % new_status.title
+            )
         self.next_url = canonical_url(self.context)
 
 
 class SpecificationEditWhiteboardView(SpecificationEditView):
-    label = 'Edit specification status whiteboard'
-    field_names = ['whiteboard']
+    label = "Edit specification status whiteboard"
+    field_names = ["whiteboard"]
     custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=15)
 
 
 class SpecificationEditWorkItemsView(SpecificationEditView):
-    label = 'Edit specification work items'
-    field_names = ['workitems_text']
+    label = "Edit specification work items"
+    field_names = ["workitems_text"]
     custom_widget_workitems_text = CustomWidgetFactory(
-        TextAreaWidget, height=15)
+        TextAreaWidget, height=15
+    )
 
-    @action(_('Change'), name='change')
+    @action(_("Change"), name="change")
     def change_action(self, action, data):
-        with notify_modified(self.context, ['workitems_text']):
-            self.context.setWorkItems(data['workitems_text'])
+        with notify_modified(self.context, ["workitems_text"]):
+            self.context.setWorkItems(data["workitems_text"])
         self.next_url = canonical_url(self.context)
 
 
 class SpecificationEditPeopleView(SpecificationEditView):
-    label = 'Change the people involved'
-    field_names = ['assignee', 'drafter', 'approver', 'whiteboard']
+    label = "Change the people involved"
+    field_names = ["assignee", "drafter", "approver", "whiteboard"]
 
 
 class SpecificationEditPriorityView(SpecificationEditView):
-    label = 'Change priority'
-    field_names = ['priority', 'direction_approved', 'whiteboard']
+    label = "Change priority"
+    field_names = ["priority", "direction_approved", "whiteboard"]
 
     @property
     def extra_info(self):
-        return ('The priority should reflect the views of the coordinating '
-                'team of %s.' % self.context.target.displayname)
+        return (
+            "The priority should reflect the views of the coordinating "
+            "team of %s." % self.context.target.displayname
+        )
 
 
 class SpecificationEditStatusView(SpecificationEditView):
-    label = 'Change status'
-    field_names = ['definition_status', 'implementation_status', 'whiteboard']
+    label = "Change status"
+    field_names = ["definition_status", "implementation_status", "whiteboard"]
 
 
 class SpecificationInformationTypeEditView(LaunchpadFormView):
@@ -852,11 +938,11 @@ class SpecificationInformationTypeEditView(LaunchpadFormView):
 
     @property
     def label(self):
-        return 'Set information type'
+        return "Set information type"
 
     page_title = label
 
-    field_names = ['information_type']
+    field_names = ["information_type"]
 
     custom_widget_information_type = LaunchpadRadioWidgetWithDescription
 
@@ -867,8 +953,11 @@ class SpecificationInformationTypeEditView(LaunchpadFormView):
 
         class information_type_schema(Interface):
             information_type_field = copy_field(
-                ISpecification['information_type'], readonly=False,
-                vocabulary=InformationTypeVocabulary(types=info_types))
+                ISpecification["information_type"],
+                readonly=False,
+                vocabulary=InformationTypeVocabulary(types=info_types),
+            )
+
         return information_type_schema
 
     @property
@@ -881,47 +970,49 @@ class SpecificationInformationTypeEditView(LaunchpadFormView):
     @property
     def initial_values(self):
         """See `LaunchpadFormView.`"""
-        return {'information_type': self.context.information_type}
+        return {"information_type": self.context.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)
-        information_type = data.pop('information_type')
+        information_type = data.pop("information_type")
         self.context.transitionToInformationType(information_type, self.user)
-        return ''
+        return ""
 
 
 class SpecificationEditMilestoneView(SpecificationEditView):
-    label = 'Target to a milestone'
-    field_names = ['milestone', 'whiteboard']
+    label = "Target to a milestone"
+    field_names = ["milestone", "whiteboard"]
 
     @property
     def extra_info(self):
-        return ("Select the milestone of %s in which you would like this "
-                "feature to be implemented."
-                % self.context.target.displayname)
+        return (
+            "Select the milestone of %s in which you would like this "
+            "feature to be implemented." % self.context.target.displayname
+        )
 
 
 class SpecificationGoalProposeView(LaunchpadEditFormView):
     schema = ISpecification
-    label = 'Target to a distribution series'
-    field_names = ['distroseries', 'whiteboard']
+    label = "Target to a distribution series"
+    field_names = ["distroseries", "whiteboard"]
     custom_widget_whiteboard = CustomWidgetFactory(TextAreaWidget, height=5)
 
     @property
     def initial_values(self):
         return {
-            'productseries': self.context.productseries,
-            'distroseries': self.context.distroseries,
-            'whiteboard': self.context.whiteboard,
-            }
+            "productseries": self.context.productseries,
+            "distroseries": self.context.distroseries,
+            "whiteboard": self.context.whiteboard,
+        }
 
-    @action('Continue', name='continue')
+    @action("Continue", name="continue")
     def continue_action(self, action, data):
-        self.context.whiteboard = data['whiteboard']
-        self.context.proposeGoal(data['distroseries'], self.user)
+        self.context.whiteboard = data["whiteboard"]
+        self.context.proposeGoal(data["distroseries"], self.user)
         self.next_url = canonical_url(self.context)
 
     @property
@@ -930,13 +1021,13 @@ class SpecificationGoalProposeView(LaunchpadEditFormView):
 
 
 class SpecificationProductSeriesGoalProposeView(SpecificationGoalProposeView):
-    label = 'Target to a product series'
-    field_names = ['productseries', 'whiteboard']
+    label = "Target to a product series"
+    field_names = ["productseries", "whiteboard"]
 
-    @action('Continue', name='continue')
+    @action("Continue", name="continue")
     def continue_action(self, action, data):
-        self.context.whiteboard = data['whiteboard']
-        self.context.proposeGoal(data['productseries'], self.user)
+        self.context.whiteboard = data["whiteboard"]
+        self.context.proposeGoal(data["productseries"], self.user)
         self.next_url = canonical_url(self.context)
 
     @property
@@ -958,11 +1049,11 @@ class SpecificationGoalDecideView(LaunchpadFormView):
     def label(self):
         return _("Accept as %s series goal?") % self.context.goal.name
 
-    @action(_('Accept'), name='accept')
+    @action(_("Accept"), name="accept")
     def accept_action(self, action, data):
         self.context.acceptBy(self.user)
 
-    @action(_('Decline'), name='decline')
+    @action(_("Decline"), name="decline")
     def decline_action(self, action, data):
         self.context.declineBy(self.user)
 
@@ -975,14 +1066,14 @@ class SpecificationGoalDecideView(LaunchpadFormView):
 
 class ISpecificationRetargetingSchema(Interface):
 
-    target = copy_field(ISpecification['target'], readonly=False)
+    target = copy_field(ISpecification["target"], readonly=False)
 
 
 class SpecificationRetargetingView(LaunchpadFormView):
 
     schema = ISpecificationRetargetingSchema
-    field_names = ['target']
-    label = _('Move this blueprint to a different project')
+    field_names = ["target"]
+    label = _("Move this blueprint to a different project")
 
     def validate(self, data):
         """Ensure that the target is valid and that there is not
@@ -990,26 +1081,30 @@ class SpecificationRetargetingView(LaunchpadFormView):
         given target.
         """
 
-        target = data.get('target')
+        target = data.get("target")
 
         if target is None:
-            self.setFieldError('target',
+            self.setFieldError(
+                "target",
                 "There is no project with the name '%s'. "
-                "Please check that name and try again." %
-                self.request.form.get("field.target"))
+                "Please check that name and try again."
+                % self.request.form.get("field.target"),
+            )
             return
 
         try:
             self.context.validateMove(target)
         except TargetAlreadyHasSpecification:
-            self.setFieldError('target',
-                'There is already a blueprint with this name for %s. '
-                'Please change the name of this blueprint and try again.' %
-                target.displayname)
+            self.setFieldError(
+                "target",
+                "There is already a blueprint with this name for %s. "
+                "Please change the name of this blueprint and try again."
+                % target.displayname,
+            )
 
-    @action(_('Retarget Blueprint'), name='retarget')
+    @action(_("Retarget Blueprint"), name="retarget")
     def retarget_action(self, action, data):
-        self.context.retarget(data['target'])
+        self.context.retarget(data["target"])
         self._nextURL = canonical_url(self.context)
 
     @property
@@ -1023,52 +1118,57 @@ class SpecificationRetargetingView(LaunchpadFormView):
 
 class SpecificationSupersedingView(LaunchpadFormView):
     schema = ISpecification
-    field_names = ['superseded_by']
-    label = _('Mark blueprint superseded')
+    field_names = ["superseded_by"]
+    label = _("Mark blueprint superseded")
 
     @property
     def initial_values(self):
         name = None
         if self.context.superseded_by:
             name = self.context.superseded_by.name
-        return {'superseded_by': name}
+        return {"superseded_by": name}
 
     def setUpFields(self):
         """Override the setup to define own fields."""
         self.form_fields = form.Fields(
             TextLine(
-                __name__='superseded_by',
+                __name__="superseded_by",
                 title=_("Superseded by"),
                 required=False,
                 description=_(
                     "The blueprint which supersedes this one. Note "
                     "that entering a blueprint here and pressing "
                     "Continue will change the blueprint status "
-                    "to Superseded.")),
-            render_context=self.render_context)
+                    "to Superseded."
+                ),
+            ),
+            render_context=self.render_context,
+        )
 
     def validate(self, data):
         """See `LaunchpadFormView`.`"""
         super().validate(data)
-        if data['superseded_by']:
+        if data["superseded_by"]:
             spec = getUtility(ISpecificationSet).getByName(
-                self.context.target, data['superseded_by'])
+                self.context.target, data["superseded_by"]
+            )
             if spec:
-                data['superseded_by'] = spec
+                data["superseded_by"] = spec
             else:
                 self.setFieldError(
-                    'superseded_by',
-                    "No blueprint named '%s'." % data['superseded_by'])
+                    "superseded_by",
+                    "No blueprint named '%s'." % data["superseded_by"],
+                )
 
-    @action(_('Continue'), name='supersede')
+    @action(_("Continue"), name="supersede")
     def supersede_action(self, action, data):
         # Store some shorter names to avoid line-wrapping.
         SUPERSEDED = SpecificationDefinitionStatus.SUPERSEDED
         NEW = SpecificationDefinitionStatus.NEW
-        self.context.superseded_by = data['superseded_by']
+        self.context.superseded_by = data["superseded_by"]
         # XXX: salgado, 2010-11-24, bug=680880: This logic should be in model
         # code.
-        if data['superseded_by'] is not None:
+        if data["superseded_by"] is not None:
             # set the state to superseded
             self.context.definition_status = SUPERSEDED
         else:
@@ -1080,7 +1180,8 @@ class SpecificationSupersedingView(LaunchpadFormView):
         newstate = self.context.updateLifecycleStatus(self.user)
         if newstate is not None:
             self.request.response.addNotification(
-                'Blueprint is now considered "%s".' % newstate.title)
+                'Blueprint is now considered "%s".' % newstate.title
+            )
         self.next_url = canonical_url(self.context)
 
     @property
@@ -1113,10 +1214,14 @@ class SpecGraph:
         """
         if self.getNode(spec) is not None:
             raise ValueError(
-            "A spec called %s/+spec/%s is already in the graph" %
-            (spec.target.name, spec.name))
-        node = SpecGraphNode(spec, root=root,
-                url_pattern_for_testing=self.url_pattern_for_testing)
+                "A spec called %s/+spec/%s is already in the graph"
+                % (spec.target.name, spec.name)
+            )
+        node = SpecGraphNode(
+            spec,
+            root=root,
+            url_pattern_for_testing=self.url_pattern_for_testing,
+        )
         self.nodes.add(node)
         if root:
             assert not self.root_node
@@ -1125,7 +1230,7 @@ class SpecGraph:
 
     def getNode(self, spec):
         """Return the node with the given spec, or None if not matched."""
-        unique_name = '%s-%s' % (spec.target.name, spec.name)
+        unique_name = "%s-%s" % (spec.target.name, spec.name)
         for node in self.nodes:
             if node.name == unique_name:
                 return node
@@ -1153,20 +1258,24 @@ class SpecGraph:
         """Add nodes for the specs that the given spec depends on,
         transitively.
         """
+
         def get_related_specs_fn(spec):
             return spec.getDependencies(self.user)
 
         def link_nodes_fn(node, dependency):
             self.link(dependency, node)
+
         self.walkSpecsMakingNodes(spec, get_related_specs_fn, link_nodes_fn)
 
     def addBlockedNodes(self, spec):
         """Add nodes for specs that the given spec blocks, transitively."""
+
         def get_related_specs_fn(spec):
             return spec.getBlockedSpecs(self.user)
 
         def link_nodes_fn(node, blocked_spec):
             self.link(node, blocked_spec)
+
         self.walkSpecsMakingNodes(spec, get_related_specs_fn, link_nodes_fn)
 
     def walkSpecsMakingNodes(self, spec, get_related_specs_fn, link_nodes_fn):
@@ -1193,15 +1302,16 @@ class SpecGraph:
 
     def getNodesSorted(self):
         """Return a list of all nodes, sorted by name."""
-        return sorted(self.nodes, key=attrgetter('name'))
+        return sorted(self.nodes, key=attrgetter("name"))
 
     def getEdgesSorted(self):
         """Return a list of all edges, sorted by name.
 
         An edge is a tuple (from_node, to_node).
         """
-        return sorted(self.edges,
-            key=lambda nodes: (nodes[0].name, nodes[1].name))
+        return sorted(
+            self.edges, key=lambda nodes: (nodes[0].name, nodes[1].name)
+        )
 
     def listNodes(self):
         """Return a string of diagnostic output of nodes and edges.
@@ -1211,15 +1321,16 @@ class SpecGraph:
         L = []
         edges = self.getEdgesSorted()
         if self.root_node:
-            L.append('Root is %s' % self.root_node)
+            L.append("Root is %s" % self.root_node)
         else:
-            L.append('Root is undefined')
+            L.append("Root is undefined")
         for node in self.getNodesSorted():
-            L.append('%s:' % node)
-            to_nodes = [to_node for from_node, to_node in edges
-                        if from_node == node]
-            L += ['    %s' % to_node.name for to_node in to_nodes]
-        return '\n'.join(L)
+            L.append("%s:" % node)
+            to_nodes = [
+                to_node for from_node, to_node in edges if from_node == node
+            ]
+            L += ["    %s" % to_node.name for to_node in to_nodes]
+        return "\n".join(L)
 
     def getDOTGraphStatement(self):
         """Return a unicode string that is the DOT representation of this
@@ -1230,41 +1341,40 @@ class SpecGraph:
         stmt : node_stmt | edge_stmt | attr_stmt | ID '=' ID | subgraph
 
         """
-        graphname = 'deptree'
+        graphname = "deptree"
         graph_attrs = dict(
-            mode='hier',
+            mode="hier",
             # bgcolor='transparent',  # Fails with graphviz-cairo.
-            bgcolor='#ffffff',  # Same as Launchpad page background.
-            size='9.2,9',  # Width fits of 2 col layout, 1024x768.
-            ratio='compress',
+            bgcolor="#ffffff",  # Same as Launchpad page background.
+            size="9.2,9",  # Width fits of 2 col layout, 1024x768.
+            ratio="compress",
             ranksep=0.25,
-            nodesep=0.01  # Separation between nodes
-            )
+            nodesep=0.01,  # Separation between nodes
+        )
 
         # Global node and edge attributes.
         node_attrs = dict(
-            fillcolor='white',
-            style='filled',
-            fontname='Sans',
-            fontsize=11
-            )
-        edge_attrs = dict(arrowhead='normal')
+            fillcolor="white", style="filled", fontname="Sans", fontsize=11
+        )
+        edge_attrs = dict(arrowhead="normal")
 
         L = []
-        L.append('digraph %s {' % to_DOT_ID(graphname))
-        L.append('graph')
+        L.append("digraph %s {" % to_DOT_ID(graphname))
+        L.append("graph")
         L.append(dict_to_DOT_attrs(graph_attrs))
-        L.append('node')
+        L.append("node")
         L.append(dict_to_DOT_attrs(node_attrs))
-        L.append('edge')
+        L.append("edge")
         L.append(dict_to_DOT_attrs(edge_attrs))
         for node in self.getNodesSorted():
             L.append(node.getDOTNodeStatement())
         for from_node, to_node in self.getEdgesSorted():
-            L.append('%s -> %s' % (
-                to_DOT_ID(from_node.name), to_DOT_ID(to_node.name)))
-        L.append('}')
-        return '\n'.join(L)
+            L.append(
+                "%s -> %s"
+                % (to_DOT_ID(from_node.name), to_DOT_ID(to_node.name))
+            )
+        L.append("}")
+        return "\n".join(L)
 
 
 class SpecificationSprintAddView(LaunchpadFormView):
@@ -1276,7 +1386,7 @@ class SpecificationSprintAddView(LaunchpadFormView):
     # for_input to True here to ensure it's rendered as an input widget.
     for_input = True
 
-    @action(_('Continue'), name='continue')
+    @action(_("Continue"), name="continue")
     def continue_action(self, action, data):
         self.context.linkSprint(data["sprint"], self.user)
         self.next_url = canonical_url(self.context)
@@ -1293,18 +1403,18 @@ class SpecGraphNode:
     """
 
     def __init__(self, spec, root=False, url_pattern_for_testing=None):
-        self.name = '%s-%s' % (spec.target.name, spec.name)
+        self.name = "%s-%s" % (spec.target.name, spec.name)
         if url_pattern_for_testing:
             self.URL = url_pattern_for_testing % spec.name
         else:
             self.URL = canonical_url(spec)
         self.isRoot = root
         if self.isRoot:
-            self.color = 'red'
+            self.color = "red"
         elif spec.is_complete:
-            self.color = 'grey'
+            self.color = "grey"
         else:
-            self.color = 'black'
+            self.color = "black"
         self.comment = spec.title
         self.label = self.makeLabel(spec)
         self.tooltip = spec.title
@@ -1312,13 +1422,13 @@ class SpecGraphNode:
     def makeLabel(self, spec):
         """Return a label for the spec."""
         if spec.assignee:
-            label = '%s\n(%s)' % (spec.name, spec.assignee.name)
+            label = "%s\n(%s)" % (spec.name, spec.assignee.name)
         else:
             label = spec.name
         return label
 
     def __str__(self):
-        return '<%s>' % self.name
+        return "<%s>" % self.name
 
     def getDOTNodeStatement(self):
         """Return this node's data as a DOT unicode.
@@ -1336,16 +1446,16 @@ class SpecGraphNode:
         We don't care about the [ port ] part.
 
         """
-        attrnames = ['color', 'comment', 'label', 'tooltip']
+        attrnames = ["color", "comment", "label", "tooltip"]
         if not self.isRoot:
             # We want to have links in the image map for all nodes
             # except the one that were currently on the page of.
-            attrnames.append('URL')
+            attrnames.append("URL")
         attrdict = {name: getattr(self, name) for name in attrnames}
-        return '%s\n%s' % (to_DOT_ID(self.name), dict_to_DOT_attrs(attrdict))
+        return "%s\n%s" % (to_DOT_ID(self.name), dict_to_DOT_attrs(attrdict))
 
 
-def dict_to_DOT_attrs(some_dict, indent='    '):
+def dict_to_DOT_attrs(some_dict, indent="    "):
     r"""Convert some_dict to unicode DOT attrs output.
 
     attr_list : '[' [ a_list ] ']' [ attr_list ]
@@ -1354,16 +1464,16 @@ def dict_to_DOT_attrs(some_dict, indent='    '):
     The attributes are sorted by dict key.
     """
     if not some_dict:
-        return ''
+        return ""
     L = []
-    L.append('[')
+    L.append("[")
     for key, value in sorted(some_dict.items()):
-        L.append('%s=%s,' % (to_DOT_ID(key), to_DOT_ID(value)))
+        L.append("%s=%s," % (to_DOT_ID(key), to_DOT_ID(value)))
     # Remove the trailing comma from the last attr.
     lastitem = L.pop()
     L.append(lastitem[:-1])
-    L.append(']')
-    return '\n'.join('%s%s' % (indent, line) for line in L)
+    L.append("]")
+    return "\n".join("%s%s" % (indent, line) for line in L)
 
 
 def to_DOT_ID(value):
@@ -1376,11 +1486,11 @@ def to_DOT_ID(value):
 
     """
     if isinstance(value, bytes):
-        unitext = six.ensure_text(value, encoding='ascii')
+        unitext = six.ensure_text(value, encoding="ascii")
     else:
         unitext = str(value)
     output = unitext.replace('"', '\\"')
-    output = output.replace('\n', '\\n')
+    output = output.replace("\n", "\\n")
     return '"%s"' % output
 
 
@@ -1392,8 +1502,7 @@ class SpecificationTreeGraphView(LaunchpadView):
     """View for displaying the dependency tree as a PNG with image map."""
 
     def makeSpecGraph(self):
-        """Return a SpecGraph object rooted on the spec that is self.context.
-        """
+        """Return a SpecGraph object rooted on the self.context spec."""
         graph = SpecGraph(self.user)
         graph.newNode(self.context, root=True)
         graph.addDependencyNodes(self.context)
@@ -1411,17 +1520,22 @@ class SpecificationTreeGraphView(LaunchpadView):
         Shell out to `dot` to do the work.
         Raise ProblemRenderingGraph exception if `dot` gives any error output.
         """
-        assert format in ('png', 'cmapx')
-        input = self.getDotFileText().encode('UTF-8')
+        assert format in ("png", "cmapx")
+        input = self.getDotFileText().encode("UTF-8")
         # XXX sinzui 2008-04-03 bug=211568:
         # This use of subprocess.Popen is far from ideal. There is extra
         # risk of getting an OSError, or an command line issue that we
         # represent as a ProblemRenderingGraph. We need python bindings
         # to make the PNG/cmapx.
-        cmd = 'unflatten -l 2 | dot -T%s' % format
+        cmd = "unflatten -l 2 | dot -T%s" % format
         process = Popen(
-            cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE,
-            close_fds=True)
+            cmd,
+            shell=True,
+            stdin=PIPE,
+            stdout=PIPE,
+            stderr=PIPE,
+            close_fds=True,
+        )
         output, err = process.communicate(input)
         # XXX Abel Deuring 2012-12-06, bug 1087314
         # err may just contain a warning, while the image might be rendered
@@ -1444,14 +1558,19 @@ here = os.path.dirname(__file__)
 class SpecificationTreePNGView(SpecificationTreeGraphView):
 
     fail_over_image_path = os.path.join(
-        config.root, 'lib', 'canonical', 'launchpad', 'icing',
-        'blueprints-deptree-error.png')
+        config.root,
+        "lib",
+        "canonical",
+        "launchpad",
+        "icing",
+        "blueprints-deptree-error.png",
+    )
 
     def render(self):
         """Render a PNG displaying the specification dependency graph."""
         try:
-            image = self.renderGraphvizGraph('png')
-            self.request.response.setHeader('Content-type', 'image/png')
+            image = self.renderGraphvizGraph("png")
+            self.request.response.setHeader("Content-type", "image/png")
         except (ProblemRenderingGraph, OSError) as error:
             # The subprocess or command can raise errors that might not
             # occur if we used a Python bindings for GraphViz. Instead of
@@ -1459,7 +1578,7 @@ class SpecificationTreePNGView(SpecificationTreeGraphView):
             # that explains there was a problem.
             log_oops(error, self.request)
             try:
-                fail_over_image = open(self.fail_over_image_path, 'rb')
+                fail_over_image = open(self.fail_over_image_path, "rb")
                 image = fail_over_image.read()
             finally:
                 fail_over_image.close()
@@ -1467,11 +1586,10 @@ class SpecificationTreePNGView(SpecificationTreeGraphView):
 
 
 class SpecificationTreeImageTag(SpecificationTreeGraphView):
-
     def render(self):
         """Render the image and image map tags for this dependency graph."""
         try:
-            image_map = self.renderGraphvizGraph('cmapx').decode('UTF-8')
+            image_map = self.renderGraphvizGraph("cmapx").decode("UTF-8")
         except (ProblemRenderingGraph, OSError) as error:
             # The subprocess or command can raise errors that might not
             # occur if we used a Python bindings for GraphViz. Instead
@@ -1481,24 +1599,24 @@ class SpecificationTreeImageTag(SpecificationTreeGraphView):
             if isinstance(error, OSError):
                 # An OSError can be random. The image map may generate
                 # if the user reloads the page.
-                extra_help = ' Reload the page to link the image.'
+                extra_help = " Reload the page to link the image."
             else:
-                extra_help = ''
+                extra_help = ""
             image_map = (
                 '<p class="error message">'
-                'There was an error linking the dependency tree to its '
-                'specs.' + extra_help + '</p>')
-        return ('<img src="deptree.png" usemap="#deptree" />\n' + image_map)
+                "There was an error linking the dependency tree to its "
+                "specs." + extra_help + "</p>"
+            )
+        return '<img src="deptree.png" usemap="#deptree" />\n' + image_map
 
 
 class SpecificationTreeDotOutput(SpecificationTreeGraphView):
-
     def render(self):
         """Render the dep tree as a DOT file.
 
         This is useful for experimenting with the node layout offline.
         """
-        self.request.response.setHeader('Content-type', 'text/plain')
+        self.request.response.setHeader("Content-type", "text/plain")
         return self.getDotFileText()
 
 
@@ -1506,20 +1624,22 @@ class SpecificationLinkBranchView(LaunchpadFormView):
     """A form used to link a branch to this specification."""
 
     schema = ISpecificationBranch
-    field_names = ['branch']
-    label = _('Link branch to blueprint')
+    field_names = ["branch"]
+    label = _("Link branch to blueprint")
 
     def validate(self, data):
-        branch = data.get('branch')
+        branch = data.get("branch")
         if branch:
             branchlink = self.context.getBranchLink(branch)
             if branchlink is not None:
-                self.setFieldError('branch', 'This branch has already '
-                                   'been linked to the blueprint')
+                self.setFieldError(
+                    "branch",
+                    "This branch has already " "been linked to the blueprint",
+                )
 
-    @action(_('Continue'), name='continue')
+    @action(_("Continue"), name="continue")
     def continue_action(self, action, data):
-        self.context.linkBranch(branch=data['branch'], registrant=self.user)
+        self.context.linkBranch(branch=data["branch"], registrant=self.user)
 
     @property
     def next_url(self):
@@ -1531,41 +1651,44 @@ class SpecificationLinkBranchView(LaunchpadFormView):
 class SpecificationSetView(AppFrontPageSearchView, HasSpecificationsView):
     """View for the Blueprints index page."""
 
-    label = 'Blueprints'
+    label = "Blueprints"
 
     @property
     def latest_specifications(self):
         return self.context.specifications(
-            self.user, sort=SpecificationSort.DATE, quantity=5)
+            self.user, sort=SpecificationSort.DATE, quantity=5
+        )
 
     @property
     def latest_completed_specifications(self):
         return self.context.specifications(
-            self.user, sort=SpecificationSort.DATE, quantity=5,
-            filter=[SpecificationFilter.COMPLETE])
+            self.user,
+            sort=SpecificationSort.DATE,
+            quantity=5,
+            filter=[SpecificationFilter.COMPLETE],
+        )
 
     @property
     def specification_count(self):
         return self.context.specificationCount(self.user)
 
     @safe_action
-    @action('Find blueprints', name="search")
+    @action("Find blueprints", name="search")
     def search_action(self, action, data):
         """Redirect to the proper search page based on the scope widget."""
         # For the scope to be absent from the form, the user must
         # build the query string themselves - most likely because they
         # are a bot. In that case we just assume they want to search
         # all projects.
-        scope = self.widgets['scope'].getScope()
-        if scope is None or scope == 'all':
+        scope = self.widgets["scope"].getScope()
+        if scope is None or scope == "all":
             # Use 'All projects' scope.
-            url = '/'
+            url = "/"
         else:
-            url = canonical_url(
-                self.widgets['scope'].getInputValue())
-        search_text = data['search_text']
+            url = canonical_url(self.widgets["scope"].getInputValue())
+        search_text = data["search_text"]
         if search_text is not None:
-            url += '?searchtext=' + search_text
+            url += "?searchtext=" + search_text
         self.next_url = url
 
 
@@ -1573,13 +1696,15 @@ class SpecificationSetView(AppFrontPageSearchView, HasSpecificationsView):
 @implementer(IFieldHTMLRenderer)
 def starter_xhtml_representation(context, field, request):
     """Render the starter as XHTML to populate the page using AJAX."""
+
     def render(value=None):
         # The value is a webservice link to the object, we want field value.
         starter = context.starter
         if starter is None:
-            return ''
+            return ""
         date_formatter = DateTimeFormatterAPI(context.date_started)
         return "%s %s" % (format_link(starter), date_formatter.displaydate())
+
     return render
 
 
@@ -1587,11 +1712,13 @@ def starter_xhtml_representation(context, field, request):
 @implementer(IFieldHTMLRenderer)
 def completer_xhtml_representation(context, field, request):
     """Render the completer as XHTML to populate the page using AJAX."""
+
     def render(value=None):
         # The value is a webservice link to the object, we want field value.
         completer = context.completer
         if completer is None:
-            return ''
+            return ""
         date_formatter = DateTimeFormatterAPI(context.date_completed)
         return "%s %s" % (format_link(completer), date_formatter.displaydate())
+
     return render
diff --git a/lib/lp/blueprints/browser/specificationbranch.py b/lib/lp/blueprints/browser/specificationbranch.py
index 2c58b74..5b54cba 100644
--- a/lib/lp/blueprints/browser/specificationbranch.py
+++ b/lib/lp/blueprints/browser/specificationbranch.py
@@ -4,19 +4,19 @@
 """Specification views."""
 
 __all__ = [
-    'BranchLinkToSpecificationView',
-    'SpecificationBranchStatusView',
-    'SpecificationBranchURL',
-    ]
+    "BranchLinkToSpecificationView",
+    "SpecificationBranchStatusView",
+    "SpecificationBranchURL",
+]
 
 from zope.interface import implementer
 
 from lp import _
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
-    )
+    action,
+)
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
 from lp.services.webapp import canonical_url
 from lp.services.webapp.interfaces import ICanonicalUrlData
@@ -38,7 +38,7 @@ class SpecificationBranchURL:
 
     @property
     def path(self):
-        return '+branch/%s' % self.branch.unique_name[1:]
+        return "+branch/%s" % self.branch.unique_name[1:]
 
 
 class SpecificationBranchStatusView(LaunchpadEditFormView):
@@ -46,7 +46,7 @@ class SpecificationBranchStatusView(LaunchpadEditFormView):
 
     schema = ISpecificationBranch
     field_names = []
-    label = _('Delete link between specification and branch')
+    label = _("Delete link between specification and branch")
 
     def initialize(self):
         self.specification = self.context.specification
@@ -56,7 +56,7 @@ class SpecificationBranchStatusView(LaunchpadEditFormView):
     def next_url(self):
         return canonical_url(self.specification)
 
-    @action(_('Delete'), name='delete')
+    @action(_("Delete"), name="delete")
     def delete_action(self, action, data):
         self.context.destroySelf()
 
@@ -70,7 +70,7 @@ class BranchLinkToSpecificationView(LaunchpadFormView):
     # to get the read only fields rendered as input widgets.
     for_input = True
 
-    field_names = ['specification']
+    field_names = ["specification"]
 
     @property
     def label(self):
@@ -78,7 +78,7 @@ class BranchLinkToSpecificationView(LaunchpadFormView):
 
     @property
     def page_title(self):
-        return 'Link branch %s to a blueprint' % self.context.displayname
+        return "Link branch %s to a blueprint" % self.context.displayname
 
     @property
     def next_url(self):
@@ -86,7 +86,7 @@ class BranchLinkToSpecificationView(LaunchpadFormView):
 
     cancel_url = next_url
 
-    @action(_('Continue'), name='continue')
+    @action(_("Continue"), name="continue")
     def continue_action(self, action, data):
-        spec = data['specification']
+        spec = data["specification"]
         spec.linkBranch(branch=self.context, registrant=self.user)
diff --git a/lib/lp/blueprints/browser/specificationdependency.py b/lib/lp/blueprints/browser/specificationdependency.py
index d31d7a2..cdf6ed7 100644
--- a/lib/lp/blueprints/browser/specificationdependency.py
+++ b/lib/lp/blueprints/browser/specificationdependency.py
@@ -4,34 +4,28 @@
 """Views for SpecificationDependency."""
 
 __all__ = [
-    'SpecificationDependencyAddView',
-    'SpecificationDependencyRemoveView',
-    'SpecificationDependencyTreeView',
-    ]
+    "SpecificationDependencyAddView",
+    "SpecificationDependencyRemoveView",
+    "SpecificationDependencyTreeView",
+]
 
 from lazr.restful.interface import copy_field
 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.blueprints.interfaces.specificationdependency import (
     ISpecificationDependency,
     ISpecificationDependencyRemoval,
-    )
+)
 from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
-    canonical_url,
-    LaunchpadView,
-    )
+from lp.services.webapp import LaunchpadView, canonical_url
 
 
 class AddSpecificationDependencySchema(Interface):
 
     dependency = copy_field(
-        ISpecificationDependency['dependency'],
+        ISpecificationDependency["dependency"],
         readonly=False,
         description=_(
             "If another blueprint needs to be fully implemented "
@@ -39,12 +33,14 @@ class AddSpecificationDependencySchema(Interface):
             "dependency here so Launchpad knows about it and can "
             "give you an accurate project plan.  You can enter the "
             "name of a blueprint that has the same target, or the "
-            "URL of any blueprint."))
+            "URL of any blueprint."
+        ),
+    )
 
 
 class SpecificationDependencyAddView(LaunchpadFormView):
     schema = AddSpecificationDependencySchema
-    label = _('Depends On')
+    label = _("Depends On")
 
     def validate(self, data):
         """See `LaunchpadFormView.validate`.
@@ -53,17 +49,18 @@ class SpecificationDependencyAddView(LaunchpadFormView):
         widget -- it will be the infamously inscrutable 'Invalid Value' -- we
         replace it here.
         """
-        if self.getFieldError('dependency'):
-            token = self.request.form.get(self.widgets['dependency'].name)
+        if self.getFieldError("dependency"):
+            token = self.request.form.get(self.widgets["dependency"].name)
             self.setFieldError(
-                'dependency',
+                "dependency",
                 'There is no blueprint named "%s" in %s, or '
-                '%s isn\'t valid dependency of that blueprint.' %
-                (token, self.context.target.name, self.context.name))
+                "%s isn't valid dependency of that blueprint."
+                % (token, self.context.target.name, self.context.name),
+            )
 
-    @action(_('Continue'), name='linkdependency')
+    @action(_("Continue"), name="linkdependency")
     def linkdependency_action(self, action, data):
-        self.context.createDependency(data['dependency'])
+        self.context.createDependency(data["dependency"])
 
     @property
     def next_url(self):
@@ -76,13 +73,13 @@ class SpecificationDependencyAddView(LaunchpadFormView):
 
 class SpecificationDependencyRemoveView(LaunchpadFormView):
     schema = ISpecificationDependencyRemoval
-    label = 'Remove a dependency'
-    field_names = ['dependency']
+    label = "Remove a dependency"
+    field_names = ["dependency"]
     for_input = True
 
-    @action('Continue', name='continue')
+    @action("Continue", name="continue")
     def continue_action(self, action, data):
-        self.context.removeDependency(data['dependency'])
+        self.context.removeDependency(data["dependency"])
         self.next_url = canonical_url(self.context)
 
     @property
diff --git a/lib/lp/blueprints/browser/specificationgoal.py b/lib/lp/blueprints/browser/specificationgoal.py
index 911e0f1..32f0d0d 100644
--- a/lib/lp/blueprints/browser/specificationgoal.py
+++ b/lib/lp/blueprints/browser/specificationgoal.py
@@ -4,18 +4,15 @@
 """Views for Specification Goal Setting."""
 
 __all__ = [
-    'GoalDecideView',
-    ]
+    "GoalDecideView",
+]
 
 from zope.component import getUtility
 
 from lp.blueprints.browser.specificationtarget import HasSpecificationsView
 from lp.blueprints.enums import SpecificationFilter
 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.interfaces import ILaunchBag
 
 
@@ -53,45 +50,46 @@ class GoalDecideView(HasSpecificationsView, LaunchpadView):
         user = getUtility(ILaunchBag).user
         count = self.specs.count()
 
-        if 'SUBMIT_CANCEL' in form:
-            self.status_message = 'Cancelled'
+        if "SUBMIT_CANCEL" in form:
+            self.status_message = "Cancelled"
             self.request.response.redirect(canonical_url(self.context))
             return self.status_message
 
-        if 'SUBMIT_ACCEPT' not in form and 'SUBMIT_DECLINE' not in form:
-            self.status_message = ''
+        if "SUBMIT_ACCEPT" not in form and "SUBMIT_DECLINE" not in form:
+            self.status_message = ""
             return self.status_message
 
-        if self.request.method == 'POST':
-            if 'specification' not in form:
+        if self.request.method == "POST":
+            if "specification" not in form:
                 self.status_message = (
-                    'Please select specifications to accept or decline.')
+                    "Please select specifications to accept or decline."
+                )
                 return self.status_message
             # determine if we are accepting or declining
-            if 'SUBMIT_ACCEPT' in form:
-                assert 'SUBMIT_DECLINE' not in form
-                action = 'Accepted'
+            if "SUBMIT_ACCEPT" in form:
+                assert "SUBMIT_DECLINE" not in form
+                action = "Accepted"
             else:
-                assert 'SUBMIT_DECLINE' in form
-                action = 'Declined'
+                assert "SUBMIT_DECLINE" in form
+                action = "Declined"
 
-        selected_specs = form['specification']
+        selected_specs = form["specification"]
         if isinstance(selected_specs, str):
             # only a single item was selected, but we want to deal with a
             # list for the general case, so convert it to a list
             selected_specs = [selected_specs]
 
-        specs = [self.context.getSpecification(name)
-                 for name in selected_specs]
+        specs = [
+            self.context.getSpecification(name) for name in selected_specs
+        ]
         for spec in specs:
-            if action == 'Accepted':
+            if action == "Accepted":
                 spec.acceptBy(user)
             else:
                 spec.declineBy(user)
 
         # For example: "Accepted 26 specification(s)."
-        self.status_message = '%s %d specification(s).' % (
-            action, len(specs))
+        self.status_message = "%s %d specification(s)." % (action, len(specs))
         self.request.response.addNotification(self.status_message)
 
         if len(specs) >= count:
diff --git a/lib/lp/blueprints/browser/specificationsubscription.py b/lib/lp/blueprints/browser/specificationsubscription.py
index 680e585..79598e8 100644
--- a/lib/lp/blueprints/browser/specificationsubscription.py
+++ b/lib/lp/blueprints/browser/specificationsubscription.py
@@ -4,10 +4,10 @@
 """Views for SpecificationSubscription."""
 
 __all__ = [
-    'SpecificationSubscriptionAddView',
-    'SpecificationSubscriptionAddSubscriberView',
-    'SpecificationSubscriptionEditView',
-    ]
+    "SpecificationSubscriptionAddView",
+    "SpecificationSubscriptionAddSubscriberView",
+    "SpecificationSubscriptionEditView",
+]
 
 from lazr.delegates import delegate_to
 from simplejson import dumps
@@ -15,13 +15,13 @@ from zope.component import getUtility
 
 from lp import _
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
-    )
+    action,
+)
 from lp.blueprints.interfaces.specificationsubscription import (
     ISpecificationSubscription,
-    )
+)
 from lp.services.propertycache import cachedproperty
 from lp.services.webapp import canonical_url
 from lp.services.webapp.authorization import precache_permission_for_objects
@@ -33,8 +33,8 @@ class SpecificationSubscriptionAddView(LaunchpadFormView):
     """Used to subscribe the current user to a blueprint."""
 
     schema = ISpecificationSubscription
-    field_names = ['essential']
-    label = 'Subscribe to blueprint'
+    field_names = ["essential"]
+    label = "Subscribe to blueprint"
 
     @property
     def cancel_url(self):
@@ -45,27 +45,30 @@ class SpecificationSubscriptionAddView(LaunchpadFormView):
     def _subscribe(self, person, essential):
         self.context.subscribe(person, self.user, essential)
 
-    @action(_('Subscribe'), name='subscribe')
+    @action(_("Subscribe"), name="subscribe")
     def subscribe_action(self, action, data):
-        self._subscribe(self.user, data['essential'])
+        self._subscribe(self.user, data["essential"])
         self.request.response.addInfoNotification(
-            "You have subscribed to this blueprint.")
+            "You have subscribed to this blueprint."
+        )
 
 
 class SpecificationSubscriptionAddSubscriberView(
-    SpecificationSubscriptionAddView):
+    SpecificationSubscriptionAddView
+):
     """Used to subscribe someone else to a blueprint."""
 
-    field_names = ['person', 'essential']
-    label = 'Subscribe someone else'
+    field_names = ["person", "essential"]
+    label = "Subscribe someone else"
     for_input = True
 
-    @action(_('Subscribe'), name='subscribe')
+    @action(_("Subscribe"), name="subscribe")
     def subscribe_action(self, action, data):
-        person = data['person']
-        self._subscribe(person, data['essential'])
+        person = data["person"]
+        self._subscribe(person, data["essential"])
         self.request.response.addInfoNotification(
-            "%s has been subscribed to this blueprint." % person.displayname)
+            "%s has been subscribed to this blueprint." % person.displayname
+        )
 
 
 class SpecificationSubscriptionDeleteView(LaunchpadFormView):
@@ -76,9 +79,10 @@ class SpecificationSubscriptionDeleteView(LaunchpadFormView):
 
     @property
     def label(self):
-        return ("Unsubscribe %s from %s"
-                    % (self.context.person.displayname,
-                       self.context.specification.title))
+        return "Unsubscribe %s from %s" % (
+            self.context.person.displayname,
+            self.context.specification.title,
+        )
 
     page_title = label
 
@@ -88,22 +92,24 @@ class SpecificationSubscriptionDeleteView(LaunchpadFormView):
 
     next_url = cancel_url
 
-    @action('Unsubscribe', name='unsubscribe')
+    @action("Unsubscribe", name="unsubscribe")
     def unsubscribe_action(self, action, data):
         self.context.specification.unsubscribe(self.context.person, self.user)
         if self.context.person == self.user:
             self.request.response.addInfoNotification(
-                "You have unsubscribed from this blueprint.")
+                "You have unsubscribed from this blueprint."
+            )
         else:
             self.request.response.addInfoNotification(
                 "%s has been unsubscribed from this blueprint."
-                % self.context.person.displayname)
+                % self.context.person.displayname
+            )
 
 
 class SpecificationSubscriptionEditView(LaunchpadEditFormView):
 
     schema = ISpecificationSubscription
-    field_names = ['essential']
+    field_names = ["essential"]
 
     @property
     def label(self):
@@ -115,17 +121,19 @@ class SpecificationSubscriptionEditView(LaunchpadEditFormView):
 
     next_url = cancel_url
 
-    @action(_('Change'), name='change')
+    @action(_("Change"), name="change")
     def change_action(self, action, data):
         self.updateContextFromData(data)
         is_current_user_subscription = self.user == self.context.person
         if is_current_user_subscription:
             self.request.response.addInfoNotification(
-                "Your subscription has been updated.")
+                "Your subscription has been updated."
+            )
         else:
             self.request.response.addInfoNotification(
                 "The subscription for %s has been updated."
-                % self.context.person.displayname)
+                % self.context.person.displayname
+            )
 
 
 class SpecificationPortletSubcribersContents(LaunchpadView):
@@ -158,7 +166,8 @@ class SpecificationPortletSubcribersContents(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', subscribers)
+            self.request, "launchpad.LimitedView", subscribers
+        )
 
         sorted_subscriptions = can_unsubscribe + cannot_unsubscribe
         return sorted_subscriptions
@@ -167,9 +176,9 @@ class SpecificationPortletSubcribersContents(LaunchpadView):
     def current_user_subscription_class(self):
         is_subscribed = self.context.isSubscribed(self.user)
         if is_subscribed:
-            return 'subscribed-true'
+            return "subscribed-true"
         else:
-            return 'subscribed-false'
+            return "subscribed-false"
 
 
 class SpecificationPortletSubcribersIds(LaunchpadView):
@@ -188,7 +197,7 @@ class SpecificationPortletSubcribersIds(LaunchpadView):
 
         ids = {}
         for sub in subscribers:
-            ids[sub.name] = 'subscriber-%s' % sub.id
+            ids[sub.name] = "subscriber-%s" % sub.id
         return ids
 
     @property
@@ -198,11 +207,11 @@ class SpecificationPortletSubcribersIds(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_ids_js
 
 
-@delegate_to(ISpecificationSubscription, context='subscription')
+@delegate_to(ISpecificationSubscription, context="subscription")
 class SubscriptionAttrDecorator:
     """A SpecificationSubscription with added attributes for HTML/JS."""
 
@@ -211,4 +220,4 @@ class SubscriptionAttrDecorator:
 
     @property
     def css_name(self):
-        return 'subscriber-%s' % self.subscription.person.id
+        return "subscriber-%s" % self.subscription.person.id
diff --git a/lib/lp/blueprints/browser/specificationtarget.py b/lib/lp/blueprints/browser/specificationtarget.py
index e6b4e29..3d8a19f 100644
--- a/lib/lp/blueprints/browser/specificationtarget.py
+++ b/lib/lp/blueprints/browser/specificationtarget.py
@@ -4,32 +4,23 @@
 """ISpecificationTarget browser views."""
 
 __all__ = [
-    'HasSpecificationsMenuMixin',
-    'HasSpecificationsView',
-    'RegisterABlueprintButtonPortlet',
-    'SpecificationAssignmentsView',
-    'SpecificationDocumentationView',
-    ]
+    "HasSpecificationsMenuMixin",
+    "HasSpecificationsView",
+    "RegisterABlueprintButtonPortlet",
+    "SpecificationAssignmentsView",
+    "SpecificationDocumentationView",
+]
 
 from operator import itemgetter
 
 from lazr.restful.utils import smartquote
 from zope.browserpage import ViewPageTemplateFile
-from zope.component import (
-    getMultiAdapter,
-    queryMultiAdapter,
-    )
+from zope.component import getMultiAdapter, queryMultiAdapter
 
 from lp import _
 from lp.app.enums import service_uses_launchpad
-from lp.app.interfaces.launchpad import (
-    IPrivacy,
-    IServiceUsage,
-    )
-from lp.blueprints.enums import (
-    SpecificationFilter,
-    SpecificationSort,
-    )
+from lp.app.interfaces.launchpad import IPrivacy, IServiceUsage
+from lp.blueprints.enums import SpecificationFilter, SpecificationSort
 from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
 from lp.blueprints.interfaces.sprint import ISprint
 from lp.registry.interfaces.distribution import IDistribution
@@ -40,69 +31,62 @@ from lp.registry.interfaces.productseries import IProductSeries
 from lp.registry.interfaces.projectgroup import (
     IProjectGroup,
     IProjectGroupSeries,
-    )
+)
 from lp.registry.interfaces.role import IHasDrivers
 from lp.services.config import config
 from lp.services.helpers import shortlist
 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
 from lp.services.webapp.batching import BatchNavigator
-from lp.services.webapp.menu import (
-    enabled_with_permission,
-    Link,
-    )
+from lp.services.webapp.menu import Link, enabled_with_permission
 
 
 class HasSpecificationsMenuMixin:
-
     def listall(self):
         """Return a link to show all blueprints."""
-        text = 'List all blueprints'
-        return Link('+specs?show=all', text, icon='blueprint')
+        text = "List all blueprints"
+        return Link("+specs?show=all", text, icon="blueprint")
 
     def listaccepted(self):
         """Return a link to show the approved goals."""
-        text = 'List approved blueprints'
-        return Link('+specs?acceptance=accepted', text, icon='blueprint')
+        text = "List approved blueprints"
+        return Link("+specs?acceptance=accepted", text, icon="blueprint")
 
     def listproposed(self):
         """Return a link to show the proposed goals."""
-        text = 'List proposed blueprints'
-        return Link('+specs?acceptance=proposed', text, icon='blueprint')
+        text = "List proposed blueprints"
+        return Link("+specs?acceptance=proposed", text, icon="blueprint")
 
     def listdeclined(self):
         """Return a link to show the declined goals."""
-        text = 'List declined blueprints'
-        return Link('+specs?acceptance=declined', text, icon='blueprint')
+        text = "List declined blueprints"
+        return Link("+specs?acceptance=declined", text, icon="blueprint")
 
     def doc(self):
-        text = 'List documentation'
-        return Link('+documentation', text, icon='info')
+        text = "List documentation"
+        return Link("+documentation", text, icon="info")
 
     def setgoals(self):
         """Return a link to set the series goals."""
-        text = 'Set series goals'
-        return Link('+setgoals', text, icon='edit')
+        text = "Set series goals"
+        return Link("+setgoals", text, icon="edit")
 
     def assignments(self):
         """Return a link to show the people assigned to the blueprint."""
-        text = 'Assignments'
-        return Link('+assignments', text, icon='person')
+        text = "Assignments"
+        return Link("+assignments", text, icon="person")
 
     def new(self):
         """Return a link to register a blueprint."""
-        text = 'Register a blueprint'
-        return Link('+addspec', text, icon='add')
+        text = "Register a blueprint"
+        return Link("+addspec", text, icon="add")
 
-    @enabled_with_permission('launchpad.View')
+    @enabled_with_permission("launchpad.View")
     def register_sprint(self):
-        text = 'Register a meeting'
-        summary = 'Register a developer sprint, summit, or gathering'
-        return Link('/sprints/+new', text, summary=summary, icon='add')
+        text = "Register a meeting"
+        summary = "Register a developer sprint, summit, or gathering"
+        return Link("/sprints/+new", text, summary=summary, icon="add")
 
 
 class HasSpecificationsView(LaunchpadView):
@@ -147,9 +131,11 @@ class HasSpecificationsView(LaunchpadView):
     # * Disabled
     # * Unknown
     default_template = ViewPageTemplateFile(
-        '../templates/hasspecifications-specs.pt')
+        "../templates/hasspecifications-specs.pt"
+    )
     not_launchpad_template = ViewPageTemplateFile(
-        '../templates/unknown-specs.pt')
+        "../templates/unknown-specs.pt"
+    )
 
     @property
     def template(self):
@@ -158,23 +144,25 @@ class HasSpecificationsView(LaunchpadView):
         # zope.browserpage.simpleviewclass.simple class that is magically
         # mixed in by the browser:page zcml directive the template defined in
         # the directive should be used.
-        if hasattr(self, 'index'):
+        if hasattr(self, "index"):
             return super().template
 
         # Sprints and Persons don't have a usage enum for blueprints, so we
         # have to fallback to the default.
-        if (ISprint.providedBy(self.context)
-            or IPerson.providedBy(self.context)):
+        if ISprint.providedBy(self.context) or IPerson.providedBy(
+            self.context
+        ):
             return self.default_template
 
         # ProjectGroups are a special case, as their products may be a
         # combination of usage settings. To deal with this, check all
         # products via the involvment menu.
-        if (IProjectGroup.providedBy(self.context)
-            or IProjectGroupSeries.providedBy(self.context)):
+        if IProjectGroup.providedBy(
+            self.context
+        ) or IProjectGroupSeries.providedBy(self.context):
             involvement = getMultiAdapter(
-                (self.context, self.request),
-                name='+get-involved')
+                (self.context, self.request), name="+get-involved"
+            )
             if service_uses_launchpad(involvement.blueprints_usage):
                 return self.default_template
             else:
@@ -214,42 +202,42 @@ class HasSpecificationsView(LaunchpadView):
             self.show_milestone = True
             self.show_target = True
             self.show_series = True
-        elif (IProductSeries.providedBy(self.context) or
-              IDistroSeries.providedBy(self.context)):
+        elif IProductSeries.providedBy(
+            self.context
+        ) or IDistroSeries.providedBy(self.context):
             self.is_series = True
             self.show_milestone = True
         elif ISprint.providedBy(self.context):
             self.is_sprint = True
             self.show_target = True
         else:
-            raise AssertionError('Unknown blueprint listing site.')
+            raise AssertionError("Unknown blueprint listing site.")
 
         if IHasDrivers.providedBy(self.context):
             self.has_drivers = True
 
         self.batchnav = BatchNavigator(
-            self.specs, self.request,
-            size=config.launchpad.default_batch_size)
+            self.specs, self.request, size=config.launchpad.default_batch_size
+        )
 
     @property
     def can_configure_blueprints(self):
-        """Can the user configure blueprints for the `ISpecificationTarget`.
-        """
+        """Can the user configure blueprints for the `ISpecificationTarget`."""
         target = self.context
         if IProduct.providedBy(target) or IDistribution.providedBy(target):
-            return check_permission('launchpad.Edit', self.context)
+            return check_permission("launchpad.Edit", self.context)
         else:
             return False
 
     @property
     def label(self):
-        mapping = {'name': self.context.displayname}
+        mapping = {"name": self.context.displayname}
         if self.is_person:
-            return _('Blueprints involving $name', mapping=mapping)
+            return _("Blueprints involving $name", mapping=mapping)
         else:
-            return _('Blueprints for $name', mapping=mapping)
+            return _("Blueprints for $name", mapping=mapping)
 
-    page_title = 'Blueprints'
+    page_title = "Blueprints"
 
     @cachedproperty
     def has_any_specifications(self):
@@ -265,9 +253,9 @@ class HasSpecificationsView(LaunchpadView):
 
     @cachedproperty
     def searchtext(self):
-        st = self.request.form.get('searchtext')
+        st = self.request.form.get("searchtext")
         if st is None:
-            st = self.request.form.get('field.searchtext')
+            st = self.request.form.get("field.searchtext")
         return st
 
     @cachedproperty
@@ -288,23 +276,23 @@ class HasSpecificationsView(LaunchpadView):
         This method will interpret the show= part based on the kind of
         object that is the context of this request.
         """
-        show = self.request.form.get('show')
-        acceptance = self.request.form.get('acceptance')
-        role = self.request.form.get('role')
-        informational = self.request.form.get('informational', False)
+        show = self.request.form.get("show")
+        acceptance = self.request.form.get("acceptance")
+        role = self.request.form.get("role")
+        informational = self.request.form.get("informational", False)
 
         filter = []
 
         # include text for filtering if it was given
         if self.searchtext is not None and len(self.searchtext) > 0:
-            filter.append(self.searchtext.replace('%', '%%'))
+            filter.append(self.searchtext.replace("%", "%%"))
 
         # filter on completeness
-        if show == 'all':
+        if show == "all":
             filter.append(SpecificationFilter.ALL)
-        elif show == 'complete':
+        elif show == "complete":
             filter.append(SpecificationFilter.COMPLETE)
-        elif show == 'incomplete':
+        elif show == "incomplete":
             filter.append(SpecificationFilter.INCOMPLETE)
 
         # filter for informational status
@@ -314,39 +302,41 @@ class HasSpecificationsView(LaunchpadView):
         # filter on relationship or role. the underlying class will give us
         # the aggregate of everything if we don't explicitly select one or
         # more
-        if role == 'registrant':
+        if role == "registrant":
             filter.append(SpecificationFilter.CREATOR)
-        elif role == 'assignee':
+        elif role == "assignee":
             filter.append(SpecificationFilter.ASSIGNEE)
-        elif role == 'drafter':
+        elif role == "drafter":
             filter.append(SpecificationFilter.DRAFTER)
-        elif role == 'approver':
+        elif role == "approver":
             filter.append(SpecificationFilter.APPROVER)
-        elif role == 'subscriber':
+        elif role == "subscriber":
             filter.append(SpecificationFilter.SUBSCRIBER)
 
         # filter for acceptance state
-        if acceptance == 'declined':
+        if acceptance == "declined":
             filter.append(SpecificationFilter.DECLINED)
-        elif show == 'proposed':
+        elif show == "proposed":
             filter.append(SpecificationFilter.PROPOSED)
-        elif show == 'accepted':
+        elif show == "accepted":
             filter.append(SpecificationFilter.ACCEPTED)
 
         return filter
 
     @property
     def specs(self):
-        if (IPrivacy.providedBy(self.context)
-                and self.context.private
-                and not check_permission('launchpad.View', self.context)):
+        if (
+            IPrivacy.providedBy(self.context)
+            and self.context.private
+            and not check_permission("launchpad.View", self.context)
+        ):
             return []
         return self.context.specifications(self.user, filter=self.spec_filter)
 
     @cachedproperty
     def specs_batched(self):
         navigator = BatchNavigator(self.specs, self.request, size=500)
-        navigator.setHeadings('specification', 'specifications')
+        navigator.setHeadings("specification", "specifications")
         return navigator
 
     @cachedproperty
@@ -355,8 +345,10 @@ class HasSpecificationsView(LaunchpadView):
 
     @cachedproperty
     def documentation(self):
-        filter = [SpecificationFilter.COMPLETE,
-                  SpecificationFilter.INFORMATIONAL]
+        filter = [
+            SpecificationFilter.COMPLETE,
+            SpecificationFilter.INFORMATIONAL,
+        ]
         return shortlist(self.context.specifications(self.user, filter=filter))
 
     @cachedproperty
@@ -385,12 +377,12 @@ class HasSpecificationsView(LaunchpadView):
                 category = categories[spec.definition_status]
             else:
                 category = {}
-                category['status'] = spec.definition_status
-                category['specs'] = []
+                category["status"] = spec.definition_status
+                category["specs"] = []
                 categories[spec.definition_status] = category
-            category['specs'].append(spec)
+            category["specs"].append(spec)
         categories = categories.values()
-        return sorted(categories, key=itemgetter('definition_status'))
+        return sorted(categories, key=itemgetter("definition_status"))
 
     def getLatestSpecifications(self, quantity=5):
         """Return <quantity> latest specs created for this target.
@@ -398,29 +390,38 @@ class HasSpecificationsView(LaunchpadView):
         Only ACCEPTED specifications are returned.  This list is used by the
         +portlet-latestspecs view.
         """
-        return self.context.specifications(self.user,
-            sort=SpecificationSort.DATE, quantity=quantity,
-            need_people=False, need_branches=False, need_workitems=False)
+        return self.context.specifications(
+            self.user,
+            sort=SpecificationSort.DATE,
+            quantity=quantity,
+            need_people=False,
+            need_branches=False,
+            need_workitems=False,
+        )
 
 
 class SpecificationAssignmentsView(HasSpecificationsView):
     """View for +assignments pages."""
+
     page_title = "Assignments"
 
     @property
     def label(self):
         return smartquote(
-            'Blueprint assignments for "%s"' % self.context.displayname)
+            'Blueprint assignments for "%s"' % self.context.displayname
+        )
 
 
 class SpecificationDocumentationView(HasSpecificationsView):
     """View for blueprints +documentation page."""
+
     page_title = "Documentation"
 
     @property
     def label(self):
-        return smartquote('Current documentation for "%s"' %
-                          self.context.displayname)
+        return smartquote(
+            'Current documentation for "%s"' % self.context.displayname
+        )
 
 
 class RegisterABlueprintButtonPortlet:
@@ -430,8 +431,7 @@ class RegisterABlueprintButtonPortlet:
     def target_url(self):
         """The +addspec URL for the specifiation target or None"""
         # Check if the context has an +addspec view available.
-        if queryMultiAdapter(
-            (self.context, self.request), name='+addspec'):
+        if queryMultiAdapter((self.context, self.request), name="+addspec"):
             target = self.context
         else:
             # otherwise find an adapter to ISpecificationTarget which will.
@@ -440,12 +440,14 @@ class RegisterABlueprintButtonPortlet:
             return None
         else:
             return canonical_url(
-                target, rootsite='blueprints', view_name='+addspec')
+                target, rootsite="blueprints", view_name="+addspec"
+            )
 
     def __call__(self):
         if self.target_url is None:
-            return ''
-        return """
+            return ""
+        return (
+            """
             <div id="involvement" class="portlet involvement">
               <ul>
                 <li class="first">
@@ -454,4 +456,6 @@ class RegisterABlueprintButtonPortlet:
                 </li>
               </ul>
             </div>
-            """ % self.target_url
+            """
+            % self.target_url
+        )
diff --git a/lib/lp/blueprints/browser/sprint.py b/lib/lp/blueprints/browser/sprint.py
index 796306b..4a2be23 100644
--- a/lib/lp/blueprints/browser/sprint.py
+++ b/lib/lp/blueprints/browser/sprint.py
@@ -4,30 +4,30 @@
 """Sprint views."""
 
 __all__ = [
-    'HasSprintsView',
-    'SprintAddView',
-    'SprintAttendeesCsvExportView',
-    'SprintBrandingView',
-    'SprintDeleteView',
-    'SprintEditView',
-    'SprintFacets',
-    'SprintMeetingExportView',
-    'SprintNavigation',
-    'SprintOverviewMenu',
-    'SprintSetBreadcrumb',
-    'SprintSetNavigation',
-    'SprintSetView',
-    'SprintSpecificationsMenu',
-    'SprintTopicSetView',
-    'SprintView',
-    ]
+    "HasSprintsView",
+    "SprintAddView",
+    "SprintAttendeesCsvExportView",
+    "SprintBrandingView",
+    "SprintDeleteView",
+    "SprintEditView",
+    "SprintFacets",
+    "SprintMeetingExportView",
+    "SprintNavigation",
+    "SprintOverviewMenu",
+    "SprintSetBreadcrumb",
+    "SprintSetNavigation",
+    "SprintSetView",
+    "SprintSpecificationsMenu",
+    "SprintTopicSetView",
+    "SprintView",
+]
 
-from collections import defaultdict
 import csv
 import io
+from collections import defaultdict
 
-from lazr.restful.utils import smartquote
 import pytz
+from lazr.restful.utils import smartquote
 from zope.component import getUtility
 from zope.formlib.widget import CustomWidgetFactory
 from zope.formlib.widgets import TextAreaWidget
@@ -35,55 +35,46 @@ from zope.interface import implementer
 
 from lp import _
 from lp.app.browser.launchpadform import (
-    action,
     LaunchpadEditFormView,
     LaunchpadFormView,
-    )
-from lp.app.interfaces.headings import (
-    IHeadingBreadcrumb,
-    IMajorHeadingView,
-    )
+    action,
+)
+from lp.app.interfaces.headings import IHeadingBreadcrumb, IMajorHeadingView
 from lp.app.widgets.date import DateTimeWidget
 from lp.blueprints.browser.specificationtarget import (
     HasSpecificationsMenuMixin,
     HasSpecificationsView,
-    )
+)
 from lp.blueprints.enums import (
     SpecificationFilter,
     SpecificationPriority,
     SpecificationSort,
-    )
-from lp.blueprints.interfaces.sprint import (
-    ISprint,
-    ISprintSet,
-    )
+)
+from lp.blueprints.interfaces.sprint import ISprint, ISprintSet
 from lp.blueprints.model.specificationsubscription import (
     SpecificationSubscription,
-    )
+)
 from lp.registry.browser.branding import BrandingChangeView
 from lp.registry.browser.menu import (
     IRegistryCollectionNavigationMenu,
     RegistryCollectionActionMenuBase,
-    )
+)
 from lp.registry.interfaces.person import IPersonSet
 from lp.services.database.bulk import load_referencing
 from lp.services.helpers import shortlist
 from lp.services.propertycache import cachedproperty
 from lp.services.webapp import (
-    canonical_url,
-    enabled_with_permission,
     GetitemNavigation,
     LaunchpadView,
     Link,
     Navigation,
     NavigationMenu,
     StandardLaunchpadFacets,
-    )
+    canonical_url,
+    enabled_with_permission,
+)
 from lp.services.webapp.batching import BatchNavigator
-from lp.services.webapp.breadcrumb import (
-    Breadcrumb,
-    TitleBreadcrumb,
-    )
+from lp.services.webapp.breadcrumb import Breadcrumb, TitleBreadcrumb
 from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
 
 
@@ -92,9 +83,9 @@ class SprintFacets(StandardLaunchpadFacets):
 
     usedfor = ISprint
     enable_only = [
-        'overview',
-        'specifications',
-        ]
+        "overview",
+        "specifications",
+    ]
 
 
 class SprintNavigation(Navigation):
@@ -111,53 +102,58 @@ class SprintOverviewMenu(NavigationMenu):
     """Defines a menu used for the global actions."""
 
     usedfor = ISprint
-    facet = 'overview'
-    links = ['attendance', 'registration', 'attendee_export', 'edit',
-             'branding', 'delete']
+    facet = "overview"
+    links = [
+        "attendance",
+        "registration",
+        "attendee_export",
+        "edit",
+        "branding",
+        "delete",
+    ]
 
     def attendance(self):
-        text = 'Register yourself'
-        summary = 'Register as an attendee of the meeting'
-        return Link('+attend', text, summary, icon='add')
+        text = "Register yourself"
+        summary = "Register as an attendee of the meeting"
+        return Link("+attend", text, summary, icon="add")
 
     def registration(self):
-        text = 'Register someone else'
-        summary = 'Register someone else to attend the meeting'
-        return Link('+register', text, summary, icon='add')
+        text = "Register someone else"
+        summary = "Register someone else to attend the meeting"
+        return Link("+register", text, summary, icon="add")
 
-    @enabled_with_permission('launchpad.View')
+    @enabled_with_permission("launchpad.View")
     def attendee_export(self):
-        text = 'Export attendees to CSV'
-        summary = 'Export attendee contact information to CSV format'
-        return Link('+attendees-csv', text, summary, icon='info')
+        text = "Export attendees to CSV"
+        summary = "Export attendee contact information to CSV format"
+        return Link("+attendees-csv", text, summary, icon="info")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def edit(self):
-        text = 'Change details'
-        summary = 'Modify the meeting description, dates or title'
-        return Link('+edit', text, summary, icon='edit')
+        text = "Change details"
+        summary = "Modify the meeting description, dates or title"
+        return Link("+edit", text, summary, icon="edit")
 
-    @enabled_with_permission('launchpad.Edit')
+    @enabled_with_permission("launchpad.Edit")
     def branding(self):
-        text = 'Change branding'
-        summary = 'Modify the imagery used to represent this meeting'
-        return Link('+branding', text, summary, icon='edit')
+        text = "Change branding"
+        summary = "Modify the imagery used to represent this meeting"
+        return Link("+branding", text, summary, icon="edit")
 
-    @enabled_with_permission('launchpad.Moderate')
+    @enabled_with_permission("launchpad.Moderate")
     def delete(self):
-        return Link('+delete', 'Delete sprint', icon='trash-icon')
+        return Link("+delete", "Delete sprint", icon="trash-icon")
 
 
-class SprintSpecificationsMenu(NavigationMenu,
-                               HasSpecificationsMenuMixin):
+class SprintSpecificationsMenu(NavigationMenu, HasSpecificationsMenuMixin):
     usedfor = ISprint
-    facet = 'specifications'
-    links = ['assignments', 'listdeclined', 'settopics', 'new']
+    facet = "specifications"
+    links = ["assignments", "listdeclined", "settopics", "new"]
 
-    @enabled_with_permission('launchpad.Driver')
+    @enabled_with_permission("launchpad.Driver")
     def settopics(self):
-        text = 'Set agenda'
-        return Link('+settopics', text, icon='edit')
+        text = "Set agenda"
+        return Link("+settopics", text, icon="edit")
 
 
 class SprintSetNavigation(GetitemNavigation):
@@ -167,12 +163,13 @@ class SprintSetNavigation(GetitemNavigation):
 
 class SprintSetBreadcrumb(Breadcrumb):
     """Builds a breadcrumb for an `ISprintSet`."""
-    text = 'Meetings'
+
+    text = "Meetings"
 
 
 class HasSprintsView(LaunchpadView):
 
-    page_title = 'Events'
+    page_title = "Events"
 
 
 @implementer(IMajorHeadingView)
@@ -186,7 +183,7 @@ class SprintView(HasSpecificationsView):
 
     @property
     def page_title(self):
-        return '%s (sprint or meeting)' % self.context.title
+        return "%s (sprint or meeting)" % self.context.title
 
     def initialize(self):
         self.notices = []
@@ -220,35 +217,40 @@ class SprintView(HasSpecificationsView):
     @cachedproperty
     def latest_approved(self):
         filter = [SpecificationFilter.ACCEPTED]
-        return self.context.specifications(self.user, filter=filter,
-                    quantity=self.latest_specs_limit,
-                    sort=SpecificationSort.DATE)
+        return self.context.specifications(
+            self.user,
+            filter=filter,
+            quantity=self.latest_specs_limit,
+            sort=SpecificationSort.DATE,
+        )
 
     def formatDateTime(self, dt):
         """Format a datetime value according to the sprint's time zone"""
         dt = dt.astimezone(self.tzinfo)
-        return dt.strftime('%Y-%m-%d %H:%M %Z')
+        return dt.strftime("%Y-%m-%d %H:%M %Z")
 
     def formatDate(self, dt):
         """Format a date value according to the sprint's time zone"""
         dt = dt.astimezone(self.tzinfo)
-        return dt.strftime('%Y-%m-%d')
+        return dt.strftime("%Y-%m-%d")
 
-    _local_timeformat = '%H:%M %Z on %A, %Y-%m-%d'
+    _local_timeformat = "%H:%M %Z on %A, %Y-%m-%d"
 
     @property
     def local_start(self):
         """The sprint start time, in the local time zone, as text."""
         tz = pytz.timezone(self.context.time_zone)
         return self.context.time_starts.astimezone(tz).strftime(
-                    self._local_timeformat)
+            self._local_timeformat
+        )
 
     @property
     def local_end(self):
         """The sprint end time, in the local time zone, as text."""
         tz = pytz.timezone(self.context.time_zone)
         return self.context.time_ends.astimezone(tz).strftime(
-                    self._local_timeformat)
+            self._local_timeformat
+        )
 
 
 class SprintAddView(LaunchpadFormView):
@@ -256,57 +258,68 @@ class SprintAddView(LaunchpadFormView):
 
     schema = ISprint
     label = "Register a meeting"
-    field_names = ['name', 'title', 'summary', 'home_page', 'driver',
-                   'time_zone', 'time_starts', 'time_ends', 'is_physical',
-                   'address',
-                   ]
+    field_names = [
+        "name",
+        "title",
+        "summary",
+        "home_page",
+        "driver",
+        "time_zone",
+        "time_starts",
+        "time_ends",
+        "is_physical",
+        "address",
+    ]
     custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5)
     custom_widget_time_starts = CustomWidgetFactory(
-        DateTimeWidget, display_zone=False)
+        DateTimeWidget, display_zone=False
+    )
     custom_widget_time_ends = CustomWidgetFactory(
-        DateTimeWidget, display_zone=False)
+        DateTimeWidget, display_zone=False
+    )
     custom_widget_address = CustomWidgetFactory(TextAreaWidget, height=3)
 
     sprint = None
 
     def setUpWidgets(self):
         LaunchpadFormView.setUpWidgets(self)
-        timeformat = '%Y-%m-%d %H:%M'
-        self.widgets['time_starts'].timeformat = timeformat
-        self.widgets['time_ends'].timeformat = timeformat
-        time_zone_widget = self.widgets['time_zone']
+        timeformat = "%Y-%m-%d %H:%M"
+        self.widgets["time_starts"].timeformat = timeformat
+        self.widgets["time_ends"].timeformat = timeformat
+        time_zone_widget = self.widgets["time_zone"]
         if time_zone_widget.hasValidInput():
             tz = pytz.timezone(time_zone_widget.getInputValue())
-            self.widgets['time_starts'].required_time_zone = tz
-            self.widgets['time_ends'].required_time_zone = tz
+            self.widgets["time_starts"].required_time_zone = tz
+            self.widgets["time_ends"].required_time_zone = tz
 
     def validate(self, data):
-        time_starts = data.get('time_starts')
-        time_ends = data.get('time_ends')
+        time_starts = data.get("time_starts")
+        time_ends = data.get("time_ends")
         if time_starts and time_ends and time_ends < time_starts:
             self.setFieldError(
-                'time_ends', "This event can't start after it ends")
+                "time_ends", "This event can't start after it ends"
+            )
 
-    @action(_('Add Sprint'), name='add')
+    @action(_("Add Sprint"), name="add")
     def add_action(self, action, data):
         self.sprint = getUtility(ISprintSet).new(
             owner=self.user,
-            name=data['name'],
-            title=data['title'],
-            summary=data['summary'],
-            home_page=data['home_page'],
-            driver=data['driver'],
-            time_zone=data['time_zone'],
-            time_starts=data['time_starts'],
-            time_ends=data['time_ends'],
-            is_physical=data['is_physical'],
-            address=data['address'],
-            )
-        self.request.response.addInfoNotification('Sprint created.')
+            name=data["name"],
+            title=data["title"],
+            summary=data["summary"],
+            home_page=data["home_page"],
+            driver=data["driver"],
+            time_zone=data["time_zone"],
+            time_starts=data["time_starts"],
+            time_ends=data["time_ends"],
+            is_physical=data["is_physical"],
+            address=data["address"],
+        )
+        self.request.response.addInfoNotification("Sprint created.")
 
     @property
     def next_url(self):
-        assert self.sprint is not None, 'No sprint has been created'
+        assert self.sprint is not None, "No sprint has been created"
         return canonical_url(self.sprint)
 
     @property
@@ -319,7 +332,7 @@ class SprintBrandingView(BrandingChangeView):
     schema = ISprint
     # sabdfl 2007-03-28 deliberately leaving icon off the list, i think it
     # would be overkill, we can add it later if people ask for it
-    field_names = ['logo', 'mugshot']
+    field_names = ["logo", "mugshot"]
 
 
 class SprintEditView(LaunchpadEditFormView):
@@ -328,39 +341,50 @@ class SprintEditView(LaunchpadEditFormView):
     schema = ISprint
     label = "Edit sprint details"
 
-    field_names = ['name', 'title', 'summary', 'home_page', 'driver',
-                   'time_zone', 'time_starts', 'time_ends', 'is_physical',
-                   'address',
-                   ]
+    field_names = [
+        "name",
+        "title",
+        "summary",
+        "home_page",
+        "driver",
+        "time_zone",
+        "time_starts",
+        "time_ends",
+        "is_physical",
+        "address",
+    ]
     custom_widget_summary = CustomWidgetFactory(TextAreaWidget, height=5)
     custom_widget_time_starts = CustomWidgetFactory(
-        DateTimeWidget, display_zone=False)
+        DateTimeWidget, display_zone=False
+    )
     custom_widget_time_ends = CustomWidgetFactory(
-        DateTimeWidget, display_zone=False)
+        DateTimeWidget, display_zone=False
+    )
     custom_widget_address = CustomWidgetFactory(TextAreaWidget, height=3)
 
     def setUpWidgets(self):
         LaunchpadEditFormView.setUpWidgets(self)
-        timeformat = '%Y-%m-%d %H:%M'
-        self.widgets['time_starts'].timeformat = timeformat
-        self.widgets['time_ends'].timeformat = timeformat
-        time_zone_widget = self.widgets['time_zone']
+        timeformat = "%Y-%m-%d %H:%M"
+        self.widgets["time_starts"].timeformat = timeformat
+        self.widgets["time_ends"].timeformat = timeformat
+        time_zone_widget = self.widgets["time_zone"]
         # What time zone are the start and end values relative to?
         if time_zone_widget.hasValidInput():
             tz = pytz.timezone(time_zone_widget.getInputValue())
         else:
             tz = pytz.timezone(self.context.time_zone)
-        self.widgets['time_starts'].required_time_zone = tz
-        self.widgets['time_ends'].required_time_zone = tz
+        self.widgets["time_starts"].required_time_zone = tz
+        self.widgets["time_ends"].required_time_zone = tz
 
     def validate(self, data):
-        time_starts = data.get('time_starts')
-        time_ends = data.get('time_ends')
+        time_starts = data.get("time_starts")
+        time_ends = data.get("time_ends")
         if time_starts and time_ends and time_ends < time_starts:
             self.setFieldError(
-                'time_ends', "This event can't start after it ends")
+                "time_ends", "This event can't start after it ends"
+            )
 
-    @action(_('Change'), name='change')
+    @action(_("Change"), name="change")
     def change_action(self, action, data):
         self.updateContextFromData(data)
 
@@ -403,7 +427,8 @@ class SprintTopicSetView(HasSpecificationsView, LaunchpadView):
     @property
     def label(self):
         return smartquote(
-            'Review discussion topics for "%s" sprint' % self.context.title)
+            'Review discussion topics for "%s" sprint' % self.context.title
+        )
 
     page_title = label
 
@@ -411,7 +436,8 @@ class SprintTopicSetView(HasSpecificationsView, LaunchpadView):
         self.status_message = None
         self.process_form()
         self.attendee_ids = {
-            attendance.attendeeID for attendance in self.context.attendances}
+            attendance.attendeeID for attendance in self.context.attendances
+        }
 
     @cachedproperty
     def spec_filter(self):
@@ -433,50 +459,55 @@ class SprintTopicSetView(HasSpecificationsView, LaunchpadView):
         """
         form = self.request.form
 
-        if 'SUBMIT_CANCEL' in form:
-            self.status_message = 'Cancelled'
+        if "SUBMIT_CANCEL" in form:
+            self.status_message = "Cancelled"
             self.request.response.redirect(
-                canonical_url(self.context) + '/+specs')
+                canonical_url(self.context) + "/+specs"
+            )
             return
 
-        if 'SUBMIT_ACCEPT' not in form and 'SUBMIT_DECLINE' not in form:
-            self.status_message = ''
+        if "SUBMIT_ACCEPT" not in form and "SUBMIT_DECLINE" not in form:
+            self.status_message = ""
             return
 
-        if self.request.method == 'POST':
-            if 'speclink' not in form:
+        if self.request.method == "POST":
+            if "speclink" not in form:
                 self.status_message = (
-                    'Please select specifications to accept or decline.')
+                    "Please select specifications to accept or decline."
+                )
                 return
             # determine if we are accepting or declining
-            if 'SUBMIT_ACCEPT' in form:
-                assert 'SUBMIT_DECLINE' not in form
-                action = 'Accepted'
+            if "SUBMIT_ACCEPT" in form:
+                assert "SUBMIT_DECLINE" not in form
+                action = "Accepted"
             else:
-                assert 'SUBMIT_DECLINE' in form
-                action = 'Declined'
+                assert "SUBMIT_DECLINE" in form
+                action = "Declined"
 
-        selected_specs = form['speclink']
+        selected_specs = form["speclink"]
         if isinstance(selected_specs, str):
             # only a single item was selected, but we want to deal with a
             # list for the general case, so convert it to a list
             selected_specs = [selected_specs]
         selected_specs = [int(speclink) for speclink in selected_specs]
 
-        if action == 'Accepted':
+        if action == "Accepted":
             action_fn = self.context.acceptSpecificationLinks
         else:
             action_fn = self.context.declineSpecificationLinks
         leftover = action_fn(selected_specs, self.user)
 
         # Status message like: "Accepted 27 specification(s)."
-        self.status_message = '%s %d specification(s).' % (
-            action, len(selected_specs))
+        self.status_message = "%s %d specification(s)." % (
+            action,
+            len(selected_specs),
+        )
 
         if leftover == 0:
             # they are all done, so redirect back to the spec listing page
             self.request.response.redirect(
-                canonical_url(self.context) + '/+specs')
+                canonical_url(self.context) + "/+specs"
+            )
 
 
 class SprintMeetingExportView(LaunchpadView):
@@ -486,16 +517,22 @@ class SprintMeetingExportView(LaunchpadView):
         self.attendees = []
         attendee_set = set()
         for attendance in self.context.attendances:
-            self.attendees.append(dict(
-                name=attendance.attendee.name,
-                displayname=attendance.attendee.displayname,
-                start=attendance.time_starts.strftime('%Y-%m-%dT%H:%M:%SZ'),
-                end=attendance.time_ends.strftime('%Y-%m-%dT%H:%M:%SZ')))
+            self.attendees.append(
+                dict(
+                    name=attendance.attendee.name,
+                    displayname=attendance.attendee.displayname,
+                    start=attendance.time_starts.strftime(
+                        "%Y-%m-%dT%H:%M:%SZ"
+                    ),
+                    end=attendance.time_ends.strftime("%Y-%m-%dT%H:%M:%SZ"),
+                )
+            )
             attendee_set.add(attendance.attendeeID)
 
         model_specs = []
         for spec in self.context.specifications(
-            self.user, filter=[SpecificationFilter.ACCEPTED]):
+            self.user, filter=[SpecificationFilter.ACCEPTED]
+        ):
 
             # Skip sprints with no priority or less than LOW.
             if spec.priority < SpecificationPriority.UNDEFINED:
@@ -504,12 +541,14 @@ class SprintMeetingExportView(LaunchpadView):
 
         people = defaultdict(dict)
         # Attendees per specification.
-        for subscription in load_referencing(SpecificationSubscription,
-                model_specs, ['specification_id']):
+        for subscription in load_referencing(
+            SpecificationSubscription, model_specs, ["specification_id"]
+        ):
             if subscription.person_id not in attendee_set:
                 continue
             people[subscription.specification_id][
-                subscription.person_id] = subscription.essential
+                subscription.person_id
+            ] = subscription.essential
 
         # Spec specials - drafter/assignee.  Don't need approver for
         # performance, as specifications() above eager-loaded the
@@ -523,48 +562,59 @@ class SprintMeetingExportView(LaunchpadView):
             if spec.drafter is not None:
                 spec_people[spec.drafter.id] = True
                 attendee_set.add(spec.drafter.id)
-        people_by_id = {person.id: person for person in
-            getUtility(IPersonSet).getPrecachedPersonsFromIDs(attendee_set)}
+        people_by_id = {
+            person.id: person
+            for person in getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                attendee_set
+            )
+        }
         self.specifications = [
-            dict(spec=spec, interested=[
+            dict(
+                spec=spec,
+                interested=[
                     dict(name=people_by_id[person_id].name, required=required)
-                    for (person_id, required) in people[spec.id].items()]
-                ) for spec in model_specs]
+                    for (person_id, required) in people[spec.id].items()
+                ],
+            )
+            for spec in model_specs
+        ]
 
     def render(self):
         self.request.response.setHeader(
-            'content-type', 'application/xml;charset=utf-8')
+            "content-type", "application/xml;charset=utf-8"
+        )
         body = super().render()
-        return body.encode('utf-8')
+        return body.encode("utf-8")
 
 
 class SprintSetNavigationMenu(RegistryCollectionActionMenuBase):
     """Action menu for sprints index."""
+
     usedfor = ISprintSet
     links = (
-        'register_team',
-        'register_project',
-        'register_sprint',
-        'create_account',
-        'view_all_sprints',
-        )
+        "register_team",
+        "register_project",
+        "register_sprint",
+        "create_account",
+        "view_all_sprints",
+    )
 
-    @enabled_with_permission('launchpad.View')
+    @enabled_with_permission("launchpad.View")
     def register_sprint(self):
-        text = 'Register a meeting'
-        summary = 'Register a developer sprint, summit, or gathering'
-        return Link('+new', text, summary=summary, icon='add')
+        text = "Register a meeting"
+        summary = "Register a developer sprint, summit, or gathering"
+        return Link("+new", text, summary=summary, icon="add")
 
     def view_all_sprints(self):
-        text = 'Show all meetings'
-        return Link('+all', text, icon='list')
+        text = "Show all meetings"
+        return Link("+all", text, icon="list")
 
 
 @implementer(IRegistryCollectionNavigationMenu)
 class SprintSetView(LaunchpadView):
     """View for the /sprints top level collection page."""
 
-    page_title = 'Meetings and sprints registered in Launchpad'
+    page_title = "Meetings and sprints registered in Launchpad"
 
     def all_batched(self):
         return BatchNavigator(self.context.all, self.request)
@@ -575,48 +625,61 @@ class SprintAttendeesCsvExportView(LaunchpadView):
 
     def render(self):
         """Render a CSV output of all the attendees for a sprint."""
-        rows = [('Launchpad username',
-                 'Display name',
-                 'Email',
-                 'IRC nickname',
-                 'Phone',
-                 'Organization',
-                 'City',
-                 'Country',
-                 'Timezone',
-                 'Arriving',
-                 'Leaving',
-                 'Physically present',
-                 )]
+        rows = [
+            (
+                "Launchpad username",
+                "Display name",
+                "Email",
+                "IRC nickname",
+                "Phone",
+                "Organization",
+                "City",
+                "Country",
+                "Timezone",
+                "Arriving",
+                "Leaving",
+                "Physically present",
+            )
+        ]
         for attendance in self.context.attendances:
-            time_zone = ''
+            time_zone = ""
             location = attendance.attendee.location
             if location is not None and location.visible:
                 time_zone = attendance.attendee.time_zone
-            irc_nicknames = ', '.join(sorted({
-                ircid.nickname for ircid
-                 in attendance.attendee.ircnicknames}))
+            irc_nicknames = ", ".join(
+                sorted(
+                    {
+                        ircid.nickname
+                        for ircid in attendance.attendee.ircnicknames
+                    }
+                )
+            )
             rows.append(
-                (attendance.attendee.name,
-                 attendance.attendee.displayname,
-                 attendance.attendee.safe_email_or_blank,
-                 irc_nicknames,
-                 # We used to store phone, organization, city and
-                 # country, but this was a lie because users could not
-                 # update these fields.
-                 '',  # attendance.attendee.phone
-                 '',  # attendance.attendee.organization
-                 '',  # attendance.attendee.city
-                 '',  # country
-                 time_zone,
-                 attendance.time_starts.strftime('%Y-%m-%dT%H:%M:%SZ'),
-                 attendance.time_ends.strftime('%Y-%m-%dT%H:%M:%SZ'),
-                 str(attendance.is_physical)))
+                (
+                    attendance.attendee.name,
+                    attendance.attendee.displayname,
+                    attendance.attendee.safe_email_or_blank,
+                    irc_nicknames,
+                    # We used to store phone, organization, city and
+                    # country, but this was a lie because users could not
+                    # update these fields.
+                    "",  # attendance.attendee.phone
+                    "",  # attendance.attendee.organization
+                    "",  # attendance.attendee.city
+                    "",  # country
+                    time_zone,
+                    attendance.time_starts.strftime("%Y-%m-%dT%H:%M:%SZ"),
+                    attendance.time_ends.strftime("%Y-%m-%dT%H:%M:%SZ"),
+                    str(attendance.is_physical),
+                )
+            )
         self.request.response.setHeader(
-            'Content-type', 'text/csv;charset="utf-8"')
+            "Content-type", 'text/csv;charset="utf-8"'
+        )
         self.request.response.setHeader(
-            'Content-disposition',
-            'attachment; filename=%s-attendees.csv' % self.context.name)
+            "Content-disposition",
+            "attachment; filename=%s-attendees.csv" % self.context.name,
+        )
         output = io.StringIO()
         writer = csv.writer(output)
         writer.writerows(rows)
diff --git a/lib/lp/blueprints/browser/sprintattendance.py b/lib/lp/blueprints/browser/sprintattendance.py
index a3ed9ba..acfa792 100644
--- a/lib/lp/blueprints/browser/sprintattendance.py
+++ b/lib/lp/blueprints/browser/sprintattendance.py
@@ -4,9 +4,9 @@
 """Views for SprintAttendance."""
 
 __all__ = [
-    'SprintAttendanceAttendView',
-    'SprintAttendanceRegisterView',
-    ]
+    "SprintAttendanceAttendView",
+    "SprintAttendanceRegisterView",
+]
 
 from datetime import timedelta
 
@@ -14,10 +14,7 @@ import pytz
 from zope.formlib.widget import CustomWidgetFactory
 
 from lp import _
-from lp.app.browser.launchpadform import (
-    action,
-    LaunchpadFormView,
-    )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
 from lp.app.widgets.date import DateTimeWidget
 from lp.app.widgets.itemswidgets import LaunchpadBooleanRadioWidget
 from lp.blueprints.interfaces.sprintattendance import ISprintAttendance
@@ -30,26 +27,30 @@ class BaseSprintAttendanceAddView(LaunchpadFormView):
     custom_widget_time_starts = DateTimeWidget
     custom_widget_time_ends = DateTimeWidget
     custom_widget_is_physical = CustomWidgetFactory(
-        LaunchpadBooleanRadioWidget, orientation='vertical',
-        true_label="Physically", false_label="Remotely", hint=None)
+        LaunchpadBooleanRadioWidget,
+        orientation="vertical",
+        true_label="Physically",
+        false_label="Remotely",
+        hint=None,
+    )
 
     @property
     def field_names(self):
         """Return the list of field names to display."""
-        field_names = ['time_starts', 'time_ends']
+        field_names = ["time_starts", "time_ends"]
         if self.context.is_physical:
-            field_names.append('is_physical')
+            field_names.append("is_physical")
         return field_names
 
     def setUpWidgets(self):
         LaunchpadFormView.setUpWidgets(self)
         tz = pytz.timezone(self.context.time_zone)
-        self.starts_widget = self.widgets['time_starts']
-        self.ends_widget = self.widgets['time_ends']
+        self.starts_widget = self.widgets["time_starts"]
+        self.ends_widget = self.widgets["time_ends"]
         self.starts_widget.required_time_zone = tz
         self.ends_widget.required_time_zone = tz
         # We don't need to display seconds
-        timeformat = '%Y-%m-%d %H:%M'
+        timeformat = "%Y-%m-%d %H:%M"
         self.starts_widget.timeformat = timeformat
         self.ends_widget.timeformat = timeformat
         # Constrain the widget to dates from the day before to the day
@@ -71,36 +72,49 @@ class BaseSprintAttendanceAddView(LaunchpadFormView):
          * they don't arrive after the end of the sprint
          * they don't depart before the start of the sprint
         """
-        time_starts = data.get('time_starts')
-        time_ends = data.get('time_ends')
+        time_starts = data.get("time_starts")
+        time_ends = data.get("time_ends")
 
         if time_starts and time_starts > self.context.time_ends:
             self.setFieldError(
-                'time_starts',
-                _('Choose an arrival time before the end of the meeting.'))
+                "time_starts",
+                _("Choose an arrival time before the end of the meeting."),
+            )
         if time_ends:
             if time_starts and time_ends < time_starts:
                 self.setFieldError(
-                    'time_ends',
-                    _('The end time must be after the start time.'))
+                    "time_ends",
+                    _("The end time must be after the start time."),
+                )
             elif time_ends < self.context.time_starts:
                 self.setFieldError(
-                    'time_ends', _('Choose a departure time after the '
-                                   'start of the meeting.'))
-            elif (time_ends.hour == 0 and time_ends.minute == 0 and
-                  time_ends.second == 0):
+                    "time_ends",
+                    _(
+                        "Choose a departure time after the "
+                        "start of the meeting."
+                    ),
+                )
+            elif (
+                time_ends.hour == 0
+                and time_ends.minute == 0
+                and time_ends.second == 0
+            ):
                 # We assume the user entered just a date, which gives them
                 # midnight in the morning of that day, when they probably want
                 # the end of the day.
-                data['time_ends'] = min(
+                data["time_ends"] = min(
                     self.context.time_ends,
-                    time_ends + timedelta(days=1, seconds=-1))
+                    time_ends + timedelta(days=1, seconds=-1),
+                )
 
     def getDates(self, data):
-        time_starts = data['time_starts']
-        time_ends = data['time_ends']
-        if (time_ends.hour == 0 and time_ends.minute == 0 and
-            time_ends.second == 0):
+        time_starts = data["time_starts"]
+        time_ends = data["time_ends"]
+        if (
+            time_ends.hour == 0
+            and time_ends.minute == 0
+            and time_ends.second == 0
+        ):
             # We assume the user entered just a date, which gives them
             # midnight in the morning of that day, when they probably want
             # the end of the day.
@@ -121,21 +135,23 @@ class BaseSprintAttendanceAddView(LaunchpadFormView):
 
     cancel_url = next_url
 
-    _local_timeformat = '%H:%M on %A, %Y-%m-%d'
+    _local_timeformat = "%H:%M on %A, %Y-%m-%d"
 
     @property
     def local_start(self):
         """The sprint start time, in the local time zone, as text."""
         tz = pytz.timezone(self.context.time_zone)
         return self.context.time_starts.astimezone(tz).strftime(
-                    self._local_timeformat)
+            self._local_timeformat
+        )
 
     @property
     def local_end(self):
         """The sprint end time, in the local time zone, as text."""
         tz = pytz.timezone(self.context.time_zone)
         return self.context.time_ends.astimezone(tz).strftime(
-                    self._local_timeformat)
+            self._local_timeformat
+        )
 
 
 class SprintAttendanceAttendView(BaseSprintAttendanceAddView):
@@ -148,39 +164,46 @@ class SprintAttendanceAttendView(BaseSprintAttendanceAddView):
         """Show committed attendance, or default to the sprint times."""
         for attendance in self.context.attendances:
             if attendance.attendee == self.user:
-                return dict(time_starts=attendance.time_starts,
-                            time_ends=attendance.time_ends,
-                            is_physical=attendance.is_physical)
+                return dict(
+                    time_starts=attendance.time_starts,
+                    time_ends=attendance.time_ends,
+                    is_physical=attendance.is_physical,
+                )
         # If this person is not yet registered, then default to showing the
         # full sprint dates.
-        return {'time_starts': self.context.time_starts,
-                'time_ends': self.context.time_ends}
+        return {
+            "time_starts": self.context.time_starts,
+            "time_ends": self.context.time_ends,
+        }
 
-    @action(_('Register'), name='register')
+    @action(_("Register"), name="register")
     def register_action(self, action, data):
         time_starts, time_ends = self.getDates(data)
-        is_physical = self.context.is_physical and data['is_physical']
+        is_physical = self.context.is_physical and data["is_physical"]
         self.context.attend(self.user, time_starts, time_ends, is_physical)
 
 
 class SprintAttendanceRegisterView(BaseSprintAttendanceAddView):
     """A view used to register someone else's attendance at a sprint."""
 
-    label = 'Register someone else'
+    label = "Register someone else"
 
     @property
     def field_names(self):
-        return ['attendee'] + super().field_names
+        return ["attendee"] + super().field_names
 
     @property
     def initial_values(self):
         """Default to displaying the full span of the sprint."""
-        return {'time_starts': self.context.time_starts,
-                'time_ends': self.context.time_ends}
+        return {
+            "time_starts": self.context.time_starts,
+            "time_ends": self.context.time_ends,
+        }
 
-    @action(_('Register'), name='register')
+    @action(_("Register"), name="register")
     def register_action(self, action, data):
         time_starts, time_ends = self.getDates(data)
-        is_physical = self.context.is_physical and data['is_physical']
+        is_physical = self.context.is_physical and data["is_physical"]
         self.context.attend(
-            data['attendee'], time_starts, time_ends, is_physical)
+            data["attendee"], time_starts, time_ends, is_physical
+        )
diff --git a/lib/lp/blueprints/browser/sprintspecification.py b/lib/lp/blueprints/browser/sprintspecification.py
index 3ecf7c2..ce8d5ad 100644
--- a/lib/lp/blueprints/browser/sprintspecification.py
+++ b/lib/lp/blueprints/browser/sprintspecification.py
@@ -4,28 +4,25 @@
 """Views for SprintSpecification."""
 
 __all__ = [
-    'SprintSpecificationDecideView',
-    ]
+    "SprintSpecificationDecideView",
+]
 
 from lazr.restful.utils import smartquote
 
-from lp.services.webapp import (
-    canonical_url,
-    LaunchpadView,
-    )
+from lp.services.webapp import LaunchpadView, canonical_url
 
 
 class SprintSpecificationDecideView(LaunchpadView):
-
     @property
     def label(self):
         return smartquote(
-            'Consider agenda item for "%s"' % self.context.sprint.title)
+            'Consider agenda item for "%s"' % self.context.sprint.title
+        )
 
     def initialize(self):
-        accept = self.request.form.get('accept')
-        decline = self.request.form.get('decline')
-        cancel = self.request.form.get('cancel')
+        accept = self.request.form.get("accept")
+        decline = self.request.form.get("decline")
+        cancel = self.request.form.get("cancel")
         decided = False
         if accept is not None:
             self.context.acceptBy(self.user)
@@ -35,4 +32,5 @@ class SprintSpecificationDecideView(LaunchpadView):
             decided = True
         if decided or cancel is not None:
             self.request.response.redirect(
-                canonical_url(self.context.specification))
+                canonical_url(self.context.specification)
+            )
diff --git a/lib/lp/blueprints/browser/tests/test_breadcrumbs.py b/lib/lp/blueprints/browser/tests/test_breadcrumbs.py
index dc5c8e5..f56fd48 100644
--- a/lib/lp/blueprints/browser/tests/test_breadcrumbs.py
+++ b/lib/lp/blueprints/browser/tests/test_breadcrumbs.py
@@ -5,33 +5,37 @@ from lp.services.webapp.publisher import canonical_url
 from lp.testing.breadcrumbs import BaseBreadcrumbTestCase
 
 
-class TestHasSpecificationsBreadcrumbOnBlueprintsFacet(
-        BaseBreadcrumbTestCase):
+class TestHasSpecificationsBreadcrumbOnBlueprintsFacet(BaseBreadcrumbTestCase):
     """Test Breadcrumbs for IHasSpecifications on the blueprints vhost."""
 
     def setUp(self):
         super().setUp()
         self.person = self.factory.makePerson()
         self.person_specs_url = canonical_url(
-            self.person, rootsite='blueprints')
+            self.person, rootsite="blueprints"
+        )
         self.product = self.factory.makeProduct(
-            name='crumb-tester', displayname="Crumb Tester")
+            name="crumb-tester", displayname="Crumb Tester"
+        )
         self.product_specs_url = canonical_url(
-            self.product, rootsite='blueprints')
+            self.product, rootsite="blueprints"
+        )
 
     def test_product(self):
         crumbs = self.getBreadcrumbsForObject(
-            self.product, rootsite='blueprints')
+            self.product, rootsite="blueprints"
+        )
         last_crumb = crumbs[-1]
         self.assertEqual(last_crumb.url, self.product_specs_url)
-        self.assertEqual(last_crumb.text, 'Blueprints')
+        self.assertEqual(last_crumb.text, "Blueprints")
 
     def test_person(self):
         crumbs = self.getBreadcrumbsForObject(
-            self.person, rootsite='blueprints')
+            self.person, rootsite="blueprints"
+        )
         last_crumb = crumbs[-1]
         self.assertEqual(last_crumb.url, self.person_specs_url)
-        self.assertEqual(last_crumb.text, 'Blueprints')
+        self.assertEqual(last_crumb.text, "Blueprints")
 
 
 class TestSpecificationBreadcrumb(BaseBreadcrumbTestCase):
@@ -40,11 +44,14 @@ class TestSpecificationBreadcrumb(BaseBreadcrumbTestCase):
     def setUp(self):
         super().setUp()
         self.product = self.factory.makeProduct(
-            name='crumb-tester', displayname="Crumb Tester")
+            name="crumb-tester", displayname="Crumb Tester"
+        )
         self.specification = self.factory.makeSpecification(
-            title="Crumby Specification", product=self.product)
+            title="Crumby Specification", product=self.product
+        )
         self.specification_url = canonical_url(
-            self.specification, rootsite='blueprints')
+            self.specification, rootsite="blueprints"
+        )
 
     def test_specification(self):
         crumbs = self.getBreadcrumbsForObject(self.specification)
diff --git a/lib/lp/blueprints/browser/tests/test_hasspecifications.py b/lib/lp/blueprints/browser/tests/test_hasspecifications.py
index 6046cae..501fc63 100644
--- a/lib/lp/blueprints/browser/tests/test_hasspecifications.py
+++ b/lib/lp/blueprints/browser/tests/test_hasspecifications.py
@@ -1,10 +1,7 @@
 # Copyright 2010 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from lp.testing import (
-    login_person,
-    TestCaseWithFactory,
-    )
+from lp.testing import TestCaseWithFactory, login_person
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.views import create_initialized_view
 
@@ -15,15 +12,14 @@ class TestPersonSpecWorkloadView(TestCaseWithFactory):
 
     def setUp(self):
         super().setUp()
-        self.owner = self.factory.makePerson(name='blue')
+        self.owner = self.factory.makePerson(name="blue")
         login_person(self.owner)
-        self.team = self.factory.makeTeam(name='square', owner='blue')
-        self.member = self.factory.makePerson(name='green')
+        self.team = self.factory.makeTeam(name="square", owner="blue")
+        self.member = self.factory.makePerson(name="green")
         self.team.addMember(self.member, self.owner)
 
     def test_view_attributes(self):
-        view = create_initialized_view(
-            self.team, name='+specworkload')
-        label = 'Blueprint workload'
+        view = create_initialized_view(self.team, name="+specworkload")
+        label = "Blueprint workload"
         self.assertEqual(label, view.label)
         self.assertEqual(20, view.members.batch.size)
diff --git a/lib/lp/blueprints/browser/tests/test_menus.py b/lib/lp/blueprints/browser/tests/test_menus.py
index 2edb49a..56d90e9 100644
--- a/lib/lp/blueprints/browser/tests/test_menus.py
+++ b/lib/lp/blueprints/browser/tests/test_menus.py
@@ -4,7 +4,7 @@
 from lp.blueprints.browser.specification import (
     SpecificationActionMenu,
     SpecificationContextMenu,
-    )
+)
 from lp.testing import TestCaseWithFactory
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.menu import check_menu_links
@@ -12,6 +12,7 @@ from lp.testing.menu import check_menu_links
 
 class TestSpecificationMenus(TestCaseWithFactory):
     """Test specification menus links."""
+
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
diff --git a/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py b/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py
index e867699..320d5c6 100644
--- a/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py
+++ b/lib/lp/blueprints/browser/tests/test_person_upcomingwork.py
@@ -1,10 +1,7 @@
 # Copyright 2012 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from datetime import (
-    datetime,
-    timedelta,
-    )
+from datetime import datetime, timedelta
 from operator import attrgetter
 
 from zope.security.proxy import removeSecurityProxy
@@ -12,22 +9,18 @@ from zope.security.proxy import removeSecurityProxy
 from lp.app.enums import InformationType
 from lp.blueprints.browser.person_upcomingwork import (
     GenericWorkItem,
-    getWorkItemsDueBefore,
     WorkItemContainer,
-    )
+    getWorkItemsDueBefore,
+)
 from lp.blueprints.enums import SpecificationWorkItemStatus
 from lp.testing import (
-    anonymous_logged_in,
     BrowserTestCase,
     TestCase,
     TestCaseWithFactory,
-    )
+    anonymous_logged_in,
+)
 from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.pages import (
-    extract_text,
-    find_tag_by_id,
-    find_tags_by_class,
-    )
+from lp.testing.pages import extract_text, find_tag_by_id, find_tags_by_class
 from lp.testing.views import create_initialized_view
 
 
@@ -38,29 +31,35 @@ class Test_getWorkItemsDueBefore(TestCaseWithFactory):
     def setUp(self):
         super().setUp()
         self.today = datetime.today().date()
-        current_milestone = self.factory.makeMilestone(
-            dateexpected=self.today)
+        current_milestone = self.factory.makeMilestone(dateexpected=self.today)
         self.current_milestone = current_milestone
         self.future_milestone = self.factory.makeMilestone(
             product=current_milestone.product,
-            dateexpected=datetime(2060, 1, 1))
+            dateexpected=datetime(2060, 1, 1),
+        )
         self.team = self.factory.makeTeam()
 
     def test_basic(self):
         spec = self.factory.makeSpecification(
             product=self.current_milestone.product,
-            assignee=self.team.teamowner, milestone=self.current_milestone)
+            assignee=self.team.teamowner,
+            milestone=self.current_milestone,
+        )
         workitem = self.factory.makeSpecificationWorkItem(
-            title='workitem 1', specification=spec)
+            title="workitem 1", specification=spec
+        )
         bugtask = self.factory.makeBug(
-            milestone=self.current_milestone).bugtasks[0]
+            milestone=self.current_milestone
+        ).bugtasks[0]
         removeSecurityProxy(bugtask).assignee = self.team.teamowner
 
         workitems = getWorkItemsDueBefore(
-            self.team, self.current_milestone.dateexpected, user=None)
+            self.team, self.current_milestone.dateexpected, user=None
+        )
 
         self.assertEqual(
-            [self.current_milestone.dateexpected], list(workitems))
+            [self.current_milestone.dateexpected], list(workitems)
+        )
         containers = workitems[self.current_milestone.dateexpected]
         # We have one container for the work item from the spec and another
         # one for the bugtask.
@@ -71,8 +70,7 @@ class Test_getWorkItemsDueBefore(TestCaseWithFactory):
         self.assertEqual(bugtask, bugtask_container.items[0].actual_workitem)
 
         self.assertEqual(1, len(workitem_container.items))
-        self.assertEqual(
-            workitem, workitem_container.items[0].actual_workitem)
+        self.assertEqual(workitem, workitem_container.items[0].actual_workitem)
 
     def test_foreign_container(self):
         # This spec is targeted to a person who's not a member of our team, so
@@ -81,18 +79,24 @@ class Test_getWorkItemsDueBefore(TestCaseWithFactory):
         spec = self.factory.makeSpecification(
             product=self.current_milestone.product,
             milestone=self.current_milestone,
-            assignee=self.factory.makePerson())
+            assignee=self.factory.makePerson(),
+        )
         self.factory.makeSpecificationWorkItem(
-            title='workitem 1', specification=spec)
+            title="workitem 1", specification=spec
+        )
         workitem = self.factory.makeSpecificationWorkItem(
-            title='workitem 2', specification=spec,
-            assignee=self.team.teamowner)
+            title="workitem 2",
+            specification=spec,
+            assignee=self.team.teamowner,
+        )
 
         workitems = getWorkItemsDueBefore(
-            self.team, self.current_milestone.dateexpected, user=None)
+            self.team, self.current_milestone.dateexpected, user=None
+        )
 
         self.assertEqual(
-            [self.current_milestone.dateexpected], list(workitems))
+            [self.current_milestone.dateexpected], list(workitems)
+        )
         containers = workitems[self.current_milestone.dateexpected]
         self.assertEqual(1, len(containers))
         [container] = containers
@@ -102,21 +106,28 @@ class Test_getWorkItemsDueBefore(TestCaseWithFactory):
     def test_future_container(self):
         spec = self.factory.makeSpecification(
             product=self.current_milestone.product,
-            assignee=self.team.teamowner)
+            assignee=self.team.teamowner,
+        )
         # This workitem is targeted to a future milestone so it won't be in
         # our results below.
         self.factory.makeSpecificationWorkItem(
-            title='workitem 1', specification=spec,
-            milestone=self.future_milestone)
+            title="workitem 1",
+            specification=spec,
+            milestone=self.future_milestone,
+        )
         current_wi = self.factory.makeSpecificationWorkItem(
-            title='workitem 2', specification=spec,
-            milestone=self.current_milestone)
+            title="workitem 2",
+            specification=spec,
+            milestone=self.current_milestone,
+        )
 
         workitems = getWorkItemsDueBefore(
-            self.team, self.current_milestone.dateexpected, user=None)
+            self.team, self.current_milestone.dateexpected, user=None
+        )
 
         self.assertEqual(
-            [self.current_milestone.dateexpected], list(workitems))
+            [self.current_milestone.dateexpected], list(workitems)
+        )
         containers = workitems[self.current_milestone.dateexpected]
         self.assertEqual(1, len(containers))
         [container] = containers
@@ -129,41 +140,53 @@ class Test_getWorkItemsDueBefore(TestCaseWithFactory):
         # in both with only the relevant work items.
         spec = self.factory.makeSpecification(
             product=self.current_milestone.product,
-            assignee=self.team.teamowner)
+            assignee=self.team.teamowner,
+        )
         current_workitem = self.factory.makeSpecificationWorkItem(
-            title='workitem 1', specification=spec,
-            milestone=self.current_milestone)
+            title="workitem 1",
+            specification=spec,
+            milestone=self.current_milestone,
+        )
         future_workitem = self.factory.makeSpecificationWorkItem(
-            title='workitem 2', specification=spec,
-            milestone=self.future_milestone)
+            title="workitem 2",
+            specification=spec,
+            milestone=self.future_milestone,
+        )
 
         workitems = getWorkItemsDueBefore(
-            self.team, self.future_milestone.dateexpected, user=None)
+            self.team, self.future_milestone.dateexpected, user=None
+        )
 
         # Both milestone dates are present in the returned results.
         self.assertContentEqual(
-            [self.current_milestone.dateexpected,
-             self.future_milestone.dateexpected],
-            workitems.keys())
+            [
+                self.current_milestone.dateexpected,
+                self.future_milestone.dateexpected,
+            ],
+            workitems.keys(),
+        )
 
         # Current milestone date has a single specification
         # with only the matching work item.
         containers_current = workitems[self.current_milestone.dateexpected]
         self.assertContentEqual(
-            [spec], [container.spec for container in containers_current])
+            [spec], [container.spec for container in containers_current]
+        )
         self.assertContentEqual(
             [current_workitem],
-            [item.actual_workitem for item in containers_current[0].items])
+            [item.actual_workitem for item in containers_current[0].items],
+        )
 
         # Future milestone date has the same specification
         # containing only the work item targetted to future.
         containers_future = workitems[self.future_milestone.dateexpected]
         self.assertContentEqual(
-            [spec],
-            [container.spec for container in containers_future])
+            [spec], [container.spec for container in containers_future]
+        )
         self.assertContentEqual(
             [future_workitem],
-            [item.actual_workitem for item in containers_future[0].items])
+            [item.actual_workitem for item in containers_future[0].items],
+        )
 
 
 class TestGenericWorkItem(TestCaseWithFactory):
@@ -187,7 +210,8 @@ class TestGenericWorkItem(TestCaseWithFactory):
 
     def test_from_workitem(self):
         workitem = self.factory.makeSpecificationWorkItem(
-            milestone=self.milestone)
+            milestone=self.milestone
+        )
         generic_wi = GenericWorkItem.from_workitem(workitem)
         self.assertEqual(generic_wi.assignee, workitem.assignee)
         self.assertEqual(generic_wi.status, workitem.status)
@@ -198,9 +222,7 @@ class TestGenericWorkItem(TestCaseWithFactory):
 
 
 class TestWorkItemContainer(TestCase):
-
     class MockWorkItem:
-
         def __init__(self, is_complete, is_postponed):
             self.is_complete = is_complete
 
@@ -214,7 +236,7 @@ class TestWorkItemContainer(TestCase):
         container.append(self.MockWorkItem(True, False))
         container.append(self.MockWorkItem(False, False))
         container.append(self.MockWorkItem(False, True))
-        self.assertEqual('67', container.percent_done_or_postponed)
+        self.assertEqual("67", container.percent_done_or_postponed)
 
     def test_has_incomplete_work(self):
         # If there are incomplete work items,
@@ -240,9 +262,11 @@ class TestPersonUpcomingWork(BrowserTestCase):
         self.today = datetime.today().date()
         self.tomorrow = self.today + timedelta(days=1)
         self.today_milestone = self.factory.makeMilestone(
-            dateexpected=self.today)
+            dateexpected=self.today
+        )
         self.tomorrow_milestone = self.factory.makeMilestone(
-            dateexpected=self.tomorrow)
+            dateexpected=self.tomorrow
+        )
         self.team = self.factory.makeTeam()
 
     def test_basic_for_team(self):
@@ -250,33 +274,40 @@ class TestPersonUpcomingWork(BrowserTestCase):
         of a team.
         """
         workitem1 = self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.today_milestone)
+            assignee=self.team.teamowner, milestone=self.today_milestone
+        )
         workitem2 = self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.tomorrow_milestone)
+            assignee=self.team.teamowner, milestone=self.tomorrow_milestone
+        )
         bugtask1 = self.factory.makeBug(
-            milestone=self.today_milestone).bugtasks[0]
+            milestone=self.today_milestone
+        ).bugtasks[0]
         bugtask2 = self.factory.makeBug(
-            milestone=self.tomorrow_milestone).bugtasks[0]
+            milestone=self.tomorrow_milestone
+        ).bugtasks[0]
         for bugtask in [bugtask1, bugtask2]:
             removeSecurityProxy(bugtask).assignee = self.team.teamowner
 
         browser = self.getViewBrowser(
-            self.team, view_name='+upcomingwork', no_login=True)
+            self.team, view_name="+upcomingwork", no_login=True
+        )
 
         # Check that the two work items and bugtasks created above are shown
         # and grouped under the appropriate milestone date.
-        groups = find_tags_by_class(browser.contents, 'workitems-group')
+        groups = find_tags_by_class(browser.contents, "workitems-group")
         self.assertEqual(2, len(groups))
         todays_group = extract_text(groups[0])
         tomorrows_group = extract_text(groups[1])
         self.assertStartsWith(
-            todays_group, 'Work items due in %s' % self.today)
+            todays_group, "Work items due in %s" % self.today
+        )
         self.assertIn(workitem1.title, todays_group)
         with anonymous_logged_in():
             self.assertIn(bugtask1.bug.title, todays_group)
 
         self.assertStartsWith(
-            tomorrows_group, 'Work items due in %s' % self.tomorrow)
+            tomorrows_group, "Work items due in %s" % self.tomorrow
+        )
         self.assertIn(workitem2.title, tomorrows_group)
         with anonymous_logged_in():
             self.assertIn(bugtask2.bug.title, tomorrows_group)
@@ -284,37 +315,47 @@ class TestPersonUpcomingWork(BrowserTestCase):
     def test_no_xss_on_workitem_title(self):
         self.factory.makeSpecificationWorkItem(
             title="<script>window.alert('XSS')</script>",
-            assignee=self.team.teamowner, milestone=self.today_milestone)
+            assignee=self.team.teamowner,
+            milestone=self.today_milestone,
+        )
 
         browser = self.getViewBrowser(
-            self.team, view_name='+upcomingwork', no_login=True)
+            self.team, view_name="+upcomingwork", no_login=True
+        )
 
-        groups = find_tags_by_class(browser.contents, 'collapsible-body')
+        groups = find_tags_by_class(browser.contents, "collapsible-body")
         self.assertEqual(1, len(groups))
         tbody = groups[0]
-        title_td = tbody.find_all('td')[0]
+        title_td = tbody.find_all("td")[0]
         self.assertEqual(
             "<td>\n<span>&lt;script&gt;window.alert('XSS')&lt;/script&gt;"
-            "</span>\n</td>", str(title_td))
+            "</span>\n</td>",
+            str(title_td),
+        )
 
     def test_overall_progressbar(self):
         """Check that the per-date progress bar is present."""
         # Create two work items on separate specs. One of them is done and the
         # other is in progress.
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.today_milestone,
-            status=SpecificationWorkItemStatus.DONE)
+            assignee=self.team.teamowner,
+            milestone=self.today_milestone,
+            status=SpecificationWorkItemStatus.DONE,
+        )
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.today_milestone,
-            status=SpecificationWorkItemStatus.INPROGRESS)
+            assignee=self.team.teamowner,
+            milestone=self.today_milestone,
+            status=SpecificationWorkItemStatus.INPROGRESS,
+        )
 
         browser = self.getViewBrowser(
-            self.team, view_name='+upcomingwork', no_login=True)
+            self.team, view_name="+upcomingwork", no_login=True
+        )
 
         # The progress bar for the due date of today_milestone will show that
         # 50% of the work is done (1 out of 2 work items).
-        progressbar = find_tag_by_id(browser.contents, 'progressbar_0')
-        self.assertEqual('50%', progressbar.get('width'))
+        progressbar = find_tag_by_id(browser.contents, "progressbar_0")
+        self.assertEqual("50%", progressbar.get("width"))
 
     def test_container_progressbar(self):
         """Check that the per-blueprint progress bar is present."""
@@ -322,65 +363,82 @@ class TestPersonUpcomingWork(BrowserTestCase):
         # other is in progress. Here we create the specs explicitly and in
         # order to force spec1 to show up first on the page.
         spec1 = self.factory.makeSpecification(
-            product=self.today_milestone.product)
+            product=self.today_milestone.product
+        )
         spec2 = self.factory.makeSpecification(
-            product=self.today_milestone.product)
+            product=self.today_milestone.product
+        )
         spec3 = self.factory.makeSpecification(
-            product=self.today_milestone.product)
+            product=self.today_milestone.product
+        )
         self.factory.makeSpecificationWorkItem(
-            specification=spec1, assignee=self.team.teamowner,
+            specification=spec1,
+            assignee=self.team.teamowner,
             milestone=self.today_milestone,
-            status=SpecificationWorkItemStatus.DONE)
+            status=SpecificationWorkItemStatus.DONE,
+        )
         self.factory.makeSpecificationWorkItem(
-            specification=spec2, assignee=self.team.teamowner,
+            specification=spec2,
+            assignee=self.team.teamowner,
             milestone=self.today_milestone,
-            status=SpecificationWorkItemStatus.INPROGRESS)
+            status=SpecificationWorkItemStatus.INPROGRESS,
+        )
         self.factory.makeSpecificationWorkItem(
-            specification=spec3, assignee=self.team.teamowner,
+            specification=spec3,
+            assignee=self.team.teamowner,
             milestone=self.today_milestone,
-            status=SpecificationWorkItemStatus.POSTPONED)
+            status=SpecificationWorkItemStatus.POSTPONED,
+        )
 
         browser = self.getViewBrowser(
-            self.team, view_name='+upcomingwork', no_login=True)
+            self.team, view_name="+upcomingwork", no_login=True
+        )
 
         # The progress bar of the first blueprint will be complete as the sole
         # work item there is done, while the other is going to be empty as the
         # sole work item is still in progress.
         container1_progressbar = find_tag_by_id(
-            browser.contents, 'container_progressbar_0')
+            browser.contents, "container_progressbar_0"
+        )
         container2_progressbar = find_tag_by_id(
-            browser.contents, 'container_progressbar_1')
+            browser.contents, "container_progressbar_1"
+        )
         container3_progressbar = find_tag_by_id(
-            browser.contents, 'container_progressbar_2')
-        self.assertEqual('100%', container1_progressbar.get('width'))
-        self.assertEqual('0%', container2_progressbar.get('width'))
-        self.assertEqual('100%', container3_progressbar.get('width'))
+            browser.contents, "container_progressbar_2"
+        )
+        self.assertEqual("100%", container1_progressbar.get("width"))
+        self.assertEqual("0%", container2_progressbar.get("width"))
+        self.assertEqual("100%", container3_progressbar.get("width"))
 
     def test_basic_for_person(self):
-        """Check that the page shows the bugs/work items assigned to a person.
-        """
+        """The page shows the bugs/work items assigned to a person."""
         person = self.factory.makePerson()
         workitem = self.factory.makeSpecificationWorkItem(
-            assignee=person, milestone=self.today_milestone)
+            assignee=person, milestone=self.today_milestone
+        )
         bugtask = self.factory.makeBug(
-            milestone=self.tomorrow_milestone).bugtasks[0]
+            milestone=self.tomorrow_milestone
+        ).bugtasks[0]
         removeSecurityProxy(bugtask).assignee = person
 
         browser = self.getViewBrowser(
-            person, view_name='+upcomingwork', no_login=True)
+            person, view_name="+upcomingwork", no_login=True
+        )
 
         # Check that the two work items created above are shown and grouped
         # under the appropriate milestone date.
-        groups = find_tags_by_class(browser.contents, 'workitems-group')
+        groups = find_tags_by_class(browser.contents, "workitems-group")
         self.assertEqual(2, len(groups))
         todays_group = extract_text(groups[0])
         tomorrows_group = extract_text(groups[1])
         self.assertStartsWith(
-            todays_group, 'Work items due in %s' % self.today)
+            todays_group, "Work items due in %s" % self.today
+        )
         self.assertIn(workitem.title, todays_group)
 
         self.assertStartsWith(
-            tomorrows_group, 'Work items due in %s' % self.tomorrow)
+            tomorrows_group, "Work items due in %s" % self.tomorrow
+        )
         with anonymous_logged_in():
             self.assertIn(bugtask.bug.title, tomorrows_group)
 
@@ -388,24 +446,31 @@ class TestPersonUpcomingWork(BrowserTestCase):
         """Work items for non-public specs are filtered correctly."""
         person = self.factory.makePerson()
         proprietary_spec = self.factory.makeSpecification(
-            information_type=InformationType.PROPRIETARY)
+            information_type=InformationType.PROPRIETARY
+        )
         product = removeSecurityProxy(proprietary_spec).product
         today_milestone = self.factory.makeMilestone(
-            dateexpected=self.today, product=product)
+            dateexpected=self.today, product=product
+        )
         public_workitem = self.factory.makeSpecificationWorkItem(
-            assignee=person, milestone=today_milestone)
+            assignee=person, milestone=today_milestone
+        )
         proprietary_workitem = self.factory.makeSpecificationWorkItem(
-            assignee=person, milestone=today_milestone,
-            specification=proprietary_spec)
-        browser = self.getViewBrowser(
-            person, view_name='+upcomingwork')
+            assignee=person,
+            milestone=today_milestone,
+            specification=proprietary_spec,
+        )
+        browser = self.getViewBrowser(person, view_name="+upcomingwork")
         self.assertIn(public_workitem.specification.name, browser.contents)
-        self.assertNotIn(proprietary_workitem.specification.name,
-                         browser.contents)
+        self.assertNotIn(
+            proprietary_workitem.specification.name, browser.contents
+        )
         browser = self.getViewBrowser(
-            person, view_name='+upcomingwork', user=product.owner)
-        self.assertIn(proprietary_workitem.specification.name,
-                      browser.contents)
+            person, view_name="+upcomingwork", user=product.owner
+        )
+        self.assertIn(
+            proprietary_workitem.specification.name, browser.contents
+        )
 
 
 class TestPersonUpcomingWorkView(TestCaseWithFactory):
@@ -417,65 +482,82 @@ class TestPersonUpcomingWorkView(TestCaseWithFactory):
         self.today = datetime.today().date()
         self.tomorrow = self.today + timedelta(days=1)
         self.today_milestone = self.factory.makeMilestone(
-            dateexpected=self.today)
+            dateexpected=self.today
+        )
         self.tomorrow_milestone = self.factory.makeMilestone(
-            dateexpected=self.tomorrow)
+            dateexpected=self.tomorrow
+        )
         self.team = self.factory.makeTeam()
 
     def test_workitem_counts(self):
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.today_milestone)
+            assignee=self.team.teamowner, milestone=self.today_milestone
+        )
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.today_milestone)
+            assignee=self.team.teamowner, milestone=self.today_milestone
+        )
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.tomorrow_milestone)
+            assignee=self.team.teamowner, milestone=self.tomorrow_milestone
+        )
 
-        view = create_initialized_view(self.team, '+upcomingwork')
+        view = create_initialized_view(self.team, "+upcomingwork")
         self.assertEqual(2, view.workitem_counts[self.today])
         self.assertEqual(1, view.workitem_counts[self.tomorrow])
 
     def test_bugtask_counts(self):
         bugtask1 = self.factory.makeBug(
-            milestone=self.today_milestone).bugtasks[0]
+            milestone=self.today_milestone
+        ).bugtasks[0]
         bugtask2 = self.factory.makeBug(
-            milestone=self.tomorrow_milestone).bugtasks[0]
+            milestone=self.tomorrow_milestone
+        ).bugtasks[0]
         bugtask3 = self.factory.makeBug(
-            milestone=self.tomorrow_milestone).bugtasks[0]
+            milestone=self.tomorrow_milestone
+        ).bugtasks[0]
         for bugtask in [bugtask1, bugtask2, bugtask3]:
             removeSecurityProxy(bugtask).assignee = self.team.teamowner
 
-        view = create_initialized_view(self.team, '+upcomingwork')
+        view = create_initialized_view(self.team, "+upcomingwork")
         self.assertEqual(1, view.bugtask_counts[self.today])
         self.assertEqual(2, view.bugtask_counts[self.tomorrow])
 
     def test_milestones_per_date(self):
         another_milestone_due_today = self.factory.makeMilestone(
-            dateexpected=self.today)
+            dateexpected=self.today
+        )
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.today_milestone)
+            assignee=self.team.teamowner, milestone=self.today_milestone
+        )
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner,
-            milestone=another_milestone_due_today)
+            assignee=self.team.teamowner, milestone=another_milestone_due_today
+        )
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.tomorrow_milestone)
+            assignee=self.team.teamowner, milestone=self.tomorrow_milestone
+        )
 
-        view = create_initialized_view(self.team, '+upcomingwork')
+        view = create_initialized_view(self.team, "+upcomingwork")
         self.assertEqual(
-            sorted([self.today_milestone, another_milestone_due_today],
-                   key=attrgetter('displayname')),
-            view.milestones_per_date[self.today])
+            sorted(
+                [self.today_milestone, another_milestone_due_today],
+                key=attrgetter("displayname"),
+            ),
+            view.milestones_per_date[self.today],
+        )
         self.assertEqual(
-            [self.tomorrow_milestone],
-            view.milestones_per_date[self.tomorrow])
+            [self.tomorrow_milestone], view.milestones_per_date[self.tomorrow]
+        )
 
     def test_work_item_containers_are_sorted_by_date(self):
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.today_milestone)
+            assignee=self.team.teamowner, milestone=self.today_milestone
+        )
         self.factory.makeSpecificationWorkItem(
-            assignee=self.team.teamowner, milestone=self.tomorrow_milestone)
+            assignee=self.team.teamowner, milestone=self.tomorrow_milestone
+        )
 
-        view = create_initialized_view(self.team, '+upcomingwork')
+        view = create_initialized_view(self.team, "+upcomingwork")
         self.assertEqual(2, len(view.work_item_containers))
         self.assertEqual(
             [self.today, self.tomorrow],
-            [date for date, containers in view.work_item_containers])
+            [date for date, containers in view.work_item_containers],
+        )
diff --git a/lib/lp/blueprints/browser/tests/test_specification.py b/lib/lp/blueprints/browser/tests/test_specification.py
index 655c5d1..1405a16 100644
--- a/lib/lp/blueprints/browser/tests/test_specification.py
+++ b/lib/lp/blueprints/browser/tests/test_specification.py
@@ -1,21 +1,18 @@
 # Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from datetime import datetime
 import json
 import re
 import unittest
+from datetime import datetime
 
-from fixtures import FakeLogger
-from lazr.restful.interfaces import IJSONRequestCache
 import pytz
 import soupmatchers
-from testtools.matchers import (
-    Equals,
-    Not,
-    )
-from testtools.testcase import ExpectedException
 import transaction
+from fixtures import FakeLogger
+from lazr.restful.interfaces import IJSONRequestCache
+from testtools.matchers import Equals, Not
+from testtools.testcase import ExpectedException
 from zope.component import getUtility
 from zope.publisher.interfaces import NotFound
 from zope.security.interfaces import Unauthorized
@@ -29,7 +26,7 @@ from lp.blueprints.enums import SpecificationImplementationStatus
 from lp.blueprints.interfaces.specification import (
     ISpecification,
     ISpecificationSet,
-    )
+)
 from lp.registry.enums import SpecificationSharingPolicy
 from lp.registry.interfaces.person import PersonVisibility
 from lp.registry.interfaces.product import IProductSeries
@@ -41,23 +38,20 @@ from lp.services.webapp.publisher import canonical_url
 from lp.testing import (
     BrowserTestCase,
     FakeLaunchpadRequest,
+    TestCaseWithFactory,
     login_celebrity,
     login_person,
     logout,
     person_logged_in,
-    TestCaseWithFactory,
-    )
+)
 from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.matchers import (
-    BrowsesWithQueryLimit,
-    DocTestMatches,
-    )
+from lp.testing.matchers import BrowsesWithQueryLimit, DocTestMatches
 from lp.testing.pages import (
     extract_text,
     find_tag_by_id,
     setupBrowser,
     setupBrowserForUser,
-    )
+)
 from lp.testing.views import create_initialized_view
 
 
@@ -68,8 +62,8 @@ class TestSpecificationSearch(TestCaseWithFactory):
     def test_search_with_percent(self):
         # Using '%' in a search should not error.
         specs = getUtility(ISpecificationSet)
-        form = {'field.search_text': r'%'}
-        view = create_initialized_view(specs, '+index', form=form)
+        form = {"field.search_text": r"%"}
+        view = create_initialized_view(specs, "+index", form=form)
         self.assertEqual([], view.errors)
 
 
@@ -89,59 +83,67 @@ class TestBranchTraversal(TestCaseWithFactory):
         self.specification.linkBranch(branch, self.factory.makePerson())
 
     def traverse(self, segments):
-        stack = list(reversed(['+branch'] + segments))
+        stack = list(reversed(["+branch"] + segments))
         name = stack.pop()
         request = FakeLaunchpadRequest([], stack)
         traverser = specification.SpecificationNavigation(
-            self.specification, request)
+            self.specification, request
+        )
         return traverser.publishTraverse(request, name)
 
     def test_junk_branch(self):
         branch = self.factory.makePersonalBranch()
         self.linkBranch(branch)
-        segments = [branch.owner.name, '+junk', branch.name]
+        segments = [branch.owner.name, "+junk", branch.name]
         self.assertEqual(
-            self.specification.getBranchLink(branch), self.traverse(segments))
+            self.specification.getBranchLink(branch), self.traverse(segments)
+        )
 
     def test_junk_branch_no_such_person(self):
         person_name = self.factory.getUniqueString()
         branch_name = self.factory.getUniqueString()
         self.assertRaises(
-            NotFound, self.traverse, [person_name, '+junk', branch_name])
+            NotFound, self.traverse, [person_name, "+junk", branch_name]
+        )
 
     def test_junk_branch_no_such_branch(self):
         person = self.factory.makePerson()
         branch_name = self.factory.getUniqueString()
         self.assertRaises(
-            NotFound, self.traverse, [person.name, '+junk', branch_name])
+            NotFound, self.traverse, [person.name, "+junk", branch_name]
+        )
 
     def test_product_branch(self):
         branch = self.factory.makeProductBranch()
         self.linkBranch(branch)
         segments = [branch.owner.name, branch.product.name, branch.name]
         self.assertEqual(
-            self.specification.getBranchLink(branch), self.traverse(segments))
+            self.specification.getBranchLink(branch), self.traverse(segments)
+        )
 
     def test_product_branch_no_such_person(self):
         person_name = self.factory.getUniqueString()
         product_name = self.factory.getUniqueString()
         branch_name = self.factory.getUniqueString()
         self.assertRaises(
-            NotFound, self.traverse, [person_name, product_name, branch_name])
+            NotFound, self.traverse, [person_name, product_name, branch_name]
+        )
 
     def test_product_branch_no_such_product(self):
         person = self.factory.makePerson()
         product_name = self.factory.getUniqueString()
         branch_name = self.factory.getUniqueString()
         self.assertRaises(
-            NotFound, self.traverse, [person.name, product_name, branch_name])
+            NotFound, self.traverse, [person.name, product_name, branch_name]
+        )
 
     def test_product_branch_no_such_branch(self):
         person = self.factory.makePerson()
         product = self.factory.makeProduct()
         branch_name = self.factory.getUniqueString()
         self.assertRaises(
-            NotFound, self.traverse, [person.name, product.name, branch_name])
+            NotFound, self.traverse, [person.name, product.name, branch_name]
+        )
 
     def test_package_branch(self):
         branch = self.factory.makePackageBranch()
@@ -151,9 +153,11 @@ class TestBranchTraversal(TestCaseWithFactory):
             branch.distribution.name,
             branch.distroseries.name,
             branch.sourcepackagename.name,
-            branch.name]
+            branch.name,
+        ]
         self.assertEqual(
-            self.specification.getBranchLink(branch), self.traverse(segments))
+            self.specification.getBranchLink(branch), self.traverse(segments)
+        )
 
 
 class TestSpecificationView(BrowserTestCase):
@@ -165,23 +169,24 @@ class TestSpecificationView(BrowserTestCase):
         """The specification URL is rendered when present."""
         spec = self.factory.makeSpecification()
         login_person(spec.owner)
-        spec.specurl = 'http://eg.dom/parrot'
+        spec.specurl = "http://eg.dom/parrot";
         view = create_initialized_view(
-            spec, name='+index', principal=spec.owner,
-            rootsite='blueprints')
-        li = find_tag_by_id(view.render(), 'spec-url')
-        self.assertEqual(['nofollow'], li.a['rel'])
-        self.assertEqual(spec.specurl, li.a['href'])
+            spec, name="+index", principal=spec.owner, rootsite="blueprints"
+        )
+        li = find_tag_by_id(view.render(), "spec-url")
+        self.assertEqual(["nofollow"], li.a["rel"])
+        self.assertEqual(spec.specurl, li.a["href"])
 
     def test_registration_date_displayed(self):
         """The time frame does not prepend on incorrectly."""
         spec = self.factory.makeSpecification(
-            owner=self.factory.makePerson(displayname="Some Person"))
-        html = create_initialized_view(
-                spec, '+index')()
+            owner=self.factory.makePerson(displayname="Some Person")
+        )
+        html = create_initialized_view(spec, "+index")()
         self.assertThat(
-            extract_text(html), DocTestMatches(
-                "... Registered by Some Person ... ago ..."))
+            extract_text(html),
+            DocTestMatches("... Registered by Some Person ... ago ..."),
+        )
 
     def test_private_specification_without_authorization(self):
         # Users without access get a 404 when trying to view private
@@ -189,12 +194,15 @@ class TestSpecificationView(BrowserTestCase):
         self.useFixture(FakeLogger())
         owner = self.factory.makePerson()
         policy = SpecificationSharingPolicy.PROPRIETARY
-        product = self.factory.makeProduct(owner=owner,
-            specification_sharing_policy=policy)
+        product = self.factory.makeProduct(
+            owner=owner, specification_sharing_policy=policy
+        )
         with person_logged_in(owner):
             spec = self.factory.makeSpecification(
-                product=product, owner=owner,
-                information_type=InformationType.PROPRIETARY)
+                product=product,
+                owner=owner,
+                information_type=InformationType.PROPRIETARY,
+            )
             url = canonical_url(spec)
         self.assertRaises(NotFound, self.getUserBrowser, url=url, user=None)
 
@@ -204,12 +212,14 @@ class TestSpecificationView(BrowserTestCase):
         owner = self.factory.makePerson()
         user = self.factory.makePerson()
         product = self.factory.makeProduct(
-            owner=owner,
-            information_type=InformationType.PROPRIETARY)
+            owner=owner, information_type=InformationType.PROPRIETARY
+        )
         with person_logged_in(owner):
             spec = self.factory.makeSpecification(
-                product=product, owner=owner,
-                information_type=InformationType.PROPRIETARY)
+                product=product,
+                owner=owner,
+                information_type=InformationType.PROPRIETARY,
+            )
             spec.subscribe(user, subscribed_by=owner)
             spec_name = spec.name
         # Creating the view does not raise any exceptions.
@@ -225,30 +235,31 @@ class TestSpecificationSet(BrowserTestCase):
         """Blueprints home page tolerates proprietary Specifications."""
         specs = getUtility(ISpecificationSet)
         policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
-        product = self.factory.makeProduct(
-            specification_sharing_policy=policy)
+        product = self.factory.makeProduct(specification_sharing_policy=policy)
         spec = self.factory.makeSpecification(product=product)
         spec_name = spec.name
         spec_owner = spec.owner
         browser = self.getViewBrowser(specs)
-        self.assertNotIn('Not allowed', browser.contents)
+        self.assertNotIn("Not allowed", browser.contents)
         self.assertIn(spec_name, browser.contents)
         with person_logged_in(spec_owner):
             removeSecurityProxy(spec.target)._ensurePolicies(
-                [InformationType.PROPRIETARY])
+                [InformationType.PROPRIETARY]
+            )
             spec.transitionToInformationType(
-                InformationType.PROPRIETARY, spec.owner)
+                InformationType.PROPRIETARY, spec.owner
+            )
         browser = self.getViewBrowser(specs)
-        self.assertNotIn('Not allowed', browser.contents)
+        self.assertNotIn("Not allowed", browser.contents)
         self.assertNotIn(spec_name, browser.contents)
 
     def test_query_count(self):
         product = self.factory.makeProduct()
         removeSecurityProxy(product).official_blueprints = True
         self.factory.makeSpecification(product=product)
-        limit = BrowsesWithQueryLimit(30, product.owner, rootsite='blueprints')
+        limit = BrowsesWithQueryLimit(30, product.owner, rootsite="blueprints")
         self.assertThat(product, limit)
-        login_celebrity('admin')
+        login_celebrity("admin")
         [self.factory.makeSpecification(product=product) for i in range(4)]
         self.assertThat(product, limit)
 
@@ -258,7 +269,8 @@ class TestSpecificationInformationType(BrowserTestCase):
     layer = DatabaseFunctionalLayer
 
     portlet_tag = soupmatchers.Tag(
-        'info-type-portlet', True, attrs=dict(id='information-type-summary'))
+        "info-type-portlet", True, attrs=dict(id="information-type-summary")
+    )
 
     def assertBrowserMatches(self, matcher):
         browser = self.getViewBrowser(self.factory.makeSpecification())
@@ -270,33 +282,42 @@ class TestSpecificationInformationType(BrowserTestCase):
     def test_has_privacy_banner(self):
         owner = self.factory.makePerson()
         policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
-        target = self.factory.makeProduct(
-            specification_sharing_policy=policy)
+        target = self.factory.makeProduct(specification_sharing_policy=policy)
         removeSecurityProxy(target)._ensurePolicies(
-            [InformationType.PROPRIETARY])
+            [InformationType.PROPRIETARY]
+        )
         spec = self.factory.makeSpecification(
-            information_type=InformationType.PROPRIETARY, owner=owner,
-            product=target)
+            information_type=InformationType.PROPRIETARY,
+            owner=owner,
+            product=target,
+        )
         with person_logged_in(target.owner):
-            getUtility(IService, 'sharing').ensureAccessGrants(
-                [owner], target.owner, specifications=[spec])
+            getUtility(IService, "sharing").ensureAccessGrants(
+                [owner], target.owner, specifications=[spec]
+            )
         with person_logged_in(owner):
             browser = self.getViewBrowser(spec, user=owner)
-        privacy_banner = soupmatchers.Tag('privacy-banner', True,
-                attrs={'class': 'private_banner_container'})
+        privacy_banner = soupmatchers.Tag(
+            "privacy-banner", True, attrs={"class": "private_banner_container"}
+        )
         self.assertThat(
-            browser.contents, soupmatchers.HTMLContains(privacy_banner))
+            browser.contents, soupmatchers.HTMLContains(privacy_banner)
+        )
 
-    def set_secrecy(self, spec, owner, information_type='PROPRIETARY'):
+    def set_secrecy(self, spec, owner, information_type="PROPRIETARY"):
         form = {
-            'field.actions.change': 'Change',
-            'field.information_type': information_type,
-            'field.validate_change': 'off',
+            "field.actions.change": "Change",
+            "field.information_type": information_type,
+            "field.validate_change": "off",
         }
         with person_logged_in(owner):
             view = create_initialized_view(
-                spec, '+secrecy', form, principal=owner,
-                HTTP_X_REQUESTED_WITH='XMLHttpRequest')
+                spec,
+                "+secrecy",
+                form,
+                principal=owner,
+                HTTP_X_REQUESTED_WITH="XMLHttpRequest",
+            )
             body = view.render()
         return view.request.response.getStatus(), body
 
@@ -304,15 +325,16 @@ class TestSpecificationInformationType(BrowserTestCase):
         """Setting the value via '+secrecy' works."""
         owner = self.factory.makePerson()
         policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
-        product = self.factory.makeProduct(
-            specification_sharing_policy=policy)
+        product = self.factory.makeProduct(specification_sharing_policy=policy)
         spec = self.factory.makeSpecification(owner=owner, product=product)
         removeSecurityProxy(spec.target)._ensurePolicies(
-            [InformationType.PROPRIETARY])
+            [InformationType.PROPRIETARY]
+        )
         self.set_secrecy(spec, owner)
         with person_logged_in(owner):
             self.assertEqual(
-                InformationType.PROPRIETARY, spec.information_type)
+                InformationType.PROPRIETARY, spec.information_type
+            )
 
     def test_secrecy_change_nonsense(self):
         """Invalid values produce sane errors."""
@@ -320,18 +342,19 @@ class TestSpecificationInformationType(BrowserTestCase):
         spec = self.factory.makeSpecification(owner=owner)
         transaction.commit()
         status, body = self.set_secrecy(
-            spec, owner, information_type=self.factory.getUniqueString())
+            spec, owner, information_type=self.factory.getUniqueString()
+        )
         self.assertEqual(400, status)
         error_data = json.loads(body)
-        self.assertEqual({'field.information_type': 'Invalid value'},
-                         error_data['errors'])
+        self.assertEqual(
+            {"field.information_type": "Invalid value"}, error_data["errors"]
+        )
         self.assertEqual(InformationType.PUBLIC, spec.information_type)
 
     def test_secrecy_change_unprivileged(self):
         """Unprivileged users cannot change information_type."""
         policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
-        product = self.factory.makeProduct(
-            specification_sharing_policy=policy)
+        product = self.factory.makeProduct(specification_sharing_policy=policy)
         spec = self.factory.makeSpecification(product=product)
         person = self.factory.makePerson()
         with ExpectedException(Unauthorized):
@@ -343,57 +366,65 @@ class TestSpecificationInformationType(BrowserTestCase):
         owner = self.factory.makePerson()
         policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         product = self.factory.makeProduct(
-            owner=owner,
-            specification_sharing_policy=policy)
+            owner=owner, specification_sharing_policy=policy
+        )
         spec = self.factory.makeSpecification(
-            information_type=InformationType.PROPRIETARY, owner=owner,
-            product=product)
+            information_type=InformationType.PROPRIETARY,
+            owner=owner,
+            product=product,
+        )
 
-        privacy_banner = soupmatchers.Tag('privacy-banner', True,
-                text=re.compile('The information on this page is private'))
+        privacy_banner = soupmatchers.Tag(
+            "privacy-banner",
+            True,
+            text=re.compile("The information on this page is private"),
+        )
 
-        getUtility(IService, 'sharing').ensureAccessGrants(
-              [owner], owner, specifications=[spec],
-              ignore_permissions=True)
+        getUtility(IService, "sharing").ensureAccessGrants(
+            [owner], owner, specifications=[spec], ignore_permissions=True
+        )
 
-        browser = self.getViewBrowser(spec, '+index', user=owner)
+        browser = self.getViewBrowser(spec, "+index", user=owner)
         self.assertThat(
-            browser.contents, soupmatchers.HTMLContains(privacy_banner))
-        browser = self.getViewBrowser(spec, '+subscribe', user=owner)
+            browser.contents, soupmatchers.HTMLContains(privacy_banner)
+        )
+        browser = self.getViewBrowser(spec, "+subscribe", user=owner)
         self.assertThat(
-            browser.contents, soupmatchers.HTMLContains(privacy_banner))
+            browser.contents, soupmatchers.HTMLContains(privacy_banner)
+        )
 
 
 # canonical_url erroneously returns http://blueprints.launchpad.test/+new
-NEW_SPEC_FROM_ROOT_URL = 'http://blueprints.launchpad.test/specs/+new'
+NEW_SPEC_FROM_ROOT_URL = "http://blueprints.launchpad.test/specs/+new";
 
 
 class NewSpecificationTests:
 
-    expected_keys = {'PROPRIETARY', 'PUBLIC', 'EMBARGOED'}
+    expected_keys = {"PROPRIETARY", "PUBLIC", "EMBARGOED"}
 
     def _create_form_data(self, context):
         return {
-            'field.actions.register': 'Register Blueprint',
-            'field.definition_status': 'NEW',
-            'field.target': context,
-            'field.name': 'TestBlueprint',
-            'field.title': 'Test Blueprint',
-            'field.summary': 'Test Blueprint Summary',
+            "field.actions.register": "Register Blueprint",
+            "field.definition_status": "NEW",
+            "field.target": context,
+            "field.name": "TestBlueprint",
+            "field.title": "Test Blueprint",
+            "field.summary": "Test Blueprint Summary",
         }
 
     def _assert_information_type_validation_error(self, context, form, owner):
         """Helper to check for invalid information type on submit."""
         with person_logged_in(owner):
-            view = create_initialized_view(context, '+addspec', form=form)
-            expected = ('This information type is not permitted for'
-                        ' this product')
+            view = create_initialized_view(context, "+addspec", form=form)
+            expected = (
+                "This information type is not permitted for" " this product"
+            )
             self.assertIn(expected, view.errors)
 
     def test_cache_contains_information_type(self):
         view = self.createInitializedView()
         cache = IJSONRequestCache(view.request)
-        info_data = cache.objects.get('information_type_data')
+        info_data = cache.objects.get("information_type_data")
         self.assertIsNot(None, info_data)
         self.assertEqual(self.expected_keys, set(info_data.keys()))
 
@@ -402,104 +433,112 @@ class NewSpecificationTests:
         # specifications.
         view = self.createInitializedView()
         self.assertEqual(
-            InformationType.PUBLIC, view.initial_values['information_type'])
+            InformationType.PUBLIC, view.initial_values["information_type"]
+        )
 
     def test_default_drafter_is_user(self):
         drafter = self.factory.makePerson()
         with person_logged_in(drafter):
             view = self.createInitializedView()
-            self.assertEqual(drafter, view.widgets['drafter']._getFormValue())
+            self.assertEqual(drafter, view.widgets["drafter"]._getFormValue())
 
 
-class TestNewSpecificationFromRootView(TestCaseWithFactory,
-                                       NewSpecificationTests):
+class TestNewSpecificationFromRootView(
+    TestCaseWithFactory, NewSpecificationTests
+):
 
     layer = DatabaseFunctionalLayer
 
     def createInitializedView(self):
         context = getUtility(ISpecificationSet)
-        return create_initialized_view(context, '+new')
+        return create_initialized_view(context, "+new")
 
     def test_allowed_info_type_validated(self):
         """information_type must be validated against context"""
         context = getUtility(ISpecificationSet)
         product = self.factory.makeProduct()
         form = self._create_form_data(product.name)
-        form['field.information_type'] = 'PROPRIETARY'
-        view = create_initialized_view(context, '+new', form=form)
-        expected = 'This information type is not permitted for this product'
+        form["field.information_type"] = "PROPRIETARY"
+        view = create_initialized_view(context, "+new", form=form)
+        expected = "This information type is not permitted for this product"
         self.assertIn(expected, view.errors)
 
 
-class TestNewSpecificationFromSprintView(TestCaseWithFactory,
-                                         NewSpecificationTests):
+class TestNewSpecificationFromSprintView(
+    TestCaseWithFactory, NewSpecificationTests
+):
 
     layer = DatabaseFunctionalLayer
 
     def createInitializedView(self):
         sprint = self.factory.makeSprint()
-        return create_initialized_view(sprint, '+addspec')
+        return create_initialized_view(sprint, "+addspec")
 
     def test_allowed_info_type_validated(self):
         """information_type must be validated against context"""
         sprint = self.factory.makeSprint()
         product = self.factory.makeProduct(owner=sprint.owner)
         form = self._create_form_data(product.name)
-        form['field.information_type'] = 'PROPRIETARY'
+        form["field.information_type"] = "PROPRIETARY"
         self._assert_information_type_validation_error(
-            sprint, form, sprint.owner)
+            sprint, form, sprint.owner
+        )
 
 
-class TestNewSpecificationFromProjectGroupView(TestCaseWithFactory,
-                                               NewSpecificationTests):
+class TestNewSpecificationFromProjectGroupView(
+    TestCaseWithFactory, NewSpecificationTests
+):
 
     layer = DatabaseFunctionalLayer
 
     def createInitializedView(self):
         projectgroup = self.factory.makeProject()
-        return create_initialized_view(projectgroup, '+addspec')
+        return create_initialized_view(projectgroup, "+addspec")
 
     def test_allowed_info_type_validated(self):
         """information_type must be validated against context"""
         projectgroup = self.factory.makeProject()
         product = self.factory.makeProduct(projectgroup=projectgroup)
         form = self._create_form_data(product.name)
-        form['field.information_type'] = 'PROPRIETARY'
+        form["field.information_type"] = "PROPRIETARY"
         self._assert_information_type_validation_error(
-            projectgroup, form, projectgroup.owner)
+            projectgroup, form, projectgroup.owner
+        )
 
 
-class TestNewSpecificationFromProductView(TestCaseWithFactory,
-                                          NewSpecificationTests):
+class TestNewSpecificationFromProductView(
+    TestCaseWithFactory, NewSpecificationTests
+):
 
     layer = DatabaseFunctionalLayer
 
-    expected_keys = {'PROPRIETARY', 'EMBARGOED'}
+    expected_keys = {"PROPRIETARY", "EMBARGOED"}
 
     def createInitializedView(self):
         policy = SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY
-        product = self.factory.makeProduct(
-            specification_sharing_policy=policy)
-        return create_initialized_view(product, '+addspec')
+        product = self.factory.makeProduct(specification_sharing_policy=policy)
+        return create_initialized_view(product, "+addspec")
 
     def test_default_info_type(self):
         # In this case the default info type cannot be PUBlIC as it's not
         # among the allowed types.
         view = self.createInitializedView()
         self.assertEqual(
-            InformationType.EMBARGOED, view.initial_values['information_type'])
+            InformationType.EMBARGOED, view.initial_values["information_type"]
+        )
 
 
-class TestNewSpecificationFromDistributionView(TestCaseWithFactory,
-                                               NewSpecificationTests):
+class TestNewSpecificationFromDistributionView(
+    TestCaseWithFactory, NewSpecificationTests
+):
 
     layer = DatabaseFunctionalLayer
 
-    expected_keys = {'PUBLIC'}
+    expected_keys = {"PUBLIC"}
 
     def createInitializedView(self):
         distro = self.factory.makeDistribution()
-        return create_initialized_view(distro, '+addspec')
+        return create_initialized_view(distro, "+addspec")
 
 
 class TestNewSpecificationInformationType(BrowserTestCase):
@@ -509,7 +548,8 @@ class TestNewSpecificationInformationType(BrowserTestCase):
     def setUp(self):
         super().setUp()
         it_field = soupmatchers.Tag(
-            'it-field', True, attrs=dict(name='field.information_type'))
+            "it-field", True, attrs=dict(name="field.information_type")
+        )
         self.match_it = soupmatchers.HTMLContains(it_field)
 
     def test_from_root(self):
@@ -520,16 +560,16 @@ class TestNewSpecificationInformationType(BrowserTestCase):
     def test_from_sprint(self):
         """Information_type is included creating from a sprint."""
         sprint = self.factory.makeSprint()
-        browser = self.getViewBrowser(sprint, view_name='+addspec')
+        browser = self.getViewBrowser(sprint, view_name="+addspec")
         self.assertThat(browser.contents, self.match_it)
 
     def submitSpec(self, browser):
         """Submit a Specification via a browser."""
         name = self.factory.getUniqueString()
-        browser.getControl('Name').value = name
-        browser.getControl('Title').value = self.factory.getUniqueString()
-        browser.getControl('Summary').value = self.factory.getUniqueString()
-        browser.getControl('Register Blueprint').click()
+        browser.getControl("Name").value = name
+        browser.getControl("Title").value = self.factory.getUniqueString()
+        browser.getControl("Summary").value = self.factory.getUniqueString()
+        browser.getControl("Register Blueprint").click()
         return name
 
     def createSpec(self, information_type, sharing_policy=None):
@@ -539,7 +579,7 @@ class TestNewSpecificationInformationType(BrowserTestCase):
             if sharing_policy is not None:
                 self.factory.makeCommercialSubscription(product)
                 product.setSpecificationSharingPolicy(sharing_policy)
-            browser = self.getViewBrowser(product, view_name='+addspec')
+            browser = self.getViewBrowser(product, view_name="+addspec")
             control = browser.getControl(information_type.title)
             if not control.selected:
                 control.click()
@@ -553,36 +593,38 @@ class TestNewSpecificationInformationType(BrowserTestCase):
         """Creating honours information types."""
         spec = self.createSpec(
             InformationType.PUBLIC,
-            sharing_policy=SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY)
+            sharing_policy=SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY,
+        )
         self.assertEqual(InformationType.PUBLIC, spec.information_type)
         spec = self.createSpec(
             InformationType.PROPRIETARY,
-            sharing_policy=SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY)
+            sharing_policy=SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY,
+        )
         self.assertEqual(InformationType.PROPRIETARY, spec.information_type)
         spec = self.createSpec(
             InformationType.EMBARGOED,
-            SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY)
+            SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY,
+        )
         self.assertEqual(InformationType.EMBARGOED, spec.information_type)
 
     def test_from_productseries(self):
         """Information_type is included creating from productseries."""
         policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
-        product = self.factory.makeProduct(
-            specification_sharing_policy=policy)
+        product = self.factory.makeProduct(specification_sharing_policy=policy)
         series = self.factory.makeProductSeries(product=product)
-        browser = self.getViewBrowser(series, view_name='+addspec')
+        browser = self.getViewBrowser(series, view_name="+addspec")
         self.assertThat(browser.contents, self.match_it)
 
     def test_from_distribution(self):
         """information_type is excluded creating from distro."""
         distro = self.factory.makeDistribution()
-        browser = self.getViewBrowser(distro, view_name='+addspec')
+        browser = self.getViewBrowser(distro, view_name="+addspec")
         self.assertThat(browser.contents, Not(self.match_it))
 
     def test_from_distroseries(self):
         """information_type is excluded creating from distroseries."""
         series = self.factory.makeDistroSeries()
-        browser = self.getViewBrowser(series, view_name='+addspec')
+        browser = self.getViewBrowser(series, view_name="+addspec")
         self.assertThat(browser.contents, Not(self.match_it))
 
 
@@ -592,11 +634,12 @@ class BaseNewSpecificationInformationTypeDefaultMixin:
 
     def _setUp(self):
         it_field = soupmatchers.Tag(
-            'it-field', True, attrs=dict(name='field.information_type'))
+            "it-field", True, attrs=dict(name="field.information_type")
+        )
         self.match_it = soupmatchers.HTMLContains(it_field)
 
     def makeTarget(self, policy, owner=None):
-        raise NotImplementedError('makeTarget')
+        raise NotImplementedError("makeTarget")
 
     def ensurePolicy(self, target, information_type):
         """Helper to call _ensurePolicies
@@ -623,17 +666,17 @@ class BaseNewSpecificationInformationTypeDefaultMixin:
     def submitSpec(self, browser):
         """Submit a Specification via a browser."""
         name = self.factory.getUniqueString()
-        browser.getControl('Name').value = name
-        browser.getControl('Title').value = self.factory.getUniqueString()
-        browser.getControl('Summary').value = self.factory.getUniqueString()
-        browser.getControl('Register Blueprint').click()
+        browser.getControl("Name").value = name
+        browser.getControl("Title").value = self.factory.getUniqueString()
+        browser.getControl("Summary").value = self.factory.getUniqueString()
+        browser.getControl("Register Blueprint").click()
         return name
 
     def test_public(self):
         """Creating from PUBLIC policy allows only PUBLIC."""
         policy = SpecificationSharingPolicy.PUBLIC
         target = self.makeTarget(policy)
-        browser = self.getViewBrowser(target, view_name='+addspec')
+        browser = self.getViewBrowser(target, view_name="+addspec")
         self.assertThat(browser.contents, Not(self.match_it))
         spec = self.getSpecification(target, self.submitSpec(browser))
         self.assertEqual(spec.information_type, InformationType.PUBLIC)
@@ -642,7 +685,7 @@ class BaseNewSpecificationInformationTypeDefaultMixin:
         """Creating from PUBLIC_OR_PROPRIETARY defaults to PUBLIC."""
         policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         target = self.makeTarget(policy)
-        browser = self.getViewBrowser(target, view_name='+addspec')
+        browser = self.getViewBrowser(target, view_name="+addspec")
         self.assertThat(browser.contents, self.match_it)
         spec = self.getSpecification(target, self.submitSpec(browser))
         self.assertEqual(spec.information_type, InformationType.PUBLIC)
@@ -653,8 +696,7 @@ class BaseNewSpecificationInformationTypeDefaultMixin:
         owner = self.factory.makePerson()
         target = self.makeTarget(policy, owner=owner)
         self.ensurePolicy(target, [InformationType.PROPRIETARY])
-        browser = self.getViewBrowser(
-            target, view_name='+addspec', user=owner)
+        browser = self.getViewBrowser(target, view_name="+addspec", user=owner)
         self.assertThat(browser.contents, self.match_it)
         spec = self.getSpecification(target, self.submitSpec(browser))
         self.assertEqual(spec.information_type, InformationType.PROPRIETARY)
@@ -665,8 +707,7 @@ class BaseNewSpecificationInformationTypeDefaultMixin:
         owner = self.factory.makePerson()
         target = self.makeTarget(policy, owner=owner)
         self.ensurePolicy(target, [InformationType.PROPRIETARY])
-        browser = self.getViewBrowser(
-            target, view_name='+addspec', user=owner)
+        browser = self.getViewBrowser(target, view_name="+addspec", user=owner)
         self.assertThat(browser.contents, Not(self.match_it))
         spec = self.getSpecification(target, self.submitSpec(browser))
         self.assertEqual(spec.information_type, InformationType.PROPRIETARY)
@@ -677,38 +718,39 @@ class BaseNewSpecificationInformationTypeDefaultMixin:
         owner = self.factory.makePerson()
         target = self.makeTarget(policy, owner=owner)
         self.ensurePolicy(target, [InformationType.EMBARGOED])
-        browser = self.getViewBrowser(
-            target, view_name='+addspec', user=owner)
+        browser = self.getViewBrowser(target, view_name="+addspec", user=owner)
         self.assertThat(browser.contents, self.match_it)
         spec = self.getSpecification(target, self.submitSpec(browser))
         self.assertEqual(spec.information_type, InformationType.EMBARGOED)
 
 
 class TestNewSpecificationDefaultInformationTypeProduct(
-    BrowserTestCase, BaseNewSpecificationInformationTypeDefaultMixin):
-
+    BrowserTestCase, BaseNewSpecificationInformationTypeDefaultMixin
+):
     def makeTarget(self, policy, owner=None):
         self._setUp()
         if owner is None:
             owner = self.factory.makePerson()
         return self.factory.makeProduct(
-            owner=owner, specification_sharing_policy=policy)
+            owner=owner, specification_sharing_policy=policy
+        )
 
 
 class TestNewSpecificationDefaultInformationTypeProductSeries(
-    BrowserTestCase, BaseNewSpecificationInformationTypeDefaultMixin):
-
+    BrowserTestCase, BaseNewSpecificationInformationTypeDefaultMixin
+):
     def makeTarget(self, policy, owner=None):
         self._setUp()
         if owner is None:
             owner = self.factory.makePerson()
         product = self.factory.makeProduct(
-            owner=owner, specification_sharing_policy=policy)
+            owner=owner, specification_sharing_policy=policy
+        )
         return self.factory.makeProductSeries(product=product)
 
 
 class TestSpecificationViewPrivateArtifacts(BrowserTestCase):
-    """ Tests that specifications with private team artifacts can be viewed.
+    """Tests that specifications with private team artifacts can be viewed.
 
     A Specification may be associated with a private team as follows:
     - a subscriber is a private team
@@ -732,37 +774,38 @@ class TestSpecificationViewPrivateArtifacts(BrowserTestCase):
     def test_view_specification_with_private_subscriber(self):
         # A specification with a private subscriber is rendered.
         private_subscriber = self.factory.makeTeam(
-            name="privateteam",
-            visibility=PersonVisibility.PRIVATE)
+            name="privateteam", visibility=PersonVisibility.PRIVATE
+        )
         spec = self.factory.makeSpecification()
         with person_logged_in(spec.owner):
             spec.subscribe(private_subscriber, spec.owner)
             # Ensure the specification subscriber is rendered.
-            url = canonical_url(spec, rootsite='blueprints')
+            url = canonical_url(spec, rootsite="blueprints")
             user = self.factory.makePerson()
             browser = self._getBrowser(user)
             browser.open(url)
             soup = BeautifulSoup(browser.contents)
-            subscriber_portlet = soup.find(
-                'div', attrs={'id': 'subscribers'})
+            subscriber_portlet = soup.find("div", attrs={"id": "subscribers"})
             self.assertIsNotNone(
-                subscriber_portlet.find('a', text='Privateteam'))
+                subscriber_portlet.find("a", text="Privateteam")
+            )
 
     def test_anonymous_view_specification_with_private_subscriber(self):
         # A specification with a private subscriber is not rendered for anon.
         private_subscriber = self.factory.makeTeam(
-            name="privateteam",
-            visibility=PersonVisibility.PRIVATE)
+            name="privateteam", visibility=PersonVisibility.PRIVATE
+        )
         spec = self.factory.makeSpecification()
         with person_logged_in(spec.owner):
             spec.subscribe(private_subscriber, spec.owner)
             # Viewing the specification doesn't display private subscriber.
-            url = canonical_url(spec, rootsite='blueprints')
+            url = canonical_url(spec, rootsite="blueprints")
             browser = self._getBrowser()
             browser.open(url)
             soup = BeautifulSoup(browser.contents)
             self.assertIsNone(
-                soup.find('div', attrs={'id': 'subscriber-privateteam'}))
+                soup.find("div", attrs={"id": "subscriber-privateteam"})
+            )
 
 
 class TestSpecificationEditStatusView(TestCaseWithFactory):
@@ -773,78 +816,88 @@ class TestSpecificationEditStatusView(TestCaseWithFactory):
     def test_records_started(self):
         not_started = SpecificationImplementationStatus.NOTSTARTED
         spec = self.factory.makeSpecification(
-            implementation_status=not_started)
+            implementation_status=not_started
+        )
         login_person(spec.owner)
         form = {
-            'field.implementation_status': 'STARTED',
-            'field.actions.change': 'Change',
-            }
-        view = create_initialized_view(spec, name='+status', form=form)
+            "field.implementation_status": "STARTED",
+            "field.actions.change": "Change",
+        }
+        view = create_initialized_view(spec, name="+status", form=form)
         self.assertEqual(
             SpecificationImplementationStatus.STARTED,
-            spec.implementation_status)
+            spec.implementation_status,
+        )
         self.assertEqual(spec.owner, spec.starter)
         [notification] = view.request.notifications
         self.assertEqual(BrowserNotificationLevel.INFO, notification.level)
         self.assertEqual(
             html_escape('Blueprint is now considered "Started".'),
-            notification.message)
+            notification.message,
+        )
 
     def test_unchanged_lifecycle_has_no_notification(self):
         spec = self.factory.makeSpecification(
-            implementation_status=SpecificationImplementationStatus.STARTED)
+            implementation_status=SpecificationImplementationStatus.STARTED
+        )
         login_person(spec.owner)
         form = {
-            'field.implementation_status': 'SLOW',
-            'field.actions.change': 'Change',
-            }
-        view = create_initialized_view(spec, name='+status', form=form)
+            "field.implementation_status": "SLOW",
+            "field.actions.change": "Change",
+        }
+        view = create_initialized_view(spec, name="+status", form=form)
         self.assertEqual(
-            SpecificationImplementationStatus.SLOW,
-            spec.implementation_status)
+            SpecificationImplementationStatus.SLOW, spec.implementation_status
+        )
         self.assertEqual(0, len(view.request.notifications))
 
     def test_records_unstarting(self):
         # If a spec was started, and is changed to not started,
         # a notice is shown. Also the spec.starter is cleared out.
         spec = self.factory.makeSpecification(
-            implementation_status=SpecificationImplementationStatus.STARTED)
+            implementation_status=SpecificationImplementationStatus.STARTED
+        )
         login_person(spec.owner)
         form = {
-            'field.implementation_status': 'NOTSTARTED',
-            'field.actions.change': 'Change',
-            }
-        view = create_initialized_view(spec, name='+status', form=form)
+            "field.implementation_status": "NOTSTARTED",
+            "field.actions.change": "Change",
+        }
+        view = create_initialized_view(spec, name="+status", form=form)
         self.assertEqual(
             SpecificationImplementationStatus.NOTSTARTED,
-            spec.implementation_status)
+            spec.implementation_status,
+        )
         self.assertIs(None, spec.starter)
         [notification] = view.request.notifications
         self.assertEqual(BrowserNotificationLevel.INFO, notification.level)
         self.assertEqual(
             html_escape('Blueprint is now considered "Not started".'),
-            notification.message)
+            notification.message,
+        )
 
     def test_records_completion(self):
         # If a spec is marked as implemented the user is notifiec it is now
         # complete.
         spec = self.factory.makeSpecification(
-            implementation_status=SpecificationImplementationStatus.STARTED)
+            implementation_status=SpecificationImplementationStatus.STARTED
+        )
         login_person(spec.owner)
         form = {
-            'field.implementation_status': 'IMPLEMENTED',
-            'field.actions.change': 'Change',
-            }
-        view = create_initialized_view(spec, name='+status', form=form)
+            "field.implementation_status": "IMPLEMENTED",
+            "field.actions.change": "Change",
+        }
+        view = create_initialized_view(spec, name="+status", form=form)
         self.assertEqual(
             SpecificationImplementationStatus.IMPLEMENTED,
-            spec.implementation_status)
+            spec.implementation_status,
+        )
         self.assertEqual(spec.owner, spec.completer)
         [notification] = view.request.notifications
         self.assertEqual(BrowserNotificationLevel.INFO, notification.level)
         self.assertEqual(
             html_escape('Blueprint is now considered "Complete".'),
-            notification.message)
+            notification.message,
+        )
 
 
 class TestSecificationHelpers(unittest.TestCase):
@@ -853,16 +906,14 @@ class TestSecificationHelpers(unittest.TestCase):
     def test_dict_to_DOT_attrs(self):
         """Verify that dicts are converted to a sorted DOT attr string."""
         expected_attrs = (
-            '  [\n'
+            "  [\n"
             '  "bar"="bar \\" \\n bar",\n'
             '  "baz"="zab",\n'
             '  "foo"="foo"\n'
-            '  ]')
-        dict_attrs = dict(
-            foo="foo",
-            bar="bar \" \n bar",
-            baz="zab")
-        dot_attrs = specification.dict_to_DOT_attrs(dict_attrs, indent='  ')
+            "  ]"
+        )
+        dict_attrs = dict(foo="foo", bar='bar " \n bar', baz="zab")
+        dot_attrs = specification.dict_to_DOT_attrs(dict_attrs, indent="  ")
         self.assertEqual(dot_attrs, expected_attrs)
 
 
@@ -873,8 +924,9 @@ class TestSpecificationFieldXHTMLRepresentations(TestCaseWithFactory):
     def test_starter_empty(self):
         blueprint = self.factory.makeBlueprint()
         repr_method = specification.starter_xhtml_representation(
-            blueprint, ISpecification['starter'], None)
-        self.assertThat(repr_method(), Equals(''))
+            blueprint, ISpecification["starter"], None
+        )
+        self.assertThat(repr_method(), Equals(""))
 
     def test_starter_set(self):
         user = self.factory.makePerson()
@@ -882,18 +934,21 @@ class TestSpecificationFieldXHTMLRepresentations(TestCaseWithFactory):
         when = datetime(2011, 1, 1, tzinfo=pytz.UTC)
         with person_logged_in(user):
             blueprint.setImplementationStatus(
-                SpecificationImplementationStatus.STARTED, user)
+                SpecificationImplementationStatus.STARTED, user
+            )
         removeSecurityProxy(blueprint).date_started = when
         repr_method = specification.starter_xhtml_representation(
-            blueprint, ISpecification['starter'], None)
-        expected = format_link(user) + ' on 2011-01-01'
+            blueprint, ISpecification["starter"], None
+        )
+        expected = format_link(user) + " on 2011-01-01"
         self.assertThat(repr_method(), Equals(expected))
 
     def test_completer_empty(self):
         blueprint = self.factory.makeBlueprint()
         repr_method = specification.completer_xhtml_representation(
-            blueprint, ISpecification['completer'], None)
-        self.assertThat(repr_method(), Equals(''))
+            blueprint, ISpecification["completer"], None
+        )
+        self.assertThat(repr_method(), Equals(""))
 
     def test_completer_set(self):
         user = self.factory.makePerson()
@@ -901,9 +956,11 @@ class TestSpecificationFieldXHTMLRepresentations(TestCaseWithFactory):
         when = datetime(2011, 1, 1, tzinfo=pytz.UTC)
         with person_logged_in(user):
             blueprint.setImplementationStatus(
-                SpecificationImplementationStatus.IMPLEMENTED, user)
+                SpecificationImplementationStatus.IMPLEMENTED, user
+            )
         removeSecurityProxy(blueprint).date_completed = when
         repr_method = specification.completer_xhtml_representation(
-            blueprint, ISpecification['completer'], None)
-        expected = format_link(user) + ' on 2011-01-01'
+            blueprint, ISpecification["completer"], None
+        )
+        expected = format_link(user) + " on 2011-01-01"
         self.assertThat(repr_method(), Equals(expected))
diff --git a/lib/lp/blueprints/browser/tests/test_specificationdependency.py b/lib/lp/blueprints/browser/tests/test_specificationdependency.py
index 3408a64..b42cfa7 100644
--- a/lib/lp/blueprints/browser/tests/test_specificationdependency.py
+++ b/lib/lp/blueprints/browser/tests/test_specificationdependency.py
@@ -10,11 +10,11 @@ from lp.app.enums import InformationType
 from lp.registry.enums import SpecificationSharingPolicy
 from lp.services.webapp import canonical_url
 from lp.testing import (
-    anonymous_logged_in,
     BrowserTestCase,
-    person_logged_in,
     TestCaseWithFactory,
-    )
+    anonymous_logged_in,
+    person_logged_in,
+)
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.views import create_view
 
@@ -29,9 +29,9 @@ class TestAddDependency(BrowserTestCase):
         spec = self.factory.makeSpecification(owner=self.user)
         dependency = self.factory.makeSpecification()
         dependency_url = canonical_url(dependency)
-        browser = self.getViewBrowser(spec, '+linkdependency')
-        browser.getControl('Depends On').value = dependency_url
-        browser.getControl('Continue').click()
+        browser = self.getViewBrowser(spec, "+linkdependency")
+        browser.getControl("Depends On").value = dependency_url
+        browser.getControl("Continue").click()
         # click() above issues a request, and
         # ZopePublication.endRequest() calls
         # zope.security.management.endInteraction().
@@ -51,10 +51,12 @@ class TestDepTree(TestCaseWithFactory):
         sharing_policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         owner = self.factory.makePerson()
         product = self.factory.makeProduct(
-            owner=owner, specification_sharing_policy=sharing_policy)
+            owner=owner, specification_sharing_policy=sharing_policy
+        )
         root = self.factory.makeBlueprint(product=product)
         proprietary_dep = self.factory.makeBlueprint(
-            product=product, information_type=InformationType.PROPRIETARY)
+            product=product, information_type=InformationType.PROPRIETARY
+        )
         public_dep = self.factory.makeBlueprint(product=product)
         root.createDependency(proprietary_dep)
         root.createDependency(public_dep)
@@ -68,10 +70,8 @@ class TestDepTree(TestCaseWithFactory):
         # The owner can see everything.
         with person_logged_in(owner):
             view = create_view(root, name="+deptree")
-            self.assertEqual(
-                [proprietary_dep, public_dep], view.all_deps)
-            self.assertEqual(
-                [proprietary_dep, public_dep], view.dependencies)
+            self.assertEqual([proprietary_dep, public_dep], view.all_deps)
+            self.assertEqual([proprietary_dep, public_dep], view.dependencies)
 
         # A random person cannot see the propriety dep.
         with person_logged_in(self.factory.makePerson()):
@@ -85,10 +85,12 @@ class TestDepTree(TestCaseWithFactory):
         sharing_policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         owner = self.factory.makePerson()
         product = self.factory.makeProduct(
-            owner=owner, specification_sharing_policy=sharing_policy)
+            owner=owner, specification_sharing_policy=sharing_policy
+        )
         root = self.factory.makeBlueprint(product=product)
         proprietary_blocked = self.factory.makeBlueprint(
-            product=product, information_type=InformationType.PROPRIETARY)
+            product=product, information_type=InformationType.PROPRIETARY
+        )
         public_blocked = self.factory.makeBlueprint(product=product)
         with person_logged_in(product.owner):
             proprietary_blocked.createDependency(root)
@@ -104,9 +106,11 @@ class TestDepTree(TestCaseWithFactory):
         with person_logged_in(owner):
             view = create_view(root, name="+deptree")
             self.assertEqual(
-                [proprietary_blocked, public_blocked], view.all_blocked)
+                [proprietary_blocked, public_blocked], view.all_blocked
+            )
             self.assertEqual(
-                [proprietary_blocked, public_blocked], view.blocked_specs)
+                [proprietary_blocked, public_blocked], view.blocked_specs
+            )
 
         # A random person cannot see the propriety dep.
         with person_logged_in(self.factory.makePerson()):
diff --git a/lib/lp/blueprints/browser/tests/test_specificationsubscription.py b/lib/lp/blueprints/browser/tests/test_specificationsubscription.py
index 6f0d148..3e8b050 100644
--- a/lib/lp/blueprints/browser/tests/test_specificationsubscription.py
+++ b/lib/lp/blueprints/browser/tests/test_specificationsubscription.py
@@ -1,10 +1,7 @@
 # Copyright 2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-from lp.testing import (
-    person_logged_in,
-    TestCaseWithFactory,
-    )
+from lp.testing import TestCaseWithFactory, person_logged_in
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.views import create_initialized_view
 
@@ -25,7 +22,8 @@ class SpecificationPortletSubscribersContentsTestCase(TestCaseWithFactory):
             sub2 = spec.subscribe(subscriber1, subscriber)
             sub3 = spec.subscribe(subscriber2, subscriber)
             view = create_initialized_view(
-                spec, name="+blueprint-portlet-subscribers-content")
+                spec, name="+blueprint-portlet-subscribers-content"
+            )
             self.assertEqual([sub1, sub3, sub2], view.sorted_subscriptions)
 
 
@@ -40,8 +38,10 @@ class TestSpecificationPortletSubcribersIds(TestCaseWithFactory):
         with person_logged_in(person):
             spec.subscribe(subscriber, subscriber)
             view = create_initialized_view(
-                spec, name="+blueprint-portlet-subscribers-ids")
+                spec, name="+blueprint-portlet-subscribers-ids"
+            )
             subscriber_ids = {
-                    subscriber.name: 'subscriber-%s' % subscriber.id
-                    for subscriber in [person, subscriber]}
+                subscriber.name: "subscriber-%s" % subscriber.id
+                for subscriber in [person, subscriber]
+            }
             self.assertEqual(subscriber_ids, view.subscriber_ids)
diff --git a/lib/lp/blueprints/browser/tests/test_specificationtarget.py b/lib/lp/blueprints/browser/tests/test_specificationtarget.py
index b992b69..a4baddc 100644
--- a/lib/lp/blueprints/browser/tests/test_specificationtarget.py
+++ b/lib/lp/blueprints/browser/tests/test_specificationtarget.py
@@ -5,52 +5,48 @@ from fixtures import FakeLogger
 from zope.component import getUtility
 from zope.security.proxy import removeSecurityProxy
 
-from lp.app.enums import (
-    InformationType,
-    ServiceUsage,
-    )
+from lp.app.enums import InformationType, ServiceUsage
 from lp.blueprints.browser.specificationtarget import HasSpecificationsView
 from lp.blueprints.interfaces.specification import ISpecificationSet
 from lp.blueprints.interfaces.specificationtarget import (
     IHasSpecifications,
     ISpecificationTarget,
-    )
+)
 from lp.services.beautifulsoup import BeautifulSoup
 from lp.testing import (
     BrowserTestCase,
+    TestCaseWithFactory,
     login_person,
     person_logged_in,
-    TestCaseWithFactory,
-    )
+)
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.matchers import IsConfiguredBatchNavigator
 from lp.testing.pages import find_tag_by_id
-from lp.testing.views import (
-    create_initialized_view,
-    create_view,
-    )
+from lp.testing.views import create_initialized_view, create_view
 
 
 class TestRegisterABlueprintButtonPortlet(TestCaseWithFactory):
     """Test specification menus links."""
+
     layer = DatabaseFunctionalLayer
 
     def verify_view(self, context, name):
-        view = create_view(
-            context, '+register-a-blueprint-button')
+        view = create_view(context, "+register-a-blueprint-button")
         self.assertEqual(
-            'http://blueprints.launchpad.test/%s/+addspec' % name,
-            view.target_url)
+            "http://blueprints.launchpad.test/%s/+addspec"; % name,
+            view.target_url,
+        )
         self.assertTrue(
-            '<div id="involvement" class="portlet involvement">' in view())
+            '<div id="involvement" class="portlet involvement">' in view()
+        )
 
     def test_specificationtarget(self):
-        context = self.factory.makeProduct(name='almond')
+        context = self.factory.makeProduct(name="almond")
         self.assertTrue(ISpecificationTarget.providedBy(context))
         self.verify_view(context, context.name)
 
     def test_adaptable_to_specificationtarget(self):
-        context = self.factory.makeProject(name='hazelnut')
+        context = self.factory.makeProject(name="hazelnut")
         self.assertFalse(ISpecificationTarget.providedBy(context))
         self.verify_view(context, context.name)
 
@@ -58,13 +54,14 @@ class TestRegisterABlueprintButtonPortlet(TestCaseWithFactory):
         # Sprints are a special case. They are not ISpecificationTargets,
         # nor can they be adapted to a ISpecificationTarget,
         # but can create a spcification for a ISpecificationTarget.
-        context = self.factory.makeSprint(title='Walnut', name='walnut')
+        context = self.factory.makeSprint(title="Walnut", name="walnut")
         self.assertFalse(ISpecificationTarget.providedBy(context))
-        self.verify_view(context, 'sprints/%s' % context.name)
+        self.verify_view(context, "sprints/%s" % context.name)
 
 
 class TestHasSpecificationsViewInvolvement(TestCaseWithFactory):
     """Test specification menus links."""
+
     layer = DatabaseFunctionalLayer
 
     def setUp(self):
@@ -77,12 +74,13 @@ class TestHasSpecificationsViewInvolvement(TestCaseWithFactory):
 
     def verify_involvment(self, context):
         self.assertTrue(IHasSpecifications.providedBy(context))
-        view = create_view(context, '+specs', principal=self.user)
+        view = create_view(context, "+specs", principal=self.user)
         self.assertTrue(
-            '<div id="involvement" class="portlet involvement">' in view())
+            '<div id="involvement" class="portlet involvement">' in view()
+        )
 
     def test_specificationtarget(self):
-        context = self.factory.makeProduct(name='almond')
+        context = self.factory.makeProduct(name="almond")
         naked_product = removeSecurityProxy(context)
         naked_product.blueprints_usage = ServiceUsage.LAUNCHPAD
         self.verify_involvment(context)
@@ -90,35 +88,38 @@ class TestHasSpecificationsViewInvolvement(TestCaseWithFactory):
     def test_adaptable_to_specificationtarget(self):
         # A project group should adapt to the products within to determine
         # involvment.
-        context = self.factory.makeProject(name='hazelnut')
+        context = self.factory.makeProject(name="hazelnut")
         product = self.factory.makeProduct(projectgroup=context)
         naked_product = removeSecurityProxy(product)
         naked_product.blueprints_usage = ServiceUsage.LAUNCHPAD
         self.verify_involvment(context)
 
     def test_sprint(self):
-        context = self.factory.makeSprint(title='Walnut', name='walnut')
+        context = self.factory.makeSprint(title="Walnut", name="walnut")
         self.verify_involvment(context)
 
     def test_person(self):
-        context = self.factory.makePerson(name='pistachio')
+        context = self.factory.makePerson(name="pistachio")
         self.assertTrue(IHasSpecifications.providedBy(context))
-        view = create_view(context, '+specs', principal=self.user)
+        view = create_view(context, "+specs", principal=self.user)
         self.assertFalse(
-            '<div id="involvement" class="portlet involvement">' in view())
+            '<div id="involvement" class="portlet involvement">' in view()
+        )
 
     def test_specs_batch(self):
         # Some pages turn up in very large contexts and so batch. E.g.
         # Distro:+assignments which uses SpecificationAssignmentsView, a
         # subclass.
         person = self.factory.makePerson()
-        view = create_initialized_view(person, name='+assignments')
+        view = create_initialized_view(person, name="+assignments")
         # Because +assignments is meant to provide an overview, we default to
         # 500 as the default batch size.
         self.assertThat(
             view.specs_batched,
             IsConfiguredBatchNavigator(
-                'specification', 'specifications', batch_size=500))
+                "specification", "specifications", batch_size=500
+            ),
+        )
 
 
 class TestAssignments(TestCaseWithFactory):
@@ -135,13 +136,18 @@ class TestAssignments(TestCaseWithFactory):
         product = self.factory.makeProduct()
         self.factory.makeSpecification(product=product)
         self.factory.makeSpecification(product=product)
-        view = create_initialized_view(product, name='+assignments',
-            query_string="batch=1")
+        view = create_initialized_view(
+            product, name="+assignments", query_string="batch=1"
+        )
         content = view.render()
-        self.assertEqual(['next'],
-            find_tag_by_id(content, 'upper-batch-nav-batchnav-next')['class'])
-        self.assertEqual(['next'],
-            find_tag_by_id(content, 'lower-batch-nav-batchnav-next')['class'])
+        self.assertEqual(
+            ["next"],
+            find_tag_by_id(content, "upper-batch-nav-batchnav-next")["class"],
+        )
+        self.assertEqual(
+            ["next"],
+            find_tag_by_id(content, "lower-batch-nav-batchnav-next")["class"],
+        )
 
 
 class TestHasSpecificationsTemplates(TestCaseWithFactory):
@@ -163,17 +169,17 @@ class TestHasSpecificationsTemplates(TestCaseWithFactory):
             ServiceUsage.EXTERNAL,
             ServiceUsage.NOT_APPLICABLE,
             ServiceUsage.LAUNCHPAD,
-            ]
+        ]
         correct_templates = [
             HasSpecificationsView.not_launchpad_template.filename,
             HasSpecificationsView.not_launchpad_template.filename,
             HasSpecificationsView.not_launchpad_template.filename,
             HasSpecificationsView.default_template.filename,
-            ]
+        ]
         used_templates = list()
         for config in test_configurations:
             naked_target.blueprints_usage = config
-            view = create_view(context, '+specs', principal=self.user)
+            view = create_view(context, "+specs", principal=self.user)
             used_templates.append(view.template.filename)
         self.assertEqual(correct_templates, used_templates)
 
@@ -185,8 +191,8 @@ class TestHasSpecificationsTemplates(TestCaseWithFactory):
         product = self.factory.makeProduct()
         product_series = self.factory.makeProductSeries(product=product)
         self._test_templates_for_configuration(
-            target=product,
-            context=product_series)
+            target=product, context=product_series
+        )
 
     def test_distribution(self):
         distribution = self.factory.makeDistribution()
@@ -195,18 +201,19 @@ class TestHasSpecificationsTemplates(TestCaseWithFactory):
     def test_distroseries(self):
         distribution = self.factory.makeDistribution()
         distro_series = self.factory.makeDistroSeries(
-            distribution=distribution)
+            distribution=distribution
+        )
         self._test_templates_for_configuration(
-            target=distribution,
-            context=distro_series)
+            target=distribution, context=distro_series
+        )
 
     def test_projectgroup(self):
         projectgroup = self.factory.makeProject()
         product1 = self.factory.makeProduct(projectgroup=projectgroup)
         self.factory.makeProduct(projectgroup=projectgroup)
         self._test_templates_for_configuration(
-            target=product1,
-            context=projectgroup)
+            target=product1, context=projectgroup
+        )
 
 
 class TestHasSpecificationsConfiguration(TestCaseWithFactory):
@@ -215,30 +222,30 @@ class TestHasSpecificationsConfiguration(TestCaseWithFactory):
 
     def test_cannot_configure_blueprints_product_no_edit_permission(self):
         product = self.factory.makeProduct()
-        view = create_initialized_view(product, '+specs')
+        view = create_initialized_view(product, "+specs")
         self.assertEqual(False, view.can_configure_blueprints)
 
     def test_can_configure_blueprints_product_with_edit_permission(self):
         product = self.factory.makeProduct()
         login_person(product.owner)
-        view = create_initialized_view(product, '+specs')
+        view = create_initialized_view(product, "+specs")
         self.assertEqual(True, view.can_configure_blueprints)
 
     def test_cant_configure_blueprints_distribution_no_edit_permission(self):
         distribution = self.factory.makeDistribution()
-        view = create_initialized_view(distribution, '+specs')
+        view = create_initialized_view(distribution, "+specs")
         self.assertEqual(False, view.can_configure_blueprints)
 
     def test_can_configure_blueprints_distribution_with_edit_permission(self):
         distribution = self.factory.makeDistribution()
         login_person(distribution.owner)
-        view = create_initialized_view(distribution, '+specs')
+        view = create_initialized_view(distribution, "+specs")
         self.assertEqual(True, view.can_configure_blueprints)
 
     def test_cannot_configure_blueprints_projectgroup(self):
         project_group = self.factory.makeProject()
         login_person(project_group.owner)
-        view = create_initialized_view(project_group, '+specs')
+        view = create_initialized_view(project_group, "+specs")
         self.assertEqual(False, view.can_configure_blueprints)
 
 
@@ -257,20 +264,20 @@ class TestSpecificationsRobots(TestCaseWithFactory):
 
     def _configure_project(self, usage):
         self.naked_product.blueprints_usage = usage
-        view = create_initialized_view(self.product, '+specs')
+        view = create_initialized_view(self.product, "+specs")
         soup = BeautifulSoup(view())
-        robots = soup.find('meta', attrs={'name': 'robots'})
+        robots = soup.find("meta", attrs={"name": "robots"})
         return soup, robots
 
     def _verify_robots_not_blocked(self, usage):
         soup, robots = self._configure_project(usage)
         self.assertTrue(robots is None)
-        self.assertTrue(soup.find(True, id='specs-unknown') is None)
+        self.assertTrue(soup.find(True, id="specs-unknown") is None)
 
     def _verify_robots_are_blocked(self, usage):
         soup, robots = self._configure_project(usage)
-        self.assertEqual('noindex,nofollow', robots['content'])
-        self.assertTrue(soup.find(True, id='specs-unknown') is not None)
+        self.assertEqual("noindex,nofollow", robots["content"])
+        self.assertTrue(soup.find(True, id="specs-unknown") is not None)
 
     def test_UNKNOWN_blocks_robots(self):
         self._verify_robots_are_blocked(ServiceUsage.UNKNOWN)
@@ -293,20 +300,20 @@ class SpecificationSetViewTestCase(TestCaseWithFactory):
     def test_search_specifications_form_rendering(self):
         # The view's template directly renders the form widgets.
         specification_set = getUtility(ISpecificationSet)
-        view = create_initialized_view(specification_set, '+index')
-        content = find_tag_by_id(view.render(), 'search-all-specifications')
-        self.assertIsNot(None, content.find(True, id='text'))
-        self.assertIsNot(
-            None, content.find(True, id='field.actions.search'))
-        self.assertIsNot(
-            None, content.find(True, id='field.scope.option.all'))
+        view = create_initialized_view(specification_set, "+index")
+        content = find_tag_by_id(view.render(), "search-all-specifications")
+        self.assertIsNot(None, content.find(True, id="text"))
+        self.assertIsNot(None, content.find(True, id="field.actions.search"))
+        self.assertIsNot(None, content.find(True, id="field.scope.option.all"))
         self.assertIsNot(
-            None, content.find(True, id='field.scope.option.project'))
-        target_widget = view.widgets['scope'].target_widget
+            None, content.find(True, id="field.scope.option.project")
+        )
+        target_widget = view.widgets["scope"].target_widget
         self.assertIsNot(
-            None, content.find(True, id=target_widget.show_widget_id))
+            None, content.find(True, id=target_widget.show_widget_id)
+        )
         text = str(content)
-        picker_vocab = 'DistributionOrProductOrProjectGroup'
+        picker_vocab = "DistributionOrProductOrProjectGroup"
         self.assertIn(picker_vocab, text)
         focus_script = "setFocusByName('field.search_text')"
         self.assertIn(focus_script, text)
@@ -320,17 +327,20 @@ class TestPrivacy(BrowserTestCase):
         # Proprietary specs are only listed for users who can see them.
         # Other users see the page, but not the private specs.
         proprietary = self.factory.makeSpecification(
-            information_type=InformationType.PROPRIETARY)
+            information_type=InformationType.PROPRIETARY
+        )
         product = removeSecurityProxy(proprietary).product
         public = self.factory.makeSpecification(product=product)
         with person_logged_in(product.owner):
             product.blueprints_usage = ServiceUsage.LAUNCHPAD
-            browser = self.getViewBrowser(product, '+specs')
+            browser = self.getViewBrowser(product, "+specs")
         self.assertIn(public.name, browser.contents)
         self.assertNotIn(
-            removeSecurityProxy(proprietary).name, browser.contents)
+            removeSecurityProxy(proprietary).name, browser.contents
+        )
         with person_logged_in(None):
-            browser = self.getViewBrowser(product, '+specs',
-                                          user=product.owner)
+            browser = self.getViewBrowser(
+                product, "+specs", user=product.owner
+            )
         self.assertIn(public.name, browser.contents)
         self.assertIn(removeSecurityProxy(proprietary).name, browser.contents)
diff --git a/lib/lp/blueprints/browser/tests/test_sprint.py b/lib/lp/blueprints/browser/tests/test_sprint.py
index 603700d..00d63f3 100644
--- a/lib/lp/blueprints/browser/tests/test_sprint.py
+++ b/lib/lp/blueprints/browser/tests/test_sprint.py
@@ -14,14 +14,11 @@ from lp.app.enums import InformationType
 from lp.services.webapp.publisher import canonical_url
 from lp.testing import (
     BrowserTestCase,
-    person_logged_in,
     RequestTimelineCollector,
-    )
+    person_logged_in,
+)
 from lp.testing.layers import DatabaseFunctionalLayer
-from lp.testing.matchers import (
-    BrowsesWithQueryLimit,
-    HasQueryCount,
-    )
+from lp.testing.matchers import BrowsesWithQueryLimit, HasQueryCount
 
 
 class TestSprintIndex(BrowserTestCase):
@@ -34,7 +31,10 @@ class TestSprintIndex(BrowserTestCase):
             for x in range(30):
                 sprint.attend(
                     self.factory.makePerson(),
-                    sprint.time_starts, sprint.time_ends, True)
+                    sprint.time_starts,
+                    sprint.time_ends,
+                    True,
+                )
         self.assertThat(sprint, BrowsesWithQueryLimit(21, sprint.owner))
 
     def test_blueprint_listing_query_count(self):
@@ -53,7 +53,8 @@ class TestSprintIndex(BrowserTestCase):
         sprint = self.factory.makeSprint()
         for count in range(10):
             blueprint = self.factory.makeSpecification(
-                information_type=InformationType.PROPRIETARY)
+                information_type=InformationType.PROPRIETARY
+            )
             owner = removeSecurityProxy(blueprint).owner
             link = removeSecurityProxy(blueprint).linkSprint(sprint, owner)
             link.acceptBy(sprint.owner)
@@ -71,7 +72,10 @@ class TestSprintDeleteView(BrowserTestCase):
         with person_logged_in(sprint.owner):
             sprint.attend(
                 self.factory.makePerson(),
-                sprint.time_starts, sprint.time_ends, True)
+                sprint.time_starts,
+                sprint.time_ends,
+                True,
+            )
         blueprint = self.factory.makeSpecification()
         blueprint.linkSprint(sprint, blueprint.owner).acceptBy(sprint.owner)
         return sprint
@@ -85,8 +89,11 @@ class TestSprintDeleteView(BrowserTestCase):
         browser = self.getViewBrowser(sprint, user=other_person)
         self.assertRaises(LinkNotFoundError, browser.getLink, "Delete sprint")
         self.assertRaises(
-            Unauthorized, self.getUserBrowser, sprint_url + "/+delete",
-            user=other_person)
+            Unauthorized,
+            self.getUserBrowser,
+            sprint_url + "/+delete",
+            user=other_person,
+        )
 
     def test_delete_sprint_owner(self):
         # A sprint can be deleted by its owner, even if it has attendees and
@@ -109,7 +116,8 @@ class TestSprintDeleteView(BrowserTestCase):
         sprint_url = canonical_url(sprint)
         owner_url = canonical_url(sprint.owner)
         browser = self.getViewBrowser(
-            sprint, user=self.factory.makeRegistryExpert())
+            sprint, user=self.factory.makeRegistryExpert()
+        )
         browser.getLink("Delete sprint").click()
         browser.getControl("Delete sprint").click()
         self.assertEqual(owner_url, browser.url)
diff --git a/lib/lp/blueprints/browser/tests/test_views.py b/lib/lp/blueprints/browser/tests/test_views.py
index b985bb4..1e2c49e 100644
--- a/lib/lp/blueprints/browser/tests/test_views.py
+++ b/lib/lp/blueprints/browser/tests/test_views.py
@@ -13,19 +13,15 @@ from testtools.matchers import LessThan
 
 from lp.services.webapp import canonical_url
 from lp.testing import (
-    login,
-    logout,
     RequestTimelineCollector,
     TestCaseWithFactory,
-    )
+    login,
+    logout,
+)
 from lp.testing.layers import DatabaseFunctionalLayer
 from lp.testing.matchers import HasQueryCount
 from lp.testing.sampledata import ADMIN_EMAIL
-from lp.testing.systemdocs import (
-    LayeredDocFileSuite,
-    setUp,
-    tearDown,
-    )
+from lp.testing.systemdocs import LayeredDocFileSuite, setUp, tearDown
 
 
 class TestAssignments(TestCaseWithFactory):
@@ -45,8 +41,9 @@ class TestAssignments(TestCaseWithFactory):
         store.invalidate()
         browser.open(url)
 
-    def check_query_counts_scaling_with_unique_people(self,
-        target, targettype):
+    def check_query_counts_scaling_with_unique_people(
+        self, target, targettype
+    ):
         """Check that a particular hasSpecifications target scales well.
 
         :param target: A spec target like a product.
@@ -59,8 +56,9 @@ class TestAssignments(TestCaseWithFactory):
             people.append(self.factory.makePerson())
         specs = []
         for _ in range(10):
-            specs.append(self.factory.makeSpecification(
-                **{targettype: target}))
+            specs.append(
+                self.factory.makeSpecification(**{targettype: target})
+            )
         collector = RequestTimelineCollector()
         collector.register()
         self.addCleanup(collector.unregister)
@@ -83,15 +81,18 @@ class TestAssignments(TestCaseWithFactory):
         logout()
         self.invalidate_and_render(browser, target, url)
         self.assertThat(
-            collector, HasQueryCount(LessThan(no_assignees_count + 5)))
+            collector, HasQueryCount(LessThan(no_assignees_count + 5))
+        )
 
     def test_product_query_counts_scale_below_unique_people(self):
         self.check_query_counts_scaling_with_unique_people(
-            self.factory.makeProduct(), 'product')
+            self.factory.makeProduct(), "product"
+        )
 
     def test_distro_query_counts_scale_below_unique_people(self):
         self.check_query_counts_scaling_with_unique_people(
-            self.factory.makeDistribution(), 'distribution')
+            self.factory.makeDistribution(), "distribution"
+        )
 
 
 def test_suite():
@@ -100,18 +101,22 @@ def test_suite():
     testsdir = os.path.abspath(here)
 
     # Add tests using default setup/teardown
-    filenames = [filename
-                 for filename in os.listdir(testsdir)
-                 if filename.endswith('.rst')]
+    filenames = [
+        filename
+        for filename in os.listdir(testsdir)
+        if filename.endswith(".rst")
+    ]
     # Sort the list to give a predictable order.
     filenames.sort()
     for filename in filenames:
         path = filename
         one_test = LayeredDocFileSuite(
             path,
-            setUp=setUp, tearDown=tearDown,
+            setUp=setUp,
+            tearDown=tearDown,
             layer=DatabaseFunctionalLayer,
-            stdout_logging_level=logging.WARNING)
+            stdout_logging_level=logging.WARNING,
+        )
         suite.addTest(one_test)
 
     return suite
diff --git a/lib/lp/blueprints/enums.py b/lib/lp/blueprints/enums.py
index 2c4e3cc..2e3707a 100644
--- a/lib/lp/blueprints/enums.py
+++ b/lib/lp/blueprints/enums.py
@@ -4,17 +4,17 @@
 """Enumerations used in the lp/blueprints modules."""
 
 __all__ = [
-    'NewSpecificationDefinitionStatus',
-    'SpecificationDefinitionStatus',
-    'SpecificationFilter',
-    'SpecificationGoalStatus',
-    'SpecificationImplementationStatus',
-    'SpecificationLifecycleStatus',
-    'SpecificationPriority',
-    'SpecificationSort',
-    'SprintSpecificationStatus',
-    'SpecificationWorkItemStatus',
-    ]
+    "NewSpecificationDefinitionStatus",
+    "SpecificationDefinitionStatus",
+    "SpecificationFilter",
+    "SpecificationGoalStatus",
+    "SpecificationImplementationStatus",
+    "SpecificationLifecycleStatus",
+    "SpecificationPriority",
+    "SpecificationSort",
+    "SprintSpecificationStatus",
+    "SpecificationWorkItemStatus",
+]
 
 
 from lazr.enum import (
@@ -23,7 +23,7 @@ from lazr.enum import (
     EnumeratedType,
     Item,
     use_template,
-    )
+)
 
 
 class SpecificationImplementationStatus(DBEnumeratedType):
@@ -40,108 +40,148 @@ class SpecificationImplementationStatus(DBEnumeratedType):
     database checks) if additional states are added that are also "not
     started".
     """
+
     # The `UNKNOWN` state is considered "not started"
-    UNKNOWN = DBItem(0, """
+    UNKNOWN = DBItem(
+        0,
+        """
         Unknown
 
         We have no information on the implementation of this feature.
-        """)
+        """,
+    )
 
     # The `NOTSTARTED` state is considered "not started"
-    NOTSTARTED = DBItem(5, """
+    NOTSTARTED = DBItem(
+        5,
+        """
         Not started
 
         No work has yet been done on the implementation of this feature.
-        """)
+        """,
+    )
 
     # The `DEFERRED` state is considered "not started"
-    DEFERRED = DBItem(10, """
+    DEFERRED = DBItem(
+        10,
+        """
         Deferred
 
         There is no chance that this feature will actually be delivered in
         the targeted release. The specification has effectively been
         deferred to a later date of implementation.
-        """)
+        """,
+    )
 
-    NEEDSINFRASTRUCTURE = DBItem(40, """
+    NEEDSINFRASTRUCTURE = DBItem(
+        40,
+        """
         Needs Infrastructure
 
         Work cannot proceed, because the feature depends on
         infrastructure (servers, databases, connectivity, system
         administration work) that has not been supplied.
-        """)
+        """,
+    )
 
-    BLOCKED = DBItem(50, """
+    BLOCKED = DBItem(
+        50,
+        """
         Blocked
 
         Work cannot proceed on this specification because it depends on
         a separate feature that has not yet been implemented.
         (The specification for that feature should be listed as a blocker of
         this one.)
-        """)
+        """,
+    )
 
-    STARTED = DBItem(60, """
+    STARTED = DBItem(
+        60,
+        """
         Started
 
         Work has begun, but has not yet been published
         except as informal branches or patches. No indication is given as to
         whether or not this work will be completed for the targeted release.
-        """)
+        """,
+    )
 
-    SLOW = DBItem(65, """
+    SLOW = DBItem(
+        65,
+        """
         Slow progress
 
         Work has been slow on this item, and it has a high risk of not being
         delivered on time. Help is wanted with the implementation.
-        """)
+        """,
+    )
 
-    GOOD = DBItem(70, """
+    GOOD = DBItem(
+        70,
+        """
         Good progress
 
         The feature is considered on track for delivery in the targeted
         release.
-        """)
+        """,
+    )
 
-    BETA = DBItem(75, """
+    BETA = DBItem(
+        75,
+        """
         Beta Available
 
         A beta version, implementing substantially all of the feature,
         has been published for widespread testing in personal package
         archives or a personal release. The code is not yet in the
         main archive or mainline branch. Testing and feedback are solicited.
-        """)
+        """,
+    )
 
-    NEEDSREVIEW = DBItem(80, """
+    NEEDSREVIEW = DBItem(
+        80,
+        """
         Needs Code Review
 
         The developer is satisfied that the feature has been well
         implemented. It is now ready for review and final sign-off,
         after which it will be marked implemented or deployed.
-        """)
+        """,
+    )
 
-    AWAITINGDEPLOYMENT = DBItem(85, """
+    AWAITINGDEPLOYMENT = DBItem(
+        85,
+        """
         Deployment
 
         The implementation has been done, and can be deployed in the
         production environment, but this has not yet been done by the system
         administrators. (This status is typically used for Web services where
         code is not released but instead is pushed into production.
-        """)
+        """,
+    )
 
-    IMPLEMENTED = DBItem(90, """
+    IMPLEMENTED = DBItem(
+        90,
+        """
         Implemented
 
         This functionality has been delivered for the targeted release, the
         code has been uploaded to the main archives or committed to the
         targeted product series, and no further work is necessary.
-        """)
+        """,
+    )
 
-    INFORMATIONAL = DBItem(95, """
+    INFORMATIONAL = DBItem(
+        95,
+        """
         Informational
 
         This specification is informational, and does not require
         any implementation.
-        """)
+        """,
+    )
 
 
 class SpecificationLifecycleStatus(DBEnumeratedType):
@@ -150,24 +190,33 @@ class SpecificationLifecycleStatus(DBEnumeratedType):
     Specs go from NOTSTARTED, to STARTED, to COMPLETE.
     """
 
-    NOTSTARTED = DBItem(10, """
+    NOTSTARTED = DBItem(
+        10,
+        """
         Not started
 
         No work has yet been done on this feature.
-        """)
+        """,
+    )
 
-    STARTED = DBItem(20, """
+    STARTED = DBItem(
+        20,
+        """
         Started
 
         This feature is under active development.
-        """)
+        """,
+    )
 
-    COMPLETE = DBItem(30, """
+    COMPLETE = DBItem(
+        30,
+        """
         Complete
 
         This feature has been marked "complete" because no further work is
         expected. Either the feature is done, or it has been abandoned.
-        """)
+        """,
+    )
 
 
 class SpecificationPriority(DBEnumeratedType):
@@ -176,7 +225,9 @@ class SpecificationPriority(DBEnumeratedType):
     This enum is used to prioritize work.
     """
 
-    NOTFORUS = DBItem(0, """
+    NOTFORUS = DBItem(
+        0,
+        """
         Not
 
         This feature has been proposed but the project leaders have decided
@@ -186,16 +237,22 @@ class SpecificationPriority(DBEnumeratedType):
         you are welcome to implement it in any event and publish that work
         for consideration by the community and end users, but it is unlikely
         to be accepted by the mainline developers.
-        """)
+        """,
+    )
 
-    UNDEFINED = DBItem(5, """
+    UNDEFINED = DBItem(
+        5,
+        """
         Undefined
 
         This feature has recently been proposed and has not yet been
         evaluated and prioritized by the project leaders.
-        """)
+        """,
+    )
 
-    LOW = DBItem(10, """
+    LOW = DBItem(
+        10,
+        """
         Low
 
         We would like to have it in the
@@ -204,30 +261,40 @@ class SpecificationPriority(DBEnumeratedType):
         is sound and the project leaders would incorporate this
         functionality if the work was done. In general, "low" priority
         specifications will not get core resources assigned to them.
-        """)
+        """,
+    )
 
-    MEDIUM = DBItem(50, """
+    MEDIUM = DBItem(
+        50,
+        """
         Medium
 
         The project developers will definitely get to this feature,
         but perhaps not in the next major release or two.
-        """)
+        """,
+    )
 
-    HIGH = DBItem(70, """
+    HIGH = DBItem(
+        70,
+        """
         High
 
         Strongly desired by the project leaders.
         The feature will definitely get review time, and contributions would
         be most effective if directed at a feature with this priority.
-        """)
+        """,
+    )
 
-    ESSENTIAL = DBItem(90, """
+    ESSENTIAL = DBItem(
+        90,
+        """
         Essential
 
         The specification is essential for the next release, and should be
         the focus of current development. Use this state only for the most
         important of all features.
-        """)
+        """,
+    )
 
 
 class SpecificationFilter(DBEnumeratedType):
@@ -238,107 +305,150 @@ class SpecificationFilter(DBEnumeratedType):
     kinds of specs they want returned. The different filters can be OR'ed so
     that multiple pieces of information can be used for the filter.
     """
-    ALL = DBItem(0, """
+
+    ALL = DBItem(
+        0,
+        """
         All
 
         This indicates that the list should simply include ALL
         specifications for the underlying object (person, product etc).
-        """)
+        """,
+    )
 
-    COMPLETE = DBItem(5, """
+    COMPLETE = DBItem(
+        5,
+        """
         Complete
 
         This indicates that the list should include only the complete
         specifications for this object.
-        """)
+        """,
+    )
 
-    INCOMPLETE = DBItem(10, """
+    INCOMPLETE = DBItem(
+        10,
+        """
         Incomplete
 
         This indicates that the list should include the incomplete items
         only. The rules for determining if a specification is incomplete are
         complex, depending on whether or not the spec is informational.
-        """)
+        """,
+    )
 
-    INFORMATIONAL = DBItem(20, """
+    INFORMATIONAL = DBItem(
+        20,
+        """
         Informational
 
         This indicates that the list should include only the informational
         specifications.
-        """)
+        """,
+    )
 
-    PROPOSED = DBItem(30, """
+    PROPOSED = DBItem(
+        30,
+        """
         Proposed
 
         This indicates that the list should include specifications that have
         been proposed as goals for the underlying objects, but not yet
         accepted or declined.
-        """)
+        """,
+    )
 
-    DECLINED = DBItem(40, """
+    DECLINED = DBItem(
+        40,
+        """
         Declined
 
         This indicates that the list should include specifications that were
         declined as goals for the underlying productseries or distroseries.
-        """)
+        """,
+    )
 
-    ACCEPTED = DBItem(50, """
+    ACCEPTED = DBItem(
+        50,
+        """
         Accepted
 
         This indicates that the list should include specifications that were
         accepted as goals for the underlying productseries or distroseries.
-        """)
+        """,
+    )
 
-    VALID = DBItem(55, """
+    VALID = DBItem(
+        55,
+        """
         Valid
 
         This indicates that the list should include specifications that are
         not obsolete or superseded.
-        """)
+        """,
+    )
 
-    CREATOR = DBItem(60, """
+    CREATOR = DBItem(
+        60,
+        """
         Creator
 
         This indicates that the list should include specifications that the
         person registered in Launchpad.
-        """)
+        """,
+    )
 
-    ASSIGNEE = DBItem(70, """
+    ASSIGNEE = DBItem(
+        70,
+        """
         Assignee
 
         This indicates that the list should include specifications that the
         person has been assigned to implement.
-        """)
+        """,
+    )
 
-    APPROVER = DBItem(80, """
+    APPROVER = DBItem(
+        80,
+        """
         Approver
 
         This indicates that the list should include specifications that the
         person is supposed to review and approve.
-        """)
+        """,
+    )
 
-    DRAFTER = DBItem(90, """
+    DRAFTER = DBItem(
+        90,
+        """
         Drafter
 
         This indicates that the list should include specifications that the
         person is supposed to draft. The drafter is usually only needed
         during spec sprints when there's a bottleneck on guys who are
         assignees for many specs.
-        """)
+        """,
+    )
 
-    SUBSCRIBER = DBItem(100, """
+    SUBSCRIBER = DBItem(
+        100,
+        """
         Subscriber
 
         This indicates that the list should include all the specifications
         to which the person has subscribed.
-        """)
+        """,
+    )
 
-    STARTED = DBItem(110, """
+    STARTED = DBItem(
+        110,
+        """
         Started
 
         This indicates that the list should include specifications that are
         marked as started.
-        """)
+        """,
+    )
 
 
 class SpecificationSort(EnumeratedType):
@@ -348,20 +458,25 @@ class SpecificationSort(EnumeratedType):
     specifications, so that you can tell which specifications you would
     expect to see first.
     """
-    DATE = Item("""
+
+    DATE = Item(
+        """
         Date
 
         This indicates a preferred sort order of date of creation, newest
         first.
-        """)
+        """
+    )
 
-    PRIORITY = Item("""
+    PRIORITY = Item(
+        """
         Priority
 
         This indicates a preferred sort order of priority (highest first)
         followed by status. This is the default sort order when retrieving
         specifications from the system.
-        """)
+        """
+    )
 
 
 class SpecificationDefinitionStatus(DBEnumeratedType):
@@ -373,66 +488,90 @@ class SpecificationDefinitionStatus(DBEnumeratedType):
     we probably want them displayed by default.
     """
 
-    APPROVED = DBItem(10, """
+    APPROVED = DBItem(
+        10,
+        """
         Approved
 
         The project team believe that the specification is ready to be
         implemented, without substantial issues being encountered.
-        """)
+        """,
+    )
 
-    PENDINGAPPROVAL = DBItem(15, """
+    PENDINGAPPROVAL = DBItem(
+        15,
+        """
         Pending Approval
 
         Reviewed and considered ready for final approval.
         The reviewer believes the specification is clearly written,
         and adequately addresses all important issues that will
         be raised during implementation.
-        """)
+        """,
+    )
 
-    PENDINGREVIEW = DBItem(20, """
+    PENDINGREVIEW = DBItem(
+        20,
+        """
         Review
 
         Has been put in a reviewer's queue. The reviewer will
         assess it for clarity and comprehensiveness, and decide
         whether further work is needed before the spec can be considered for
         actual approval.
-        """)
+        """,
+    )
 
-    DRAFT = DBItem(30, """
+    DRAFT = DBItem(
+        30,
+        """
         Drafting
 
         The specification is actively being drafted, with a drafter in place
         and frequent revision occurring.
         Do not park specs in the "drafting" state indefinitely.
-        """)
+        """,
+    )
 
-    DISCUSSION = DBItem(35, """
+    DISCUSSION = DBItem(
+        35,
+        """
         Discussion
 
         Still needs active discussion, at a sprint for example.
-        """)
+        """,
+    )
 
-    NEW = DBItem(40, """
+    NEW = DBItem(
+        40,
+        """
         New
 
         No thought has yet been given to implementation strategy,
         dependencies, or presentation/UI issues.
-        """)
+        """,
+    )
 
-    SUPERSEDED = DBItem(60, """
+    SUPERSEDED = DBItem(
+        60,
+        """
         Superseded
 
         Still interesting, but superseded by a newer spec or set of specs that
         clarify or describe a newer way to implement the desired feature.
         Please use the newer specs and not this one.
-        """)
+        """,
+    )
 
-    OBSOLETE = DBItem(70, """
+    OBSOLETE = DBItem(
+        70,
+        """
         Obsolete
 
         The specification has been obsoleted, probably because it was decided
         against. People should not put any effort into implementing it.
-        """)
+        """,
+    )
 
 
 class NewSpecificationDefinitionStatus(DBEnumeratedType):
@@ -441,14 +580,18 @@ class NewSpecificationDefinitionStatus(DBEnumeratedType):
     The initial status to define the feature and get approval for the
     implementation plan.
     """
-    use_template(SpecificationDefinitionStatus, include=(
-        'NEW',
-        'DISCUSSION',
-        'DRAFT',
-        'PENDINGREVIEW',
-        'PENDINGAPPROVAL',
-        'APPROVED',
-        ))
+
+    use_template(
+        SpecificationDefinitionStatus,
+        include=(
+            "NEW",
+            "DISCUSSION",
+            "DRAFT",
+            "PENDINGREVIEW",
+            "PENDINGAPPROVAL",
+            "APPROVED",
+        ),
+    )
 
 
 class SpecificationGoalStatus(DBEnumeratedType):
@@ -459,27 +602,36 @@ class SpecificationGoalStatus(DBEnumeratedType):
     distroseries.
     """
 
-    ACCEPTED = DBItem(10, """
+    ACCEPTED = DBItem(
+        10,
+        """
         Accepted
 
         The drivers have confirmed that this specification is targeted to
         the stated distribution release or product series.
-        """)
+        """,
+    )
 
-    DECLINED = DBItem(20, """
+    DECLINED = DBItem(
+        20,
+        """
         Declined
 
         The drivers have decided not to accept this specification as a goal
         for the stated distribution release or product series.
-        """)
+        """,
+    )
 
-    PROPOSED = DBItem(30, """
+    PROPOSED = DBItem(
+        30,
+        """
         Proposed
 
         This spec has been submitted as a potential goal for the stated
         product series or distribution release, but the drivers have not yet
         accepted or declined that goal.
-        """)
+        """,
+    )
 
 
 class SprintSpecificationStatus(DBEnumeratedType):
@@ -489,53 +641,77 @@ class SprintSpecificationStatus(DBEnumeratedType):
     agreed to discuss an item.
     """
 
-    ACCEPTED = DBItem(10, """
+    ACCEPTED = DBItem(
+        10,
+        """
         Accepted
 
         The meeting organisers have confirmed this topic for the meeting
         agenda.
-        """)
+        """,
+    )
 
-    DECLINED = DBItem(20, """
+    DECLINED = DBItem(
+        20,
+        """
         Declined
 
         This spec has been declined from the meeting agenda
         because of a lack of available resources, or uncertainty over
         the specific requirements or outcome desired.
-        """)
+        """,
+    )
 
-    PROPOSED = DBItem(30, """
+    PROPOSED = DBItem(
+        30,
+        """
         Proposed
 
         This spec has been submitted for consideration by the meeting
         organisers. It has not yet been accepted or declined for the
         agenda.
-        """)
+        """,
+    )
 
 
 class SpecificationWorkItemStatus(DBEnumeratedType):
-    TODO = DBItem(0, """
+    TODO = DBItem(
+        0,
+        """
         Todo
 
         A work item that's not done yet.
-        """)
-    DONE = DBItem(1, """
+        """,
+    )
+    DONE = DBItem(
+        1,
+        """
         Done
 
         A work item that's done.
-        """)
-    POSTPONED = DBItem(2, """
+        """,
+    )
+    POSTPONED = DBItem(
+        2,
+        """
         Postponed
 
         A work item that has been postponed.
-        """)
-    INPROGRESS = DBItem(3, """
+        """,
+    )
+    INPROGRESS = DBItem(
+        3,
+        """
         In progress
 
         A work item that is inprogress.
-        """)
-    BLOCKED = DBItem(4, """
+        """,
+    )
+    BLOCKED = DBItem(
+        4,
+        """
         Blocked
 
         A work item that is blocked.
-        """)
+        """,
+    )
diff --git a/lib/lp/blueprints/errors.py b/lib/lp/blueprints/errors.py
index 2a0cfbc..f1d875b 100644
--- a/lib/lp/blueprints/errors.py
+++ b/lib/lp/blueprints/errors.py
@@ -4,8 +4,8 @@
 """Specification views."""
 
 __all__ = [
-    'TargetAlreadyHasSpecification',
-    ]
+    "TargetAlreadyHasSpecification",
+]
 
 import http.client
 
@@ -18,5 +18,7 @@ class TargetAlreadyHasSpecification(Exception):
 
     def __init__(self, target, name):
         msg = "There is already a blueprint named %s for %s." % (
-                name, target.displayname)
+            name,
+            target.displayname,
+        )
         super().__init__(msg)
diff --git a/lib/lp/blueprints/interfaces/specification.py b/lib/lp/blueprints/interfaces/specification.py
index 2279cad..5dbafe5 100644
--- a/lib/lp/blueprints/interfaces/specification.py
+++ b/lib/lp/blueprints/interfaces/specification.py
@@ -4,17 +4,18 @@
 """Specification interfaces."""
 
 __all__ = [
-    'GoalProposeError',
-    'ISpecification',
-    'ISpecificationDelta',
-    'ISpecificationPublic',
-    'ISpecificationSet',
-    'ISpecificationView',
-    ]
+    "GoalProposeError",
+    "ISpecification",
+    "ISpecificationDelta",
+    "ISpecificationPublic",
+    "ISpecificationSet",
+    "ISpecificationView",
+]
 
 import http.client
 
 from lazr.restful.declarations import (
+    REQUEST_USER,
     call_with,
     collection_default_content,
     error_status,
@@ -27,28 +28,12 @@ from lazr.restful.declarations import (
     mutator_for,
     operation_for_version,
     operation_parameters,
-    REQUEST_USER,
-    )
-from lazr.restful.fields import (
-    CollectionField,
-    Reference,
-    ReferenceChoice,
-    )
+)
+from lazr.restful.fields import CollectionField, Reference, ReferenceChoice
 from lazr.restful.interface import copy_field
 from zope.component import getUtility
-from zope.interface import (
-    Attribute,
-    Interface,
-    )
-from zope.schema import (
-    Bool,
-    Choice,
-    Datetime,
-    Int,
-    List,
-    Text,
-    TextLine,
-    )
+from zope.interface import Attribute, Interface
+from zope.schema import Bool, Choice, Datetime, Int, List, Text, TextLine
 
 from lp import _
 from lp.app.enums import InformationType
@@ -62,17 +47,17 @@ from lp.blueprints.enums import (
     SpecificationLifecycleStatus,
     SpecificationPriority,
     SpecificationWorkItemStatus,
-    )
+)
 from lp.blueprints.interfaces.specificationsubscription import (
     ISpecificationSubscription,
-    )
+)
 from lp.blueprints.interfaces.specificationtarget import (
     IHasSpecifications,
     ISpecificationTarget,
-    )
+)
 from lp.blueprints.interfaces.specificationworkitem import (
     ISpecificationWorkItem,
-    )
+)
 from lp.blueprints.interfaces.sprint import ISprint
 from lp.bugs.interfaces.buglink import IBugLinkTarget
 from lp.bugs.interfaces.bugtarget import IBugTarget
@@ -87,7 +72,7 @@ from lp.services.fields import (
     Summary,
     Title,
     WorkItemsText,
-    )
+)
 from lp.services.webapp import canonical_url
 from lp.services.webapp.escaping import structured
 
@@ -142,12 +127,14 @@ class SpecNameField(ContentNameField):
 
 class SpecURLField(TextLine):
 
-    errormessage = _('%s is already registered by <a href=\"%s\">%s</a>.')
+    errormessage = _('%s is already registered by <a href="%s">%s</a>.')
 
     def _validate(self, specurl):
         TextLine._validate(self, specurl)
-        if (ISpecification.providedBy(self.context) and
-            specurl == self.context.specurl):
+        if (
+            ISpecification.providedBy(self.context)
+            and specurl == self.context.specurl
+        ):
             # The specurl wasn't changed
             return
 
@@ -155,8 +142,13 @@ class SpecURLField(TextLine):
         if specification is not None:
             specification_url = canonical_url(specification)
             raise LaunchpadValidationError(
-                    structured(self.errormessage, specurl, specification_url,
-                        specification.title))
+                structured(
+                    self.errormessage,
+                    specurl,
+                    specification_url,
+                    specification.title,
+                )
+            )
 
 
 class ISpecificationPublic(IPrivacy):
@@ -166,14 +158,19 @@ class ISpecificationPublic(IPrivacy):
 
     information_type = exported(
         Choice(
-            title=_('Information Type'), vocabulary=InformationType,
-            required=True, readonly=True, default=InformationType.PUBLIC,
+            title=_("Information Type"),
+            vocabulary=InformationType,
+            required=True,
+            readonly=True,
+            default=InformationType.PUBLIC,
             description=_(
-                'The type of information contained in this specification.')))
+                "The type of information contained in this specification."
+            ),
+        )
+    )
 
     def userCanView(user):
-        """Return True if `user` can see this ISpecification, false otherwise.
-        """
+        """Return True iff `user` can see this ISpecification."""
 
 
 class ISpecificationView(IHasOwner, IHasLinkedBranches):
@@ -183,126 +180,183 @@ class ISpecificationView(IHasOwner, IHasLinkedBranches):
 
     name = exported(
         SpecNameField(
-            title=_('Name'), required=True, readonly=False,
+            title=_("Name"),
+            required=True,
+            readonly=False,
             description=_(
                 "May contain lower-case letters, numbers, and dashes. "
                 "It will be used in the specification url. "
-                "Examples: mozilla-type-ahead-find, postgres-smart-serial.")),
-        as_of="devel")
+                "Examples: mozilla-type-ahead-find, postgres-smart-serial."
+            ),
+        ),
+        as_of="devel",
+    )
     title = exported(
         Title(
-            title=_('Title'), required=True, description=_(
+            title=_("Title"),
+            required=True,
+            description=_(
                 "Describe the feature as clearly as possible in up to 70 "
                 "characters. This title is displayed in every feature "
-                "list or report.")),
-        as_of="devel")
+                "list or report."
+            ),
+        ),
+        as_of="devel",
+    )
     specurl = exported(
         SpecURLField(
-            title=_('Specification URL'), required=False,
+            title=_("Specification URL"),
+            required=False,
             description=_(
-                "The URL of the specification. This is usually a wiki page."),
-            constraint=valid_webref),
+                "The URL of the specification. This is usually a wiki page."
+            ),
+            constraint=valid_webref,
+        ),
         exported_as="specification_url",
         as_of="devel",
-        )
+    )
     summary = exported(
         Summary(
-            title=_('Summary'), required=True, description=_(
+            title=_("Summary"),
+            required=True,
+            description=_(
                 "A single-paragraph description of the feature. "
-                "This will also be displayed in most feature listings.")),
-        as_of="devel")
+                "This will also be displayed in most feature listings."
+            ),
+        ),
+        as_of="devel",
+    )
 
     definition_status = exported(
         Choice(
-            title=_('Definition Status'), readonly=True,
+            title=_("Definition Status"),
+            readonly=True,
             vocabulary=SpecificationDefinitionStatus,
             default=SpecificationDefinitionStatus.NEW,
             description=_(
                 "The current status of the process to define the "
-                "feature and get approval for the implementation plan.")),
-        as_of="devel")
+                "feature and get approval for the implementation plan."
+            ),
+        ),
+        as_of="devel",
+    )
 
     assignee = exported(
         PublicPersonChoice(
-            title=_('Assignee'), required=False,
+            title=_("Assignee"),
+            required=False,
             description=_(
-                "The person responsible for implementing the feature."),
-            vocabulary='ValidPersonOrTeam'),
-        as_of="devel")
-    assigneeID = Attribute('db assignee value')
+                "The person responsible for implementing the feature."
+            ),
+            vocabulary="ValidPersonOrTeam",
+        ),
+        as_of="devel",
+    )
+    assigneeID = Attribute("db assignee value")
     drafter = exported(
         PublicPersonChoice(
-            title=_('Drafter'), required=False,
+            title=_("Drafter"),
+            required=False,
             description=_(
-                    "The person responsible for drafting the specification."),
-                vocabulary='ValidPersonOrTeam'),
-        as_of="devel")
-    drafterID = Attribute('db drafter value')
+                "The person responsible for drafting the specification."
+            ),
+            vocabulary="ValidPersonOrTeam",
+        ),
+        as_of="devel",
+    )
+    drafterID = Attribute("db drafter value")
     approver = exported(
         PublicPersonChoice(
-            title=_('Approver'), required=False,
+            title=_("Approver"),
+            required=False,
             description=_(
                 "The person responsible for approving the specification, "
-                "and for reviewing the code when it's ready to be landed."),
-            vocabulary='ValidPersonOrTeam'),
-        as_of="devel")
-    approverID = Attribute('db approver value')
+                "and for reviewing the code when it's ready to be landed."
+            ),
+            vocabulary="ValidPersonOrTeam",
+        ),
+        as_of="devel",
+    )
+    approverID = Attribute("db approver value")
 
     priority = exported(
         Choice(
-            title=_('Priority'), vocabulary=SpecificationPriority,
-            default=SpecificationPriority.UNDEFINED, required=True),
-        as_of="devel")
+            title=_("Priority"),
+            vocabulary=SpecificationPriority,
+            default=SpecificationPriority.UNDEFINED,
+            required=True,
+        ),
+        as_of="devel",
+    )
     datecreated = exported(
-        Datetime(
-            title=_('Date Created'), required=True, readonly=True),
+        Datetime(title=_("Date Created"), required=True, readonly=True),
         as_of="devel",
         exported_as="date_created",
-        )
+    )
     owner = exported(
         PublicPersonChoice(
-            title=_('Owner'), required=True, readonly=True,
-            vocabulary='ValidPersonOrTeam'),
-        as_of="devel")
+            title=_("Owner"),
+            required=True,
+            readonly=True,
+            vocabulary="ValidPersonOrTeam",
+        ),
+        as_of="devel",
+    )
 
-    product = Choice(title=_('Project'), required=False,
-                     vocabulary='Product')
-    distribution = Choice(title=_('Distribution'), required=False,
-                          vocabulary='Distribution')
+    product = Choice(title=_("Project"), required=False, vocabulary="Product")
+    distribution = Choice(
+        title=_("Distribution"), required=False, vocabulary="Distribution"
+    )
 
     # Exported as readonly for simplicity, but could be exported as read-write
     # using setTarget() as the mutator.
     target = exported(
         ReferenceChoice(
-            title=_('For'), required=True, readonly=True,
-            vocabulary='DistributionOrProduct',
+            title=_("For"),
+            required=True,
+            readonly=True,
+            vocabulary="DistributionOrProduct",
             description=_(
-                "The project for which this proposal is being made."),
-            schema=ISpecificationTarget),
-        as_of="devel")
+                "The project for which this proposal is being made."
+            ),
+            schema=ISpecificationTarget,
+        ),
+        as_of="devel",
+    )
 
     productseries = Choice(
-        title=_('Series Goal'), required=False,
-        vocabulary='FilteredProductSeries',
+        title=_("Series Goal"),
+        required=False,
+        vocabulary="FilteredProductSeries",
         description=_(
-             "Choose a series in which you would like to deliver this "
-             "feature. Selecting '(nothing selected)' will clear the goal."))
+            "Choose a series in which you would like to deliver this "
+            "feature. Selecting '(nothing selected)' will clear the goal."
+        ),
+    )
     distroseries = Choice(
-        title=_('Series Goal'), required=False,
-        vocabulary='FilteredDistroSeries',
+        title=_("Series Goal"),
+        required=False,
+        vocabulary="FilteredDistroSeries",
         description=_(
-             "Choose a series in which you would like to deliver this "
-             "feature. Selecting '(nothing selected)' will clear the goal."))
+            "Choose a series in which you would like to deliver this "
+            "feature. Selecting '(nothing selected)' will clear the goal."
+        ),
+    )
 
     # milestone
     milestone = exported(
         ReferenceChoice(
-            title=_('Milestone'), required=False, vocabulary='Milestone',
+            title=_("Milestone"),
+            required=False,
+            vocabulary="Milestone",
             description=_(
                 "The milestone in which we would like this feature to be "
-                "delivered."),
-            schema=IMilestone),
-        as_of="devel")
+                "delivered."
+            ),
+            schema=IMilestone,
+        ),
+        as_of="devel",
+    )
 
     # nomination to a series for release management
     # XXX: It'd be nice to export goal as read-only, but it's tricky because
@@ -310,116 +364,184 @@ class ISpecificationView(IHasOwner, IHasLinkedBranches):
     # may not be the accepted goal.
     goal = Attribute("The series for which this feature is a goal.")
     goalstatus = Choice(
-        title=_('Goal Acceptance'), vocabulary=SpecificationGoalStatus,
-        default=SpecificationGoalStatus.PROPOSED, description=_(
+        title=_("Goal Acceptance"),
+        vocabulary=SpecificationGoalStatus,
+        default=SpecificationGoalStatus.PROPOSED,
+        description=_(
             "Whether or not the drivers have accepted this feature as "
-            "a goal for the targeted series."))
-    goal_proposer = Attribute("The person who nominated the spec for "
-        "this series.")
+            "a goal for the targeted series."
+        ),
+    )
+    goal_proposer = Attribute(
+        "The person who nominated the spec for " "this series."
+    )
     date_goal_proposed = Attribute("The date of the nomination.")
-    goal_decider = Attribute("The person who approved or declined "
-        "the spec a a goal.")
-    date_goal_decided = Attribute("The date the spec was approved "
-        "or declined as a goal.")
+    goal_decider = Attribute(
+        "The person who approved or declined " "the spec a a goal."
+    )
+    date_goal_decided = Attribute(
+        "The date the spec was approved " "or declined as a goal."
+    )
 
     work_items = List(
-        description=_("All non-deleted work items for this spec, sorted by "
-                      "their 'sequence'"),
-        value_type=Reference(schema=ISpecificationWorkItem), readonly=True)
+        description=_(
+            "All non-deleted work items for this spec, sorted by "
+            "their 'sequence'"
+        ),
+        value_type=Reference(schema=ISpecificationWorkItem),
+        readonly=True,
+    )
     whiteboard = exported(
-        Text(title=_('Status Whiteboard'), required=False,
-             description=_(
+        Text(
+            title=_("Status Whiteboard"),
+            required=False,
+            description=_(
                 "Any notes on the status of this spec you would like to "
-                "make. Your changes will override the current text.")),
-        as_of="devel")
+                "make. Your changes will override the current text."
+            ),
+        ),
+        as_of="devel",
+    )
     workitems_text = exported(
         WorkItemsText(
-            title=_('Work Items'), required=False, readonly=True,
+            title=_("Work Items"),
+            required=False,
+            readonly=True,
             description=_(
                 "Work items for this specification input in a text format. "
-                "Your changes will override the current work items.")),
-        as_of="devel")
+                "Your changes will override the current work items."
+            ),
+        ),
+        as_of="devel",
+    )
     direction_approved = exported(
-        Bool(title=_('Basic direction approved?'),
-             required=True, default=False,
-             description=_(
+        Bool(
+            title=_("Basic direction approved?"),
+            required=True,
+            default=False,
+            description=_(
                 "Check this to indicate that the drafter and assignee "
                 "have satisfied the approver that they are headed in "
-                "the right basic direction with this specification.")),
-        as_of="devel")
-    man_days = Int(title=_("Estimated Developer Days"),
-        required=False, default=None, description=_("An estimate of the "
-        "number of developer days it will take to implement this feature. "
-        "Please only provide an estimate if you are relatively confident "
-        "in the number."))
+                "the right basic direction with this specification."
+            ),
+        ),
+        as_of="devel",
+    )
+    man_days = Int(
+        title=_("Estimated Developer Days"),
+        required=False,
+        default=None,
+        description=_(
+            "An estimate of the "
+            "number of developer days it will take to implement this feature. "
+            "Please only provide an estimate if you are relatively confident "
+            "in the number."
+        ),
+    )
     implementation_status = exported(
         Choice(
-            title=_("Implementation Status"), required=True, readonly=True,
+            title=_("Implementation Status"),
+            required=True,
+            readonly=True,
             default=SpecificationImplementationStatus.UNKNOWN,
             vocabulary=SpecificationImplementationStatus,
             description=_(
                 "The state of progress being made on the actual "
-                "implementation or delivery of this feature.")),
-        as_of="devel")
-    superseded_by = Choice(title=_("Superseded by"),
-        required=False, default=None,
-        vocabulary='Specification', description=_("The specification "
-        "which supersedes this one. Note that selecting a specification "
-        "here and pressing Continue will change the specification "
-        "status to Superseded."))
+                "implementation or delivery of this feature."
+            ),
+        ),
+        as_of="devel",
+    )
+    superseded_by = Choice(
+        title=_("Superseded by"),
+        required=False,
+        default=None,
+        vocabulary="Specification",
+        description=_(
+            "The specification "
+            "which supersedes this one. Note that selecting a specification "
+            "here and pressing Continue will change the specification "
+            "status to Superseded."
+        ),
+    )
 
     # lifecycle
     starter = exported(
         PublicPersonChoice(
-            title=_('Starter'), required=False, readonly=True,
+            title=_("Starter"),
+            required=False,
+            readonly=True,
             description=_(
-                'The person who first set the state of the '
-                'spec to the values that we consider mark it as started.'),
-            vocabulary='ValidPersonOrTeam'),
-        as_of="devel")
+                "The person who first set the state of the "
+                "spec to the values that we consider mark it as started."
+            ),
+            vocabulary="ValidPersonOrTeam",
+        ),
+        as_of="devel",
+    )
     date_started = exported(
         Datetime(
-            title=_('Date Started'), required=False, readonly=True,
-            description=_('The date when this spec was marked started.')),
-        as_of="devel")
+            title=_("Date Started"),
+            required=False,
+            readonly=True,
+            description=_("The date when this spec was marked started."),
+        ),
+        as_of="devel",
+    )
 
     completer = exported(
         PublicPersonChoice(
-            title=_('Starter'), required=False, readonly=True,
+            title=_("Starter"),
+            required=False,
+            readonly=True,
             description=_(
-            'The person who finally set the state of the '
-            'spec to the values that we consider mark it as complete.'),
-            vocabulary='ValidPersonOrTeam'),
-        as_of="devel")
+                "The person who finally set the state of the "
+                "spec to the values that we consider mark it as complete."
+            ),
+            vocabulary="ValidPersonOrTeam",
+        ),
+        as_of="devel",
+    )
 
     date_completed = exported(
         Datetime(
-            title=_('Date Completed'), required=False, readonly=True,
+            title=_("Date Completed"),
+            required=False,
+            readonly=True,
             description=_(
-                'The date when this spec was marked '
+                "The date when this spec was marked "
                 'complete. Note that complete also includes "obsolete" and '
-                'superseded. Essentially, it is the state where no more work '
-                'will be done on the feature.')),
-        as_of="devel")
+                "superseded. Essentially, it is the state where no more work "
+                "will be done on the feature."
+            ),
+        ),
+        as_of="devel",
+    )
 
     # joins
-    subscriptions = Attribute('The set of subscriptions to this spec.')
-    subscribers = Attribute('The set of subscribers to this spec.')
-    sprints = Attribute('The sprints at which this spec is discussed.')
-    sprint_links = Attribute('The entries that link this spec to sprints.')
+    subscriptions = Attribute("The set of subscriptions to this spec.")
+    subscribers = Attribute("The set of subscribers to this spec.")
+    sprints = Attribute("The sprints at which this spec is discussed.")
+    sprint_links = Attribute("The entries that link this spec to sprints.")
     dependencies = exported(
         CollectionField(
-            title=_('Specs on which this one depends.'),
+            title=_("Specs on which this one depends."),
             value_type=Reference(schema=Interface),  # ISpecification, really.
-            readonly=True),
-        as_of="devel")
+            readonly=True,
+        ),
+        as_of="devel",
+    )
     linked_branches = exported(
         CollectionField(
-            title=_("Branches associated with this spec, usually "
-            "branches on which this spec is being implemented."),
+            title=_(
+                "Branches associated with this spec, usually "
+                "branches on which this spec is being implemented."
+            ),
             value_type=Reference(schema=Interface),  # ISpecificationBranch
-            readonly=True),
-        as_of="devel")
+            readonly=True,
+        ),
+        as_of="devel",
+    )
 
     def getDependencies():
         """Specs on which this one depends."""
@@ -428,47 +550,66 @@ class ISpecificationView(IHasOwner, IHasLinkedBranches):
         """Specs for which this spec is a dependency."""
 
     # emergent properties
-    informational = Attribute('Is True if this spec is purely informational '
-        'and requires no implementation.')
+    informational = Attribute(
+        "Is True if this spec is purely informational "
+        "and requires no implementation."
+    )
     is_complete = exported(
-        Bool(title=_('Is started'),
-             readonly=True, required=True,
-             description=_(
-                'Is True if this spec is already completely implemented. '
-                'Note that it is True for informational specs, since '
-                'they describe general functionality rather than specific '
-                'code to be written. It is also true of obsolete and '
-                'superseded specs, since there is no longer any need '
-                'to schedule work for them.')),
-        as_of="devel")
-
-    is_incomplete = Attribute('Is True if this work still needs to '
-        'be done. Is in fact always the opposite of is_complete.')
-    is_blocked = Attribute('Is True if this spec depends on another spec '
-        'which is still incomplete.')
+        Bool(
+            title=_("Is started"),
+            readonly=True,
+            required=True,
+            description=_(
+                "Is True if this spec is already completely implemented. "
+                "Note that it is True for informational specs, since "
+                "they describe general functionality rather than specific "
+                "code to be written. It is also true of obsolete and "
+                "superseded specs, since there is no longer any need "
+                "to schedule work for them."
+            ),
+        ),
+        as_of="devel",
+    )
+
+    is_incomplete = Attribute(
+        "Is True if this work still needs to "
+        "be done. Is in fact always the opposite of is_complete."
+    )
+    is_blocked = Attribute(
+        "Is True if this spec depends on another spec "
+        "which is still incomplete."
+    )
     is_started = exported(
-        Bool(title=_('Is started'),
-             readonly=True, required=True,
-             description=_(
-                'Is True if the spec is in a state which '
+        Bool(
+            title=_("Is started"),
+            readonly=True,
+            required=True,
+            description=_(
+                "Is True if the spec is in a state which "
                 'we consider to be "started". This looks at the delivery '
-                'attribute, and also considers informational specs to be '
-                'started when they are approved.')),
-        as_of="devel")
+                "attribute, and also considers informational specs to be "
+                "started when they are approved."
+            ),
+        ),
+        as_of="devel",
+    )
 
     lifecycle_status = exported(
         Choice(
-            title=_('Lifecycle Status'),
+            title=_("Lifecycle Status"),
             vocabulary=SpecificationLifecycleStatus,
             default=SpecificationLifecycleStatus.NOTSTARTED,
-            readonly=True),
-        as_of="devel")
+            readonly=True,
+        ),
+        as_of="devel",
+    )
 
     def all_deps():
         """All the dependencies, including dependencies of dependencies.
 
         If a user is provided, filters to only dependencies the user can see.
         """
+
     def all_blocked():
         """All specs blocked on this, and those blocked on the blocked ones.
 
@@ -486,13 +627,18 @@ class ISpecificationView(IHasOwner, IHasLinkedBranches):
         """Return the list of email addresses that receive notifications."""
 
     has_accepted_goal = exported(
-        Bool(title=_('Series goal is accepted'),
-             readonly=True, required=True,
-             description=_(
-                'Is true if this specification has been '
-                'proposed as a goal for a specific series, '
-                'and the drivers of that series have accepted the goal.')),
-        as_of="devel")
+        Bool(
+            title=_("Series goal is accepted"),
+            readonly=True,
+            required=True,
+            description=_(
+                "Is true if this specification has been "
+                "proposed as a goal for a specific series, "
+                "and the drivers of that series have accepted the goal."
+            ),
+        ),
+        as_of="devel",
+    )
 
     # lifecycle management
     def updateLifecycleStatus(user):
@@ -522,20 +668,23 @@ class ISpecificationView(IHasOwner, IHasLinkedBranches):
         """Return the subscription for this person to this spec, or None."""
 
     @operation_parameters(
-        person=Reference(IPerson, title=_('Person'), required=True),
+        person=Reference(IPerson, title=_("Person"), required=True),
         essential=copy_field(
-            ISpecificationSubscription['essential'], required=False))
+            ISpecificationSubscription["essential"], required=False
+        ),
+    )
     @call_with(subscribed_by=REQUEST_USER)
     @export_write_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def subscribe(person, subscribed_by=None, essential=False):
         """Subscribe this person to the feature specification."""
 
     @operation_parameters(
-        person=Reference(IPerson, title=_('Person'), required=False))
+        person=Reference(IPerson, title=_("Person"), required=False)
+    )
     @call_with(unsubscribed_by=REQUEST_USER)
     @export_write_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def unsubscribe(person, unsubscribed_by):
         """Remove the person's subscription to this spec."""
 
@@ -584,32 +733,37 @@ class ISpecificationView(IHasOwner, IHasLinkedBranches):
 
 
 class ISpecificationEditRestricted(Interface):
-    """Specification's attributes and methods protected with launchpad.Edit.
-    """
+    """Specification's attributes and methods protected with launchpad.Edit."""
 
-    @mutator_for(ISpecificationView['definition_status'])
+    @mutator_for(ISpecificationView["definition_status"])
     @call_with(user=REQUEST_USER)
     @operation_parameters(
-        definition_status=copy_field(
-            ISpecificationView['definition_status']))
+        definition_status=copy_field(ISpecificationView["definition_status"])
+    )
     @export_write_operation()
     @operation_for_version("devel")
     def setDefinitionStatus(definition_status, user):
         """Mutator for definition_status that calls updateLifeCycle."""
 
-    @mutator_for(ISpecificationView['implementation_status'])
+    @mutator_for(ISpecificationView["implementation_status"])
     @call_with(user=REQUEST_USER)
     @operation_parameters(
         implementation_status=copy_field(
-            ISpecificationView['implementation_status']))
+            ISpecificationView["implementation_status"]
+        )
+    )
     @export_write_operation()
     @operation_for_version("devel")
     def setImplementationStatus(implementation_status, user):
         """Mutator for implementation_status that calls updateLifeCycle."""
 
-    def newWorkItem(title, sequence,
-                    status=SpecificationWorkItemStatus.TODO, assignee=None,
-                    milestone=None):
+    def newWorkItem(
+        title,
+        sequence,
+        status=SpecificationWorkItemStatus.TODO,
+        assignee=None,
+        milestone=None,
+    ):
         """Create a new SpecificationWorkItem."""
 
     def updateWorkItems(new_work_items):
@@ -632,31 +786,32 @@ class ISpecificationEditRestricted(Interface):
         :param target: an IProduct or IDistribution.
         """
 
-    @mutator_for(ISpecificationView['target'])
-    @operation_parameters(
-        target=copy_field(ISpecificationView['target']))
+    @mutator_for(ISpecificationView["target"])
+    @operation_parameters(target=copy_field(ISpecificationView["target"]))
     @export_write_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def retarget(target):
         """Move the spec to the given target.
 
         The new target must be an IProduct or IDistribution.
         """
 
-    @mutator_for(ISpecificationPublic['information_type'])
+    @mutator_for(ISpecificationPublic["information_type"])
     @call_with(who=REQUEST_USER)
     @operation_parameters(
-        information_type=copy_field(ISpecificationPublic['information_type']))
+        information_type=copy_field(ISpecificationPublic["information_type"])
+    )
     @export_write_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def transitionToInformationType(information_type, who):
         """Change the information type of the Specification."""
 
     @call_with(proposer=REQUEST_USER)
     @operation_parameters(
         goal=Reference(
-            schema=IBugTarget, title=_('Target'),
-            required=False, default=None))
+            schema=IBugTarget, title=_("Target"), required=False, default=None
+        )
+    )
     @export_write_operation()
     @operation_for_version("devel")
     def proposeGoal(goal, proposer):
@@ -667,14 +822,14 @@ class ISpecificationDriverRestricted(Interface):
     """Specification bits protected with launchpad.Driver."""
 
     @call_with(decider=REQUEST_USER)
-    @export_operation_as('acceptGoal')
+    @export_operation_as("acceptGoal")
     @export_write_operation()
     @operation_for_version("devel")
     def acceptBy(decider):
         """Mark the spec as being accepted for its current series goal."""
 
     @call_with(decider=REQUEST_USER)
-    @export_operation_as('declineGoal')
+    @export_operation_as("declineGoal")
     @export_write_operation()
     @operation_for_version("devel")
     def declineBy(decider):
@@ -684,15 +839,19 @@ class ISpecificationDriverRestricted(Interface):
 
 
 @exported_as_webservice_entry(as_of="beta")
-class ISpecification(ISpecificationPublic, ISpecificationView,
-                     ISpecificationEditRestricted,
-                     ISpecificationDriverRestricted, IBugLinkTarget):
+class ISpecification(
+    ISpecificationPublic,
+    ISpecificationView,
+    ISpecificationEditRestricted,
+    ISpecificationDriverRestricted,
+    IBugLinkTarget,
+):
     """A Specification."""
 
-    @mutator_for(ISpecificationView['workitems_text'])
+    @mutator_for(ISpecificationView["workitems_text"])
     @operation_parameters(new_work_items=WorkItemsText())
     @export_write_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def setWorkItems(new_work_items):
         """Set work items on this specification.
 
@@ -700,10 +859,9 @@ class ISpecification(ISpecificationPublic, ISpecificationView,
         """
 
     @call_with(user=REQUEST_USER)
-    @operation_parameters(
-        bug=Reference(schema=Interface))  # Really IBug
+    @operation_parameters(bug=Reference(schema=Interface))  # Really IBug
     @export_write_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def linkBug(bug, user=None, check_permissions=True):
         """Link a bug to this specification.
 
@@ -711,10 +869,9 @@ class ISpecification(ISpecificationPublic, ISpecificationView,
         """
 
     @call_with(user=REQUEST_USER)
-    @operation_parameters(
-        bug=Reference(schema=Interface))  # Really IBug
+    @operation_parameters(bug=Reference(schema=Interface))  # Really IBug
     @export_write_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def unlinkBug(bug, user=None, check_permissions=True):
         """Unlink a bug to this specification.
 
@@ -730,9 +887,9 @@ class ISpecificationSet(IHasSpecifications):
     def empty_list():
         """Return an empty set - only exists to keep lazr.restful happy."""
 
-    displayname = Attribute('Displayname')
+    displayname = Attribute("Displayname")
 
-    title = Attribute('Title')
+    title = Attribute("Title")
 
     coming_sprints = Attribute("The next 5 sprints in the system.")
 
@@ -753,24 +910,47 @@ class ISpecificationSet(IHasSpecifications):
         """Return the specification with the given url."""
 
     def getByName(pillar, name):
-        """Return the specification with the given name for the given pillar.
-        """
+        """Return the specification with the given name for this pillar."""
 
     @call_with(owner=REQUEST_USER)
-    @export_operation_as('createSpecification')
+    @export_operation_as("createSpecification")
     @operation_parameters(
         target=Reference(
-            schema=ISpecificationTarget, required=True,
-            title=("The product or distribution context of this "
-                   "specification.")))
+            schema=ISpecificationTarget,
+            required=True,
+            title=(
+                "The product or distribution context of this " "specification."
+            ),
+        )
+    )
     @export_factory_operation(
-        ISpecification, ['name', 'title', 'specurl', 'summary',
-                         'definition_status', 'assignee', 'drafter',
-                         'whiteboard'])
-    @operation_for_version('devel')
-    def new(name, title, specurl, summary, definition_status, owner,
-            target, approver=None, assignee=None, drafter=None,
-            whiteboard=None, information_type=None):
+        ISpecification,
+        [
+            "name",
+            "title",
+            "specurl",
+            "summary",
+            "definition_status",
+            "assignee",
+            "drafter",
+            "whiteboard",
+        ],
+    )
+    @operation_for_version("devel")
+    def new(
+        name,
+        title,
+        specurl,
+        summary,
+        definition_status,
+        owner,
+        target,
+        approver=None,
+        assignee=None,
+        drafter=None,
+        whiteboard=None,
+        information_type=None,
+    ):
         """Create a new specification."""
 
     def getDependencyDict(specifications):
diff --git a/lib/lp/blueprints/interfaces/specificationbranch.py b/lib/lp/blueprints/interfaces/specificationbranch.py
index ff08cd0..151cebf 100644
--- a/lib/lp/blueprints/interfaces/specificationbranch.py
+++ b/lib/lp/blueprints/interfaces/specificationbranch.py
@@ -6,7 +6,7 @@
 __all__ = [
     "ISpecificationBranch",
     "ISpecificationBranchSet",
-    ]
+]
 
 from lazr.restful.declarations import (
     export_operation_as,
@@ -14,15 +14,9 @@ from lazr.restful.declarations import (
     exported,
     exported_as_webservice_entry,
     operation_for_version,
-    )
-from lazr.restful.fields import (
-    Reference,
-    ReferenceChoice,
-    )
-from zope.interface import (
-    Attribute,
-    Interface,
-    )
+)
+from lazr.restful.fields import Reference, ReferenceChoice
+from zope.interface import Attribute, Interface
 from zope.schema import Int
 
 from lp import _
@@ -38,26 +32,38 @@ class ISpecificationBranch(Interface):
     id = Int(title=_("Specification Branch #"))
     specification = exported(
         ReferenceChoice(
-            title=_("Blueprint"), vocabulary="Specification",
+            title=_("Blueprint"),
+            vocabulary="Specification",
             required=True,
-            readonly=True, schema=ISpecification), as_of="beta")
+            readonly=True,
+            schema=ISpecification,
+        ),
+        as_of="beta",
+    )
     branch = exported(
         ReferenceChoice(
             title=_("Branch"),
             vocabulary="Branch",
             required=True,
-            schema=IBranch), as_of="beta")
+            schema=IBranch,
+        ),
+        as_of="beta",
+    )
 
     datecreated = Attribute("The date on which I was created.")
     registrant = exported(
         Reference(
-            schema=IPerson, readonly=True, required=True,
-            title=_("The person who linked the bug to the branch")),
-        as_of="beta")
+            schema=IPerson,
+            readonly=True,
+            required=True,
+            title=_("The person who linked the bug to the branch"),
+        ),
+        as_of="beta",
+    )
 
-    @export_operation_as('delete')
+    @export_operation_as("delete")
     @export_write_operation()
-    @operation_for_version('beta')
+    @operation_for_version("beta")
     def destroySelf():
         """Destroy this specification branch link"""
 
diff --git a/lib/lp/blueprints/interfaces/specificationdependency.py b/lib/lp/blueprints/interfaces/specificationdependency.py
index 3cbe343..8198ecb 100644
--- a/lib/lp/blueprints/interfaces/specificationdependency.py
+++ b/lib/lp/blueprints/interfaces/specificationdependency.py
@@ -7,19 +7,13 @@ order in which specs must be implemented. No attempt is made to prevent
 circular dependencies at present."""
 
 __all__ = [
-    'ISpecificationDependency',
-    'ISpecificationDependencyRemoval',
-    'SpecDependencyIsAlsoRemoval',
-    ]
-
-from zope.interface import (
-    implementer,
-    Interface,
-    )
-from zope.schema import (
-    Choice,
-    Int,
-    )
+    "ISpecificationDependency",
+    "ISpecificationDependencyRemoval",
+    "SpecDependencyIsAlsoRemoval",
+]
+
+from zope.interface import Interface, implementer
+from zope.schema import Choice, Int
 
 from lp import _
 
@@ -29,10 +23,15 @@ class ISpecificationDependency(Interface):
     depends.
     """
 
-    specification = Int(title=_('Specification ID'), required=True,
-        readonly=True)
-    dependency = Choice(title=_('Depends On'), required=True, readonly=True,
-        vocabulary='SpecificationDepCandidates')
+    specification = Int(
+        title=_("Specification ID"), required=True, readonly=True
+    )
+    dependency = Choice(
+        title=_("Depends On"),
+        required=True,
+        readonly=True,
+        vocabulary="SpecificationDepCandidates",
+    )
 
 
 class ISpecificationDependencyRemoval(Interface):
@@ -40,17 +39,23 @@ class ISpecificationDependencyRemoval(Interface):
     specification dependency removal form.
     """
 
-    specification = Int(title=_('Specification ID'), required=True,
-        readonly=True)
-    dependency = Choice(title=_('Dependency'), required=True, readonly=True,
-        description=_("Please select the dependency you would like to "
-        "remove from the list."),
-        vocabulary='SpecificationDependencies')
+    specification = Int(
+        title=_("Specification ID"), required=True, readonly=True
+    )
+    dependency = Choice(
+        title=_("Dependency"),
+        required=True,
+        readonly=True,
+        description=_(
+            "Please select the dependency you would like to "
+            "remove from the list."
+        ),
+        vocabulary="SpecificationDependencies",
+    )
 
 
 @implementer(ISpecificationDependencyRemoval)
 class SpecDependencyIsAlsoRemoval:
-
     def __init__(self, specdep):
         self.specdep = specdep
 
diff --git a/lib/lp/blueprints/interfaces/specificationmessage.py b/lib/lp/blueprints/interfaces/specificationmessage.py
index 0148f5d..e2dafea 100644
--- a/lib/lp/blueprints/interfaces/specificationmessage.py
+++ b/lib/lp/blueprints/interfaces/specificationmessage.py
@@ -4,9 +4,9 @@
 """Specification message interfaces."""
 
 __all__ = [
-    'ISpecificationMessage',
-    'ISpecificationMessageSet',
-    ]
+    "ISpecificationMessage",
+    "ISpecificationMessageSet",
+]
 
 from lazr.restful.fields import Reference
 from zope.interface import Interface
@@ -19,11 +19,13 @@ from lp.services.messages.interfaces.message import IMessage
 class ISpecificationMessage(Interface):
     """A link between a specification and a message."""
 
-    specification = Reference(schema=ISpecification,
-        title="The specification.")
+    specification = Reference(
+        schema=ISpecification, title="The specification."
+    )
     message = Reference(schema=IMessage, title="The message.")
-    visible = Bool(title="Is this message visible?", required=False,
-        default=True)
+    visible = Bool(
+        title="Is this message visible?", required=False, default=True
+    )
 
 
 class ISpecificationMessageSet(Interface):
diff --git a/lib/lp/blueprints/interfaces/specificationsubscription.py b/lib/lp/blueprints/interfaces/specificationsubscription.py
index 9978d90..c727957 100644
--- a/lib/lp/blueprints/interfaces/specificationsubscription.py
+++ b/lib/lp/blueprints/interfaces/specificationsubscription.py
@@ -4,50 +4,50 @@
 """Specification subscription interfaces."""
 
 __all__ = [
-    'ISpecificationSubscription',
-    ]
+    "ISpecificationSubscription",
+]
 
 from lazr.restful.declarations import (
+    REQUEST_USER,
     call_with,
     export_read_operation,
     exported_as_webservice_entry,
     operation_for_version,
-    REQUEST_USER,
-    )
-from zope.interface import (
-    Attribute,
-    Interface,
-    )
-from zope.schema import (
-    Bool,
-    Int,
-    )
+)
+from zope.interface import Attribute, Interface
+from zope.schema import Bool, Int
 
 from lp import _
 from lp.services.fields import PersonChoice
 
 
-@exported_as_webservice_entry(publish_web_link=False, as_of='devel')
+@exported_as_webservice_entry(publish_web_link=False, as_of="devel")
 class ISpecificationSubscription(Interface):
     """A subscription for a person to a specification."""
 
-    id = Int(
-        title=_('ID'), required=True, readonly=True)
+    id = Int(title=_("ID"), required=True, readonly=True)
     person = PersonChoice(
-            title=_('Subscriber'), required=True,
-            vocabulary='ValidPersonOrTeam', readonly=True,
-            description=_(
-            'The person you would like to subscribe to this blueprint. '
-            'They will be notified of the subscription by email.')
-            )
-    personID = Attribute('db person value')
-    specification = Int(title=_('Specification'), required=True,
-        readonly=True)
-    specificationID = Attribute('db specification value')
-    essential = Bool(title=_('Participation essential'), required=True,
-        description=_('Check this if participation in the design of '
-        'the feature is essential.'),
-        default=False)
+        title=_("Subscriber"),
+        required=True,
+        vocabulary="ValidPersonOrTeam",
+        readonly=True,
+        description=_(
+            "The person you would like to subscribe to this blueprint. "
+            "They will be notified of the subscription by email."
+        ),
+    )
+    personID = Attribute("db person value")
+    specification = Int(title=_("Specification"), required=True, readonly=True)
+    specificationID = Attribute("db specification value")
+    essential = Bool(
+        title=_("Participation essential"),
+        required=True,
+        description=_(
+            "Check this if participation in the design of "
+            "the feature is essential."
+        ),
+        default=False,
+    )
 
     @call_with(user=REQUEST_USER)
     @export_read_operation()
diff --git a/lib/lp/blueprints/interfaces/specificationtarget.py b/lib/lp/blueprints/interfaces/specificationtarget.py
index 24bb08f..1eba6ae 100644
--- a/lib/lp/blueprints/interfaces/specificationtarget.py
+++ b/lib/lp/blueprints/interfaces/specificationtarget.py
@@ -4,10 +4,10 @@
 """Interfaces for things which have Specifications."""
 
 __all__ = [
-    'IHasSpecifications',
-    'ISpecificationTarget',
-    'ISpecificationGoal',
-    ]
+    "IHasSpecifications",
+    "ISpecificationTarget",
+    "ISpecificationGoal",
+]
 
 from lazr.lifecycle.snapshot import doNotSnapshot
 from lazr.restful.declarations import (
@@ -17,11 +17,8 @@ from lazr.restful.declarations import (
     operation_for_version,
     operation_parameters,
     operation_returns_entry,
-    )
-from lazr.restful.fields import (
-    CollectionField,
-    Reference,
-    )
+)
+from lazr.restful.fields import CollectionField, Reference
 from zope.interface import Interface
 from zope.schema import TextLine
 
@@ -35,30 +32,52 @@ class IHasSpecifications(Interface):
     associated with them, and you can use this interface to query those.
     """
 
-    visible_specifications = exported(doNotSnapshot(
-        CollectionField(
-            title=_("All specifications"),
-            value_type=Reference(schema=Interface),  # ISpecification, really.
-            readonly=True,
-            description=_(
-                'A list of all specifications, regardless of status or '
-                'approval or completion, for this object.'))),
-        exported_as="all_specifications", as_of="devel")
-
-    api_valid_specifications = exported(doNotSnapshot(
-        CollectionField(
-            title=_("Valid specifications"),
-            value_type=Reference(schema=Interface),  # ISpecification, really.
-            readonly=True,
-            description=_(
-                'All specifications that are not obsolete. When called from '
-                'an ISpecificationGoal it will also exclude the ones that '
-                'have not been accepted for that goal'))),
-        exported_as="valid_specifications", as_of="devel")
-
-    def specifications(user, quantity=None, sort=None, filter=None,
-                       need_people=True, need_branches=True,
-                       need_workitems=False):
+    visible_specifications = exported(
+        doNotSnapshot(
+            CollectionField(
+                title=_("All specifications"),
+                value_type=Reference(
+                    schema=Interface
+                ),  # ISpecification, really.
+                readonly=True,
+                description=_(
+                    "A list of all specifications, regardless of status or "
+                    "approval or completion, for this object."
+                ),
+            )
+        ),
+        exported_as="all_specifications",
+        as_of="devel",
+    )
+
+    api_valid_specifications = exported(
+        doNotSnapshot(
+            CollectionField(
+                title=_("Valid specifications"),
+                value_type=Reference(
+                    schema=Interface
+                ),  # ISpecification, really.
+                readonly=True,
+                description=_(
+                    "All specifications that are not obsolete. When called "
+                    "from an ISpecificationGoal it will also exclude the ones "
+                    "that have not been accepted for that goal"
+                ),
+            )
+        ),
+        exported_as="valid_specifications",
+        as_of="devel",
+    )
+
+    def specifications(
+        user,
+        quantity=None,
+        sort=None,
+        filter=None,
+        need_people=True,
+        need_branches=True,
+        need_workitems=False,
+    ):
         """Specifications for this target.
 
         The user specifies which user to use for calculation of visibility.
@@ -91,10 +110,11 @@ class ISpecificationTarget(IHasSpecifications):
     """
 
     @operation_parameters(
-        name=TextLine(title=_('The name of the specification')))
+        name=TextLine(title=_("The name of the specification"))
+    )
     @operation_returns_entry(Interface)  # really ISpecification
     @export_read_operation()
-    @operation_for_version('devel')
+    @operation_for_version("devel")
     def getSpecification(name):
         """Returns the specification with the given name, for this target,
         or None.
diff --git a/lib/lp/blueprints/interfaces/specificationworkitem.py b/lib/lp/blueprints/interfaces/specificationworkitem.py
index 604686c..9af5a74 100644
--- a/lib/lp/blueprints/interfaces/specificationworkitem.py
+++ b/lib/lp/blueprints/interfaces/specificationworkitem.py
@@ -4,25 +4,17 @@
 """SpecificationWorkItem interfaces."""
 
 __all__ = [
-    'ISpecificationWorkItem',
-    'ISpecificationWorkItemSet',
-    ]
+    "ISpecificationWorkItem",
+    "ISpecificationWorkItemSet",
+]
 
 
 from zope.interface import Interface
-from zope.schema import (
-    Bool,
-    Choice,
-    Datetime,
-    Int,
-    )
+from zope.schema import Bool, Choice, Datetime, Int
 
 from lp import _
 from lp.blueprints.enums import SpecificationWorkItemStatus
-from lp.services.fields import (
-    PublicPersonChoice,
-    Title,
-    )
+from lp.services.fields import PublicPersonChoice, Title
 
 
 class ISpecificationWorkItem(Interface):
@@ -31,54 +23,81 @@ class ISpecificationWorkItem(Interface):
     id = Int(title=_("Database ID"), required=True, readonly=True)
 
     title = Title(
-        title=_('Title'), required=True, readonly=False,
-        description=_("Work item title."))
+        title=_("Title"),
+        required=True,
+        readonly=False,
+        description=_("Work item title."),
+    )
 
     assignee = PublicPersonChoice(
-        title=_('Assignee'), required=False, readonly=False,
+        title=_("Assignee"),
+        required=False,
+        readonly=False,
         description=_(
-            "The person responsible for implementing the work item."),
-        vocabulary='ValidPersonOrTeam')
+            "The person responsible for implementing the work item."
+        ),
+        vocabulary="ValidPersonOrTeam",
+    )
 
     date_created = Datetime(
-        title=_('Date Created'), required=True, readonly=True)
+        title=_("Date Created"), required=True, readonly=True
+    )
 
     milestone = Choice(
-        title=_('Milestone'), required=False, readonly=False,
-        vocabulary='Milestone',
+        title=_("Milestone"),
+        required=False,
+        readonly=False,
+        vocabulary="Milestone",
         description=_(
             "The milestone to which this work item is targetted. If this "
             "is not set, then the target is the specification's "
-            "milestone."))
+            "milestone."
+        ),
+    )
 
     status = Choice(
-        title=_("Work Item Status"), required=True, readonly=False,
+        title=_("Work Item Status"),
+        required=True,
+        readonly=False,
         default=SpecificationWorkItemStatus.TODO,
         vocabulary=SpecificationWorkItemStatus,
         description=_(
             "The state of progress being made on the actual "
-            "implementation of this work item."))
+            "implementation of this work item."
+        ),
+    )
 
     specification = Choice(
-        title=_('The specification that the work item is linked to.'),
-        required=True, readonly=True, vocabulary='Specification')
+        title=_("The specification that the work item is linked to."),
+        required=True,
+        readonly=True,
+        vocabulary="Specification",
+    )
 
     deleted = Bool(
-        title=_('Is this work item deleted?'),
-        required=True, readonly=False, default=False,
-        description=_("Marks the work item as deleted."))
+        title=_("Is this work item deleted?"),
+        required=True,
+        readonly=False,
+        default=False,
+        description=_("Marks the work item as deleted."),
+    )
 
     sequence = Int(
         title=_("Work Item Sequence."),
-        required=True, description=_(
+        required=True,
+        description=_(
             "The sequence in which the work items are to be displayed in the "
-            "UI."))
+            "UI."
+        ),
+    )
 
     is_complete = Bool(
         readonly=True,
         description=_(
             "True or False depending on whether or not there is more "
-            "work required on this work item."))
+            "work required on this work item."
+        ),
+    )
 
 
 class ISpecificationWorkItemSet(Interface):
diff --git a/lib/lp/blueprints/interfaces/sprint.py b/lib/lp/blueprints/interfaces/sprint.py
index 213675f..d9d293c 100644
--- a/lib/lp/blueprints/interfaces/sprint.py
+++ b/lib/lp/blueprints/interfaces/sprint.py
@@ -8,40 +8,27 @@ some specific issues.
 """
 
 __all__ = [
-    'ISprint',
-    'IHasSprints',
-    'ISprintSet',
-    ]
+    "ISprint",
+    "IHasSprints",
+    "ISprintSet",
+]
 
 from zope.component import getUtility
-from zope.interface import (
-    Attribute,
-    Interface,
-    )
-from zope.schema import (
-    Bool,
-    Choice,
-    Datetime,
-    Int,
-    Text,
-    TextLine,
-    )
+from zope.interface import Attribute, Interface
+from zope.schema import Bool, Choice, Datetime, Int, Text, TextLine
 
 from lp import _
 from lp.app.interfaces.launchpad import IHeadingContext
 from lp.app.validators.name import name_validator
 from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
-from lp.registry.interfaces.role import (
-    IHasDrivers,
-    IHasOwner,
-    )
+from lp.registry.interfaces.role import IHasDrivers, IHasOwner
 from lp.services.fields import (
     ContentNameField,
     IconImageUpload,
     LogoImageUpload,
     MugshotImageUpload,
     PublicPersonChoice,
-    )
+)
 
 
 class SprintNameField(ContentNameField):
@@ -56,22 +43,27 @@ class SprintNameField(ContentNameField):
         return getUtility(ISprintSet)[name]
 
 
-class ISprintPublic(IHasOwner, IHasDrivers, IHasSpecifications,
-                    IHeadingContext):
+class ISprintPublic(
+    IHasOwner, IHasDrivers, IHasSpecifications, IHeadingContext
+):
     """`ISprint` attributes that anyone can view."""
 
-    id = Int(title=_('The Sprint ID'))
+    id = Int(title=_("The Sprint ID"))
 
-    displayname = Attribute('A pseudonym for the title.')
+    displayname = Attribute("A pseudonym for the title.")
     owner = PublicPersonChoice(
-        title=_('Owner'), required=True, readonly=True,
-        vocabulary='ValidPersonOrTeam')
+        title=_("Owner"),
+        required=True,
+        readonly=True,
+        vocabulary="ValidPersonOrTeam",
+    )
     datecreated = Datetime(
-        title=_('Date Created'), required=True, readonly=True)
+        title=_("Date Created"), required=True, readonly=True
+    )
 
     # joins
-    attendees = Attribute('The set of attendees at this sprint.')
-    attendances = Attribute('The set of SprintAttendance records.')
+    attendees = Attribute("The set of attendees at this sprint.")
+    attendances = Attribute("The set of SprintAttendance records.")
 
     def specificationLinks(status=None):
         """Return the SprintSpecification records matching the filter,
@@ -115,67 +107,111 @@ class ISprintEditableAttributes(Interface):
     """
 
     name = SprintNameField(
-        title=_('Name'), required=True, description=_('A unique name '
-        'for this sprint, or conference, or meeting. This will part of '
-        'the URL so pick something short. A single word is all you get.'),
-        constraint=name_validator)
+        title=_("Name"),
+        required=True,
+        description=_(
+            "A unique name "
+            "for this sprint, or conference, or meeting. This will part of "
+            "the URL so pick something short. A single word is all you get."
+        ),
+        constraint=name_validator,
+    )
     title = TextLine(
-        title=_('Title'), required=True, description=_("Please provide "
-        "a title for this meeting. This will be shown in listings of "
-        "meetings."))
+        title=_("Title"),
+        required=True,
+        description=_(
+            "Please provide "
+            "a title for this meeting. This will be shown in listings of "
+            "meetings."
+        ),
+    )
     summary = Text(
-        title=_('Summary'), required=True, description=_("A one-paragraph "
-        "summary of the meeting plans and goals. Put the rest in a web "
-        "page and link to it using the field below."))
+        title=_("Summary"),
+        required=True,
+        description=_(
+            "A one-paragraph "
+            "summary of the meeting plans and goals. Put the rest in a web "
+            "page and link to it using the field below."
+        ),
+    )
     driver = PublicPersonChoice(
-        title=_('Meeting Driver'), required=False,
-        description=_('The person or team that will manage the agenda of '
-        'this meeting. Use this if you want to delegate the approval of '
-        'agenda items to somebody else.'), vocabulary='ValidPersonOrTeam')
+        title=_("Meeting Driver"),
+        required=False,
+        description=_(
+            "The person or team that will manage the agenda of "
+            "this meeting. Use this if you want to delegate the approval of "
+            "agenda items to somebody else."
+        ),
+        vocabulary="ValidPersonOrTeam",
+    )
     address = Text(
-        title=_('Meeting Address'), required=False,
-        description=_("The address of the meeting venue."))
+        title=_("Meeting Address"),
+        required=False,
+        description=_("The address of the meeting venue."),
+    )
     home_page = TextLine(
-        title=_('Home Page'), required=False, description=_("A web page "
-        "with further information about the event."))
+        title=_("Home Page"),
+        required=False,
+        description=_(
+            "A web page " "with further information about the event."
+        ),
+    )
     icon = IconImageUpload(
-        title=_("Icon"), required=False,
-        default_image_resource='/@@/meeting',
+        title=_("Icon"),
+        required=False,
+        default_image_resource="/@@/meeting",
         description=_(
             "A small image of exactly 14x14 pixels and at most 5kb in size, "
             "that can be used to identify this meeting. The icon will be "
-            "displayed wherever we list and link to the meeting."))
+            "displayed wherever we list and link to the meeting."
+        ),
+    )
     logo = LogoImageUpload(
-        title=_("Logo"), required=False,
-        default_image_resource='/@@/meeting-logo',
+        title=_("Logo"),
+        required=False,
+        default_image_resource="/@@/meeting-logo",
         description=_(
             "An image of exactly 64x64 pixels that will be displayed in "
             "the heading of all pages related to this meeting. It should be "
-            "no bigger than 50kb in size."))
+            "no bigger than 50kb in size."
+        ),
+    )
     mugshot = MugshotImageUpload(
-        title=_("Brand"), required=False,
-        default_image_resource='/@@/meeting-mugshot',
+        title=_("Brand"),
+        required=False,
+        default_image_resource="/@@/meeting-mugshot",
         description=_(
             "A large image of exactly 192x192 pixels, that will be displayed "
             "on this meeting's home page in Launchpad. It should be no "
-            "bigger than 100kb in size. "))
+            "bigger than 100kb in size. "
+        ),
+    )
     homepage_content = Text(
-        title=_("Homepage Content"), required=False,
+        title=_("Homepage Content"),
+        required=False,
         description=_(
             "The content of this meeting's home page. Edit this and it "
             "will be displayed for all the world to see. It is NOT a wiki "
-            "so you cannot undo changes."))
+            "so you cannot undo changes."
+        ),
+    )
     time_zone = Choice(
-        title=_('Timezone'), required=True, description=_('The time '
-        'zone in which this sprint, or conference, takes place. '),
-        vocabulary='TimezoneName')
-    time_starts = Datetime(
-        title=_('Starting Date and Time'), required=True)
-    time_ends = Datetime(
-        title=_('Finishing Date and Time'), required=True)
+        title=_("Timezone"),
+        required=True,
+        description=_(
+            "The time "
+            "zone in which this sprint, or conference, takes place. "
+        ),
+        vocabulary="TimezoneName",
+    )
+    time_starts = Datetime(title=_("Starting Date and Time"), required=True)
+    time_ends = Datetime(title=_("Finishing Date and Time"), required=True)
     is_physical = Bool(
         title=_("Is the sprint being held in a physical location?"),
-        required=True, readonly=False, default=True)
+        required=True,
+        readonly=False,
+        default=True,
+    )
 
 
 class ISprintModerate(Interface):
@@ -199,8 +235,13 @@ class ISprintDriver(Interface):
         """
 
 
-class ISprint(ISprintPublic, ISprintAnyPerson, ISprintEditableAttributes,
-              ISprintModerate, ISprintDriver):
+class ISprint(
+    ISprintPublic,
+    ISprintAnyPerson,
+    ISprintEditableAttributes,
+    ISprintModerate,
+    ISprintDriver,
+):
     """A sprint, or conference, or meeting."""
 
 
@@ -213,7 +254,8 @@ class IHasSprints(Interface):
 
     coming_sprints = Attribute(
         "A list of up to 5 events currently on, or soon to be on, that are "
-        "relevant to this context.")
+        "relevant to this context."
+    )
 
     sprints = Attribute("All sprints relevant to this context.")
 
@@ -223,9 +265,9 @@ class IHasSprints(Interface):
 class ISprintSet(Interface):
     """A container for sprints."""
 
-    title = Attribute('Title')
+    title = Attribute("Title")
 
-    all = Attribute('All sprints, in reverse order of starting')
+    all = Attribute("All sprints, in reverse order of starting")
 
     def __iter__():
         """Iterate over all Sprints, in reverse time_start order."""
@@ -233,7 +275,19 @@ class ISprintSet(Interface):
     def __getitem__(name):
         """Get a specific Sprint."""
 
-    def new(owner, name, title, time_zone, time_starts, time_ends,
-            summary, address=None, driver=None, home_page=None,
-            mugshot=None, logo=None, icon=None):
+    def new(
+        owner,
+        name,
+        title,
+        time_zone,
+        time_starts,
+        time_ends,
+        summary,
+        address=None,
+        driver=None,
+        home_page=None,
+        mugshot=None,
+        logo=None,
+        icon=None,
+    ):
         """Create a new sprint."""
diff --git a/lib/lp/blueprints/interfaces/sprintattendance.py b/lib/lp/blueprints/interfaces/sprintattendance.py
index 92d8779..059ab7d 100644
--- a/lib/lp/blueprints/interfaces/sprintattendance.py
+++ b/lib/lp/blueprints/interfaces/sprintattendance.py
@@ -4,18 +4,11 @@
 """Sprint Attendance interfaces."""
 
 __all__ = [
-    'ISprintAttendance',
-    ]
+    "ISprintAttendance",
+]
 
-from zope.interface import (
-    Attribute,
-    Interface,
-    )
-from zope.schema import (
-    Bool,
-    Choice,
-    Datetime,
-    )
+from zope.interface import Attribute, Interface
+from zope.schema import Bool, Choice, Datetime
 
 from lp import _
 from lp.services.fields import PublicPersonChoice
@@ -25,22 +18,40 @@ class ISprintAttendance(Interface):
     """An attendance of a person at a sprint."""
 
     attendee = PublicPersonChoice(
-        title=_('Attendee'), required=True, vocabulary='ValidPersonOrTeam')
-    attendeeID = Attribute('db attendee value')
-    sprint = Choice(title=_('The Sprint'), required=True,
-        vocabulary='Sprint',
-        description=_("Select the meeting from the list presented above."))
-    time_starts = Datetime(title=_('From'), required=True,
-        description=_("The date and time of arrival and "
-        "availability for sessions during the sprint."))
-    time_ends = Datetime(title=_('To'), required=True,
-        description=_("The date and time of your departure. "
-        "Please ensure the time reflects accurately "
-        "when you will no longer be available for sessions at this event, to "
-        "assist those planning the schedule."))
+        title=_("Attendee"), required=True, vocabulary="ValidPersonOrTeam"
+    )
+    attendeeID = Attribute("db attendee value")
+    sprint = Choice(
+        title=_("The Sprint"),
+        required=True,
+        vocabulary="Sprint",
+        description=_("Select the meeting from the list presented above."),
+    )
+    time_starts = Datetime(
+        title=_("From"),
+        required=True,
+        description=_(
+            "The date and time of arrival and "
+            "availability for sessions during the sprint."
+        ),
+    )
+    time_ends = Datetime(
+        title=_("To"),
+        required=True,
+        description=_(
+            "The date and time of your departure. "
+            "Please ensure the time reflects accurately "
+            "when you will no longer be available for sessions at this event, "
+            "to assist those planning the schedule."
+        ),
+    )
     is_physical = Bool(
         title=_("How will you be attending?"),
         description=_(
             "True, you will be physically present, "
-            "or false, you will be remotely present."),
-        required=False, readonly=False, default=True)
+            "or false, you will be remotely present."
+        ),
+        required=False,
+        readonly=False,
+        default=True,
+    )
diff --git a/lib/lp/blueprints/interfaces/sprintspecification.py b/lib/lp/blueprints/interfaces/sprintspecification.py
index 0b57b65..0a08976 100644
--- a/lib/lp/blueprints/interfaces/sprintspecification.py
+++ b/lib/lp/blueprints/interfaces/sprintspecification.py
@@ -4,19 +4,11 @@
 """Interfaces for linking between Sprint and a Specification."""
 
 __all__ = [
-    'ISprintSpecification',
-    ]
+    "ISprintSpecification",
+]
 
-from zope.interface import (
-    Attribute,
-    Interface,
-    )
-from zope.schema import (
-    Choice,
-    Datetime,
-    Int,
-    Text,
-    )
+from zope.interface import Attribute, Interface
+from zope.schema import Choice, Datetime, Int, Text
 
 from lp import _
 from lp.blueprints.enums import SprintSpecificationStatus
@@ -30,47 +22,62 @@ class ISprintSpecification(Interface):
         "The ID of this sprint/spec link. We expose this because there is "
         "no uniqueness of spec names across projects and of course "
         "distros, so there is no unique way to identify a sprintspec by spec "
-        "name, because multiple specs at a sprint could have the same name.")
+        "name, because multiple specs at a sprint could have the same name."
+    )
     sprint = Choice(
-        title=_('Sprint'), required=True, readonly=True,
+        title=_("Sprint"),
+        required=True,
+        readonly=True,
         description=_(
             "Select the meeting or sprint at which you would like "
             "feature to be discussed or implemented. The meeting organisers "
-            "will review and approve or decline this request."),
-        vocabulary='FutureSprint')
-    specification = Int(
-        title=_('Specification'), required=True, readonly=True)
+            "will review and approve or decline this request."
+        ),
+        vocabulary="FutureSprint",
+    )
+    specification = Int(title=_("Specification"), required=True, readonly=True)
     status = Choice(
-        title=_('Agenda Status'), required=True,
-        vocabulary=SprintSpecificationStatus)
+        title=_("Agenda Status"),
+        required=True,
+        vocabulary=SprintSpecificationStatus,
+    )
     whiteboard = Text(
-        title=_('Whiteboard'), required=False,
+        title=_("Whiteboard"),
+        required=False,
         description=_(
             "Any reasoning or rationale for your decision. "
             "Your changes will override the current text. Note that "
             "this is purely related to whether this spec is approved for "
             "the agenda of this meeting, not a commentary of "
-            "the specification in general."))
+            "the specification in general."
+        ),
+    )
     registrant = PublicPersonChoice(
-        title=_('Nominated by'), required=False,
-        vocabulary='ValidPersonOrTeam')
+        title=_("Nominated by"), required=False, vocabulary="ValidPersonOrTeam"
+    )
     date_created = Datetime(
-        title=_('Date nominated'),
+        title=_("Date nominated"),
         description=_(
-            "The date this topic was nominated for the sprint agenda."))
+            "The date this topic was nominated for the sprint agenda."
+        ),
+    )
     decider = PublicPersonChoice(
-        title=_('Decided by'), required=False,
-        vocabulary='ValidPersonOrTeam')
+        title=_("Decided by"), required=False, vocabulary="ValidPersonOrTeam"
+    )
     date_decided = Datetime(
-        title=_('Date decided'),
+        title=_("Date decided"),
         description=_(
             "The date this topic was reviewed and accepted or declined for "
-            "the meeting agenda."))
+            "the meeting agenda."
+        ),
+    )
 
     is_confirmed = Attribute(
-        "True if this spec is confirmed for the agenda of this sprint.")
+        "True if this spec is confirmed for the agenda of this sprint."
+    )
     is_decided = Attribute(
-        'True if this spec has been accepted or declined for this sprint.')
+        "True if this spec has been accepted or declined for this sprint."
+    )
 
     def acceptBy(decider):
         """Flag the sprint as being accepted by the decider."""
diff --git a/lib/lp/blueprints/interfaces/webservice.py b/lib/lp/blueprints/interfaces/webservice.py
index 2ee3dd5..e807ca5 100644
--- a/lib/lp/blueprints/interfaces/webservice.py
+++ b/lib/lp/blueprints/interfaces/webservice.py
@@ -10,13 +10,13 @@ which tells `lazr.restful` that it should look for webservice exports here.
 """
 
 __all__ = [
-    'GoalProposeError',
-    'ISpecification',
-    'ISpecificationBranch',
-    'ISpecificationSet',
-    'ISpecificationSubscription',
-    'ISpecificationTarget',
-    ]
+    "GoalProposeError",
+    "ISpecification",
+    "ISpecificationBranch",
+    "ISpecificationSet",
+    "ISpecificationSubscription",
+    "ISpecificationTarget",
+]
 
 # XXX: JonathanLange 2010-11-09 bug=673083: Legacy work-around for circular
 # import bugs.  Break this up into a per-package thing.
@@ -25,12 +25,11 @@ from lp.blueprints.interfaces.specification import (
     GoalProposeError,
     ISpecification,
     ISpecificationSet,
-    )
+)
 from lp.blueprints.interfaces.specificationbranch import ISpecificationBranch
 from lp.blueprints.interfaces.specificationsubscription import (
     ISpecificationSubscription,
-    )
+)
 from lp.blueprints.interfaces.specificationtarget import ISpecificationTarget
 
-
 _schema_circular_imports
diff --git a/lib/lp/blueprints/mail/notifications.py b/lib/lp/blueprints/mail/notifications.py
index dc20868..64ac1bf 100644
--- a/lib/lp/blueprints/mail/notifications.py
+++ b/lib/lp/blueprints/mail/notifications.py
@@ -8,7 +8,7 @@ from lp.services.database.sqlbase import block_implicit_flushes
 from lp.services.mail.helpers import (
     get_contact_email_addresses,
     get_email_template,
-    )
+)
 from lp.services.mail.mailwrapper import MailWrapper
 from lp.services.mail.notification import get_unified_diff
 from lp.services.mail.sendmail import simple_sendmail_from_person
@@ -17,7 +17,7 @@ from lp.services.webapp.publisher import canonical_url
 
 def specification_notification_subject(spec):
     """Format the email subject line for a specification."""
-    return '[Blueprint %s] %s' % (spec.name, spec.title)
+    return "[Blueprint %s] %s" % (spec.name, spec.title)
 
 
 def notify_specification_modified(spec, event):
@@ -32,73 +32,82 @@ def notify_specification_modified(spec, event):
         return
 
     subject = specification_notification_subject(spec)
-    indent = ' ' * 4
+    indent = " " * 4
     info_lines = []
     if spec_delta.name:
-        info_lines.append('%sName: %s => %s' % (
-            indent, spec_delta.name['old'], spec_delta.name['new']))
-    for dbitem_name in ('definition_status', 'priority'):
+        info_lines.append(
+            "%sName: %s => %s"
+            % (indent, spec_delta.name["old"], spec_delta.name["new"])
+        )
+    for dbitem_name in ("definition_status", "priority"):
         title = ISpecification[dbitem_name].title
         assert ISpecification[dbitem_name].required, (
-            "The mail notification assumes %s can't be None" % dbitem_name)
+            "The mail notification assumes %s can't be None" % dbitem_name
+        )
         dbitem_delta = getattr(spec_delta, dbitem_name)
         if dbitem_delta is not None:
-            old_item = dbitem_delta['old']
-            new_item = dbitem_delta['new']
-            info_lines.append("%s%s: %s => %s" % (
-                indent, title, old_item.title, new_item.title))
+            old_item = dbitem_delta["old"]
+            new_item = dbitem_delta["new"]
+            info_lines.append(
+                "%s%s: %s => %s"
+                % (indent, title, old_item.title, new_item.title)
+            )
 
-    for person_attrname in ('approver', 'assignee', 'drafter'):
+    for person_attrname in ("approver", "assignee", "drafter"):
         title = ISpecification[person_attrname].title
         person_delta = getattr(spec_delta, person_attrname)
         if person_delta is not None:
-            old_person = person_delta['old']
+            old_person = person_delta["old"]
             if old_person is None:
                 old_value = "(none)"
             else:
                 old_value = old_person.displayname
-            new_person = person_delta['new']
+            new_person = person_delta["new"]
             if new_person is None:
                 new_value = "(none)"
             else:
                 new_value = new_person.displayname
             info_lines.append(
-                "%s%s: %s => %s" % (indent, title, old_value, new_value))
+                "%s%s: %s => %s" % (indent, title, old_value, new_value)
+            )
 
     mail_wrapper = MailWrapper(width=72)
     if spec_delta.whiteboard is not None:
         if info_lines:
-            info_lines.append('')
+            info_lines.append("")
         whiteboard_delta = spec_delta.whiteboard
-        if whiteboard_delta['old'] is None:
-            info_lines.append('Whiteboard set to:')
-            info_lines.append(mail_wrapper.format(whiteboard_delta['new']))
+        if whiteboard_delta["old"] is None:
+            info_lines.append("Whiteboard set to:")
+            info_lines.append(mail_wrapper.format(whiteboard_delta["new"]))
         else:
             whiteboard_diff = get_unified_diff(
-                whiteboard_delta['old'], whiteboard_delta['new'], 72)
-            info_lines.append('Whiteboard changed:')
+                whiteboard_delta["old"], whiteboard_delta["new"], 72
+            )
+            info_lines.append("Whiteboard changed:")
             info_lines.append(whiteboard_diff)
     if spec_delta.workitems_text is not None:
         if info_lines:
-            info_lines.append('')
+            info_lines.append("")
         workitems_delta = spec_delta.workitems_text
-        if workitems_delta['old'] == '':
-            info_lines.append('Work items set to:')
-            info_lines.append(mail_wrapper.format(workitems_delta['new']))
+        if workitems_delta["old"] == "":
+            info_lines.append("Work items set to:")
+            info_lines.append(mail_wrapper.format(workitems_delta["new"]))
         else:
             workitems_diff = get_unified_diff(
-                workitems_delta['old'], workitems_delta['new'], 72)
-            info_lines.append('Work items changed:')
+                workitems_delta["old"], workitems_delta["new"], 72
+            )
+            info_lines.append("Work items changed:")
             info_lines.append(workitems_diff)
     if not info_lines:
         # The specification was modified, but we don't yet support
         # sending notification for the change.
         return
-    body = get_email_template('specification-modified.txt', 'blueprints') % {
-        'editor': user.displayname,
-        'info_fields': '\n'.join(info_lines),
-        'spec_title': spec.title,
-        'spec_url': canonical_url(spec)}
+    body = get_email_template("specification-modified.txt", "blueprints") % {
+        "editor": user.displayname,
+        "info_fields": "\n".join(info_lines),
+        "spec_title": spec.title,
+        "spec_url": canonical_url(spec),
+    }
 
     for address in spec.notificationRecipientAddresses():
         simple_sendmail_from_person(user, address, subject, body)
@@ -113,12 +122,15 @@ def notify_specification_subscription_created(specsub, event):
     subject = specification_notification_subject(spec)
     mailwrapper = MailWrapper(width=72)
     body = mailwrapper.format(
-        'You are now subscribed to the blueprint '
-        '%(blueprint_name)s - %(blueprint_title)s.\n\n'
-        '-- \n%(blueprint_url)s' %
-        {'blueprint_name': spec.name,
-         'blueprint_title': spec.title,
-         'blueprint_url': canonical_url(spec)})
+        "You are now subscribed to the blueprint "
+        "%(blueprint_name)s - %(blueprint_title)s.\n\n"
+        "-- \n%(blueprint_url)s"
+        % {
+            "blueprint_name": spec.name,
+            "blueprint_title": spec.title,
+            "blueprint_url": canonical_url(spec),
+        }
+    )
     for address in get_contact_email_addresses(person):
         simple_sendmail_from_person(user, address, subject, body)
 
@@ -137,18 +149,21 @@ def notify_specification_subscription_modified(specsub, event):
         return
     subject = specification_notification_subject(spec)
     if specsub.essential:
-        specsub_type = 'Participation essential'
+        specsub_type = "Participation essential"
     else:
-        specsub_type = 'Participation non-essential'
+        specsub_type = "Participation non-essential"
     mailwrapper = MailWrapper(width=72)
     body = mailwrapper.format(
-        'Your subscription to the blueprint '
-        '%(blueprint_name)s - %(blueprint_title)s '
-        'has changed to [%(specsub_type)s].\n\n'
-        '--\n  %(blueprint_url)s' %
-        {'blueprint_name': spec.name,
-         'blueprint_title': spec.title,
-         'specsub_type': specsub_type,
-         'blueprint_url': canonical_url(spec)})
+        "Your subscription to the blueprint "
+        "%(blueprint_name)s - %(blueprint_title)s "
+        "has changed to [%(specsub_type)s].\n\n"
+        "--\n  %(blueprint_url)s"
+        % {
+            "blueprint_name": spec.name,
+            "blueprint_title": spec.title,
+            "specsub_type": specsub_type,
+            "blueprint_url": canonical_url(spec),
+        }
+    )
     for address in get_contact_email_addresses(person):
         simple_sendmail_from_person(user, address, subject, body)
diff --git a/lib/lp/blueprints/model/specification.py b/lib/lp/blueprints/model/specification.py
index 43f60fc..225ef65 100644
--- a/lib/lp/blueprints/model/specification.py
+++ b/lib/lp/blueprints/model/specification.py
@@ -2,36 +2,28 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'HasSpecificationsMixin',
-    'recursive_blocked_query',
-    'Specification',
-    'SPECIFICATION_POLICY_ALLOWED_TYPES',
-    'SPECIFICATION_POLICY_DEFAULT_TYPES',
-    'SpecificationSet',
-    ]
+    "HasSpecificationsMixin",
+    "recursive_blocked_query",
+    "Specification",
+    "SPECIFICATION_POLICY_ALLOWED_TYPES",
+    "SPECIFICATION_POLICY_DEFAULT_TYPES",
+    "SpecificationSet",
+]
 
 import operator
 
 from lazr.lifecycle.event import ObjectCreatedEvent
 from lazr.lifecycle.objectdelta import ObjectDelta
-from storm.locals import (
-    Count,
-    Desc,
-    Join,
-    Or,
-    ReferenceSet,
-    SQL,
-    Store,
-    )
+from storm.locals import SQL, Count, Desc, Join, Or, ReferenceSet, Store
 from zope.component import getUtility
 from zope.event import notify
 from zope.interface import implementer
 
 from lp.app.enums import (
-    InformationType,
     PRIVATE_INFORMATION_TYPES,
     PUBLIC_INFORMATION_TYPES,
-    )
+    InformationType,
+)
 from lp.app.errors import UserCannotUnsubscribePerson
 from lp.app.interfaces.informationtype import IInformationType
 from lp.app.interfaces.services import IService
@@ -47,20 +39,18 @@ from lp.blueprints.enums import (
     SpecificationPriority,
     SpecificationSort,
     SpecificationWorkItemStatus,
-    )
+)
 from lp.blueprints.errors import TargetAlreadyHasSpecification
 from lp.blueprints.interfaces.specification import (
     GoalProposeError,
     ISpecification,
     ISpecificationSet,
-    )
+)
 from lp.blueprints.model.specificationbranch import SpecificationBranch
-from lp.blueprints.model.specificationdependency import (
-    SpecificationDependency,
-    )
+from lp.blueprints.model.specificationdependency import SpecificationDependency
 from lp.blueprints.model.specificationsubscription import (
     SpecificationSubscription,
-    )
+)
 from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
 from lp.bugs.interfaces.buglink import IBugLinkTarget
 from lp.bugs.interfaces.bugtask import IBugTaskSet
@@ -72,7 +62,7 @@ from lp.registry.errors import CannotChangeInformationType
 from lp.registry.interfaces.accesspolicy import (
     IAccessArtifactGrantSource,
     IAccessArtifactSource,
-    )
+)
 from lp.registry.interfaces.distribution import IDistribution
 from lp.registry.interfaces.distroseries import IDistroSeries
 from lp.registry.interfaces.person import validate_public_person
@@ -80,18 +70,15 @@ from lp.registry.interfaces.product import IProduct
 from lp.registry.interfaces.productseries import IProductSeries
 from lp.registry.model.milestone import Milestone
 from lp.services.database import bulk
-from lp.services.database.constants import (
-    DEFAULT,
-    UTC_NOW,
-    )
+from lp.services.database.constants import DEFAULT, UTC_NOW
 from lp.services.database.datetimecol import UtcDateTimeCol
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.interfaces import IStore
 from lp.services.database.sqlbase import (
-    convert_storm_clause_to_string,
     SQLBase,
+    convert_storm_clause_to_string,
     sqlvalues,
-    )
+)
 from lp.services.database.sqlobject import (
     BoolCol,
     ForeignKey,
@@ -99,12 +86,9 @@ from lp.services.database.sqlobject import (
     SQLMultipleJoin,
     SQLRelatedJoin,
     StringCol,
-    )
+)
 from lp.services.mail.helpers import get_contact_email_addresses
-from lp.services.propertycache import (
-    cachedproperty,
-    get_property_cache,
-    )
+from lp.services.propertycache import cachedproperty, get_property_cache
 from lp.services.webapp.interfaces import ILaunchBag
 from lp.services.webapp.snapshot import notify_modified
 from lp.services.xref.interfaces import IXRefSet
@@ -113,7 +97,8 @@ from lp.services.xref.interfaces import IXRefSet
 def recursive_blocked_query(user):
     from lp.blueprints.model.specificationsearch import (
         get_specification_privacy_filter,
-        )
+    )
+
     return """
         RECURSIVE blocked(id) AS (
             SELECT ?
@@ -122,14 +107,15 @@ def recursive_blocked_query(user):
             FROM blocked b, specificationdependency sd
             JOIN specification ON sd.specification = specification.id
             WHERE sd.dependency = b.id AND (%s))""" % (
-                convert_storm_clause_to_string(
-                    *get_specification_privacy_filter(user)))
+        convert_storm_clause_to_string(*get_specification_privacy_filter(user))
+    )
 
 
 def recursive_dependent_query(user):
     from lp.blueprints.model.specificationsearch import (
         get_specification_privacy_filter,
-        )
+    )
+
     return """
         RECURSIVE dependencies(id) AS (
             SELECT ?
@@ -138,153 +124,248 @@ def recursive_dependent_query(user):
             FROM dependencies d, specificationdependency sd
             JOIN specification ON sd.dependency = specification.id
             WHERE sd.specification = d.id AND (%s))""" % (
-                convert_storm_clause_to_string(
-                    *get_specification_privacy_filter(user)))
+        convert_storm_clause_to_string(*get_specification_privacy_filter(user))
+    )
 
 
 SPECIFICATION_POLICY_ALLOWED_TYPES = {
     SpecificationSharingPolicy.PUBLIC: [InformationType.PUBLIC],
-    SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY:
-        [InformationType.PUBLIC, InformationType.PROPRIETARY],
-    SpecificationSharingPolicy.PROPRIETARY_OR_PUBLIC:
-        [InformationType.PUBLIC, InformationType.PROPRIETARY],
+    SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY: [
+        InformationType.PUBLIC,
+        InformationType.PROPRIETARY,
+    ],
+    SpecificationSharingPolicy.PROPRIETARY_OR_PUBLIC: [
+        InformationType.PUBLIC,
+        InformationType.PROPRIETARY,
+    ],
     SpecificationSharingPolicy.PROPRIETARY: [InformationType.PROPRIETARY],
-    SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY:
-        [InformationType.PROPRIETARY, InformationType.EMBARGOED],
+    SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY: [
+        InformationType.PROPRIETARY,
+        InformationType.EMBARGOED,
+    ],
     SpecificationSharingPolicy.FORBIDDEN: [],
-    }
+}
 
 SPECIFICATION_POLICY_DEFAULT_TYPES = {
     SpecificationSharingPolicy.PUBLIC: InformationType.PUBLIC,
-    SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY: (
-        InformationType.PUBLIC),
+    SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY: (InformationType.PUBLIC),
     SpecificationSharingPolicy.PROPRIETARY_OR_PUBLIC: (
-        InformationType.PROPRIETARY),
+        InformationType.PROPRIETARY
+    ),
     SpecificationSharingPolicy.PROPRIETARY: InformationType.PROPRIETARY,
     SpecificationSharingPolicy.EMBARGOED_OR_PROPRIETARY: (
-        InformationType.EMBARGOED),
-    }
+        InformationType.EMBARGOED
+    ),
+}
 
 
 @implementer(ISpecification, IBugLinkTarget, IInformationType)
 class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
     """See ISpecification."""
 
-    _defaultOrder = ['-priority', 'definition_status', 'name', 'id']
+    _defaultOrder = ["-priority", "definition_status", "name", "id"]
 
     # db field names
     name = StringCol(unique=True, notNull=True)
     title = StringCol(notNull=True)
     summary = StringCol(notNull=True)
     definition_status = DBEnum(
-        enum=SpecificationDefinitionStatus, allow_none=False,
-        default=SpecificationDefinitionStatus.NEW)
+        enum=SpecificationDefinitionStatus,
+        allow_none=False,
+        default=SpecificationDefinitionStatus.NEW,
+    )
     priority = DBEnum(
-        enum=SpecificationPriority, allow_none=False,
-        default=SpecificationPriority.UNDEFINED)
-    _assignee = ForeignKey(dbName='assignee', notNull=False,
-        foreignKey='Person',
-        storm_validator=validate_public_person, default=None)
-    _drafter = ForeignKey(dbName='drafter', notNull=False,
-        foreignKey='Person',
-        storm_validator=validate_public_person, default=None)
-    _approver = ForeignKey(dbName='approver', notNull=False,
-        foreignKey='Person',
-        storm_validator=validate_public_person, default=None)
+        enum=SpecificationPriority,
+        allow_none=False,
+        default=SpecificationPriority.UNDEFINED,
+    )
+    _assignee = ForeignKey(
+        dbName="assignee",
+        notNull=False,
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        default=None,
+    )
+    _drafter = ForeignKey(
+        dbName="drafter",
+        notNull=False,
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        default=None,
+    )
+    _approver = ForeignKey(
+        dbName="approver",
+        notNull=False,
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        default=None,
+    )
     owner = ForeignKey(
-        dbName='owner', foreignKey='Person',
-        storm_validator=validate_public_person, notNull=True)
+        dbName="owner",
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        notNull=True,
+    )
     datecreated = UtcDateTimeCol(notNull=True, default=DEFAULT)
-    product = ForeignKey(dbName='product', foreignKey='Product',
-        notNull=False, default=None)
-    productseries = ForeignKey(dbName='productseries',
-        foreignKey='ProductSeries', notNull=False, default=None)
-    distribution = ForeignKey(dbName='distribution',
-        foreignKey='Distribution', notNull=False, default=None)
-    distroseries = ForeignKey(dbName='distroseries',
-        foreignKey='DistroSeries', notNull=False, default=None)
+    product = ForeignKey(
+        dbName="product", foreignKey="Product", notNull=False, default=None
+    )
+    productseries = ForeignKey(
+        dbName="productseries",
+        foreignKey="ProductSeries",
+        notNull=False,
+        default=None,
+    )
+    distribution = ForeignKey(
+        dbName="distribution",
+        foreignKey="Distribution",
+        notNull=False,
+        default=None,
+    )
+    distroseries = ForeignKey(
+        dbName="distroseries",
+        foreignKey="DistroSeries",
+        notNull=False,
+        default=None,
+    )
     goalstatus = DBEnum(
-        enum=SpecificationGoalStatus, allow_none=False,
-        default=SpecificationGoalStatus.PROPOSED)
-    goal_proposer = ForeignKey(dbName='goal_proposer', notNull=False,
-        foreignKey='Person',
-        storm_validator=validate_public_person, default=None)
+        enum=SpecificationGoalStatus,
+        allow_none=False,
+        default=SpecificationGoalStatus.PROPOSED,
+    )
+    goal_proposer = ForeignKey(
+        dbName="goal_proposer",
+        notNull=False,
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        default=None,
+    )
     date_goal_proposed = UtcDateTimeCol(notNull=False, default=None)
-    goal_decider = ForeignKey(dbName='goal_decider', notNull=False,
-        foreignKey='Person',
-        storm_validator=validate_public_person, default=None)
+    goal_decider = ForeignKey(
+        dbName="goal_decider",
+        notNull=False,
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        default=None,
+    )
     date_goal_decided = UtcDateTimeCol(notNull=False, default=None)
-    milestone = ForeignKey(dbName='milestone',
-        foreignKey='Milestone', notNull=False, default=None)
+    milestone = ForeignKey(
+        dbName="milestone", foreignKey="Milestone", notNull=False, default=None
+    )
     specurl = StringCol(notNull=False, default=None)
     whiteboard = StringCol(notNull=False, default=None)
     direction_approved = BoolCol(notNull=True, default=False)
     man_days = IntCol(notNull=False, default=None)
     implementation_status = DBEnum(
-        enum=SpecificationImplementationStatus, allow_none=False,
-        default=SpecificationImplementationStatus.UNKNOWN)
-    superseded_by = ForeignKey(dbName='superseded_by',
-        foreignKey='Specification', notNull=False, default=None)
-    completer = ForeignKey(dbName='completer', notNull=False,
-        foreignKey='Person',
-        storm_validator=validate_public_person, default=None)
+        enum=SpecificationImplementationStatus,
+        allow_none=False,
+        default=SpecificationImplementationStatus.UNKNOWN,
+    )
+    superseded_by = ForeignKey(
+        dbName="superseded_by",
+        foreignKey="Specification",
+        notNull=False,
+        default=None,
+    )
+    completer = ForeignKey(
+        dbName="completer",
+        notNull=False,
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        default=None,
+    )
     date_completed = UtcDateTimeCol(notNull=False, default=None)
-    starter = ForeignKey(dbName='starter', notNull=False,
-        foreignKey='Person',
-        storm_validator=validate_public_person, default=None)
+    starter = ForeignKey(
+        dbName="starter",
+        notNull=False,
+        foreignKey="Person",
+        storm_validator=validate_public_person,
+        default=None,
+    )
     date_started = UtcDateTimeCol(notNull=False, default=None)
 
     # useful joins
     _subscriptions = ReferenceSet(
-        'id', 'SpecificationSubscription.specification_id',
-        order_by='SpecificationSubscription.id')
+        "id",
+        "SpecificationSubscription.specification_id",
+        order_by="SpecificationSubscription.id",
+    )
     subscribers = ReferenceSet(
-        'id', 'SpecificationSubscription.specification_id',
-        'SpecificationSubscription.person_id', 'Person.id',
-        order_by=('Person.display_name', 'Person.name'))
+        "id",
+        "SpecificationSubscription.specification_id",
+        "SpecificationSubscription.person_id",
+        "Person.id",
+        order_by=("Person.display_name", "Person.name"),
+    )
     sprint_links = ReferenceSet(
-        '<primary key>', 'SprintSpecification.specification_id',
-        order_by='SprintSpecification.id')
+        "<primary key>",
+        "SprintSpecification.specification_id",
+        order_by="SprintSpecification.id",
+    )
     sprints = ReferenceSet(
-        '<primary key>', 'SprintSpecification.specification_id',
-        'SprintSpecification.sprint_id', 'Sprint.id',
-        order_by='Sprint.name')
-    spec_dependency_links = SQLMultipleJoin('SpecificationDependency',
-        joinColumn='specification', orderBy='id')
-
-    dependencies = SQLRelatedJoin('Specification', joinColumn='specification',
-        otherColumn='dependency', orderBy='title',
-        intermediateTable='SpecificationDependency')
+        "<primary key>",
+        "SprintSpecification.specification_id",
+        "SprintSpecification.sprint_id",
+        "Sprint.id",
+        order_by="Sprint.name",
+    )
+    spec_dependency_links = SQLMultipleJoin(
+        "SpecificationDependency", joinColumn="specification", orderBy="id"
+    )
+
+    dependencies = SQLRelatedJoin(
+        "Specification",
+        joinColumn="specification",
+        otherColumn="dependency",
+        orderBy="title",
+        intermediateTable="SpecificationDependency",
+    )
     information_type = DBEnum(
-        enum=InformationType, allow_none=False, default=InformationType.PUBLIC)
+        enum=InformationType, allow_none=False, default=InformationType.PUBLIC
+    )
 
     @cachedproperty
     def linked_branches(self):
-        return list(Store.of(self).find(
-            SpecificationBranch,
-            SpecificationBranch.specification == self).order_by(
-                SpecificationBranch.id))
+        return list(
+            Store.of(self)
+            .find(
+                SpecificationBranch, SpecificationBranch.specification == self
+            )
+            .order_by(SpecificationBranch.id)
+        )
 
     def _fetch_children_or_parents(self, join_cond, cond, user):
         from lp.blueprints.model.specificationsearch import (
             get_specification_privacy_filter,
+        )
+
+        return list(
+            Store.of(self)
+            .using(
+                Specification,
+                Join(SpecificationDependency, join_cond == self.id),
             )
-        return list(Store.of(self).using(
-            Specification,
-            Join(SpecificationDependency, join_cond == self.id)).find(
-            Specification,
-            cond == Specification.id, *get_specification_privacy_filter(user)
-            ).order_by(Specification.title))
+            .find(
+                Specification,
+                cond == Specification.id,
+                *get_specification_privacy_filter(user),
+            )
+            .order_by(Specification.title)
+        )
 
     def getDependencies(self, user=None):
         return self._fetch_children_or_parents(
             SpecificationDependency.specificationID,
-            SpecificationDependency.dependencyID, user)
+            SpecificationDependency.dependencyID,
+            user,
+        )
 
     def getBlockedSpecs(self, user=None):
         return self._fetch_children_or_parents(
             SpecificationDependency.dependencyID,
-            SpecificationDependency.specificationID, user)
+            SpecificationDependency.specificationID,
+            user,
+        )
 
     def set_assignee(self, person):
         self.subscribeIfAccessGrantNeeded(person)
@@ -326,8 +407,10 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
     def subscriptions(self):
         """Sort the subscriptions"""
         from lp.registry.model.person import person_sort_key
+
         return sorted(
-            self._subscriptions, key=lambda sub: person_sort_key(sub.person))
+            self._subscriptions, key=lambda sub: person_sort_key(sub.person)
+        )
 
     @property
     def workitems_text(self):
@@ -341,7 +424,7 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
                 return "Work items for %s:" % milestone.name
 
         if len(self.work_items) == 0:
-            return ''
+            return ""
         milestone = self.work_items[0].milestone
         # Start by appending a header for the milestone of the first work
         # item. After this we're going to write a new header whenever we see a
@@ -359,8 +442,9 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
                 assignee_part = ""
             # work_items are ordered by sequence
             workitems_lines.append(
-                "%s%s: %s" % (
-                    assignee_part, work_item.title, work_item.status.name))
+                "%s%s: %s"
+                % (assignee_part, work_item.title, work_item.status.name)
+            )
         return "\n".join(workitems_lines)
 
     @property
@@ -370,17 +454,30 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
             return self.product
         return self.distribution
 
-    def newWorkItem(self, title, sequence,
-                    status=SpecificationWorkItemStatus.TODO, assignee=None,
-                    milestone=None):
+    def newWorkItem(
+        self,
+        title,
+        sequence,
+        status=SpecificationWorkItemStatus.TODO,
+        assignee=None,
+        milestone=None,
+    ):
         """See ISpecification."""
         if milestone is not None:
-            assert milestone.target == self.target, (
-                "%s does not belong to this spec's target (%s)" %
-                    (milestone.displayname, self.target.name))
+            assert (
+                milestone.target == self.target
+            ), "%s does not belong to this spec's target (%s)" % (
+                milestone.displayname,
+                self.target.name,
+            )
         return SpecificationWorkItem(
-            title=title, status=status, specification=self, assignee=assignee,
-            milestone=milestone, sequence=sequence)
+            title=title,
+            status=status,
+            specification=self,
+            assignee=assignee,
+            milestone=milestone,
+            sequence=sequence,
+        )
 
     @cachedproperty
     def work_items(self):
@@ -389,12 +486,14 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
 
     @property
     def _work_items(self):
-        return Store.of(self).find(
-            SpecificationWorkItem, specification=self,
-            deleted=False).order_by("sequence")
+        return (
+            Store.of(self)
+            .find(SpecificationWorkItem, specification=self, deleted=False)
+            .order_by("sequence")
+        )
 
     def setWorkItems(self, new_work_items):
-        field = ISpecification['workitems_text'].bind(self)
+        field = ISpecification["workitems_text"].bind(self)
         self.updateWorkItems(field.parseAndValidate(new_work_items))
 
     def _deleteWorkItemsNotMatching(self, titles):
@@ -405,8 +504,10 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         title_counts = self._list_to_dict_of_frequency(titles)
 
         for work_item in self._work_items:
-            if (work_item.title not in title_counts or
-                title_counts[work_item.title] == 0):
+            if (
+                work_item.title not in title_counts
+                or title_counts[work_item.title] == 0
+            ):
                 work_item.deleted = True
 
             elif title_counts[work_item.title] > 0:
@@ -426,7 +527,8 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         # First mark work items with titles that are no longer present as
         # deleted.
         self._deleteWorkItemsNotMatching(
-            [wi['title'] for wi in new_work_items])
+            [wi["title"] for wi in new_work_items]
+        )
         work_items = self._work_items
         # At this point the list of new_work_items is necessarily the same
         # size (or longer) than the list of existing ones, so we can just
@@ -437,32 +539,42 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         existing_title_count = self._list_to_dict_of_frequency(existing_titles)
 
         for i, new_wi in enumerate(new_work_items):
-            if (new_wi['title'] not in existing_titles or
-                existing_title_count[new_wi['title']] == 0):
+            if (
+                new_wi["title"] not in existing_titles
+                or existing_title_count[new_wi["title"]] == 0
+            ):
                 to_insert.append((i, new_wi))
             else:
-                existing_title_count[new_wi['title']] -= 1
+                existing_title_count[new_wi["title"]] -= 1
                 # Get an existing work item with the same title and update
                 # it to match what we have now.
-                existing_wi_index = existing_titles.index(new_wi['title'])
+                existing_wi_index = existing_titles.index(new_wi["title"])
                 existing_wi = work_items[existing_wi_index]
                 # Mark a work item as dirty - don't use it again this update.
                 existing_titles[existing_wi_index] = None
                 # Update the sequence to match its current position on the
                 # list entered by the user.
                 existing_wi.sequence = i
-                existing_wi.status = new_wi['status']
-                existing_wi.assignee = new_wi['assignee']
-                milestone = new_wi['milestone']
+                existing_wi.status = new_wi["status"]
+                existing_wi.assignee = new_wi["assignee"]
+                milestone = new_wi["milestone"]
                 if milestone is not None:
-                    assert milestone.target == self.target, (
-                        "%s does not belong to this spec's target (%s)" %
-                            (milestone.displayname, self.target.name))
+                    assert (
+                        milestone.target == self.target
+                    ), "%s does not belong to this spec's target (%s)" % (
+                        milestone.displayname,
+                        self.target.name,
+                    )
                 existing_wi.milestone = milestone
 
         for sequence, item in to_insert:
-            self.newWorkItem(item['title'], sequence, item['status'],
-                             item['assignee'], item['milestone'])
+            self.newWorkItem(
+                item["title"],
+                sequence,
+                item["status"],
+                item["assignee"],
+                item["milestone"],
+            )
         Store.of(self).flush()
         del get_property_cache(self).work_items
 
@@ -516,16 +628,16 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
             # we are clearing goals
             self.productseries = None
             self.distroseries = None
-        elif (IProductSeries.providedBy(goal) and
-              goal.product == self.target):
+        elif IProductSeries.providedBy(goal) and goal.product == self.target:
             # set the product series as a goal
             self.productseries = goal
             self.goal_proposer = proposer
             self.date_goal_proposed = UTC_NOW
             # and make sure there is no leftover distroseries goal
             self.distroseries = None
-        elif (IDistroSeries.providedBy(goal) and
-              goal.distribution == self.target):
+        elif (
+            IDistroSeries.providedBy(goal) and goal.distribution == self.target
+        ):
             # set the distroseries goal
             self.distroseries = goal
             self.goal_proposer = proposer
@@ -533,7 +645,7 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
             # and make sure there is no leftover distroseries goal
             self.productseries = None
         else:
-            raise GoalProposeError('Inappropriate goal.')
+            raise GoalProposeError("Inappropriate goal.")
         # record who made the proposal, and when
         self.goal_proposer = proposer
         self.date_goal_proposed = UTC_NOW
@@ -567,15 +679,23 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
     def notificationRecipientAddresses(self):
         """See ISpecification."""
         related_people = [
-            self.owner, self.assignee, self.approver, self.drafter]
+            self.owner,
+            self.assignee,
+            self.approver,
+            self.drafter,
+        ]
         related_people = [
-            person for person in related_people if person is not None]
+            person for person in related_people if person is not None
+        ]
         subscribers = [
-            subscription.person for subscription in self.subscriptions]
+            subscription.person for subscription in self.subscriptions
+        ]
         notify_people = set(related_people + subscribers)
         without_access = set(
-            getUtility(IService, 'sharing').getPeopleWithoutAccess(
-                self, notify_people))
+            getUtility(IService, "sharing").getPeopleWithoutAccess(
+                self, notify_people
+            )
+        )
         notify_people -= without_access
         addresses = set()
         for person in notify_people:
@@ -592,19 +712,24 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
     def is_complete(self):
         """See `ISpecification`."""
         # Implemented blueprints are by definition complete.
-        if (self.implementation_status ==
-            SpecificationImplementationStatus.IMPLEMENTED):
+        if (
+            self.implementation_status
+            == SpecificationImplementationStatus.IMPLEMENTED
+        ):
             return True
         # Obsolete and superseded blueprints are considered complete.
         if self.definition_status in (
             SpecificationDefinitionStatus.OBSOLETE,
-            SpecificationDefinitionStatus.SUPERSEDED):
+            SpecificationDefinitionStatus.SUPERSEDED,
+        ):
             return True
         # Approved information blueprints are also considered complete.
-        if ((self.implementation_status ==
-             SpecificationImplementationStatus.INFORMATIONAL) and
-            (self.definition_status ==
-             SpecificationDefinitionStatus.APPROVED)):
+        if (
+            self.implementation_status
+            == SpecificationImplementationStatus.INFORMATIONAL
+        ) and (
+            self.definition_status == SpecificationDefinitionStatus.APPROVED
+        ):
             return True
         else:
             return False
@@ -614,16 +739,21 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         """See ISpecification. This is a code implementation of the
         SQL in spec_started_clause
         """
-        return (self.implementation_status not in [
-                    SpecificationImplementationStatus.UNKNOWN,
-                    SpecificationImplementationStatus.NOTSTARTED,
-                    SpecificationImplementationStatus.DEFERRED,
-                    SpecificationImplementationStatus.INFORMATIONAL,
-                    ]
-                or ((self.implementation_status ==
-                     SpecificationImplementationStatus.INFORMATIONAL) and
-                    (self.definition_status ==
-                     SpecificationDefinitionStatus.APPROVED)))
+        return self.implementation_status not in [
+            SpecificationImplementationStatus.UNKNOWN,
+            SpecificationImplementationStatus.NOTSTARTED,
+            SpecificationImplementationStatus.DEFERRED,
+            SpecificationImplementationStatus.INFORMATIONAL,
+        ] or (
+            (
+                self.implementation_status
+                == SpecificationImplementationStatus.INFORMATIONAL
+            )
+            and (
+                self.definition_status
+                == SpecificationDefinitionStatus.APPROVED
+            )
+        )
 
     @property
     def lifecycle_status(self):
@@ -683,20 +813,39 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
     @property
     def has_accepted_goal(self):
         """See ISpecification."""
-        if (self.goal is not None and
-            self.goalstatus == SpecificationGoalStatus.ACCEPTED):
+        if (
+            self.goal is not None
+            and self.goalstatus == SpecificationGoalStatus.ACCEPTED
+        ):
             return True
         return False
 
     def getDelta(self, old_spec, user):
         """See ISpecification."""
         delta = ObjectDelta(old_spec, self)
-        delta.recordNewValues(("title", "summary",
-                               "specurl", "productseries",
-                               "distroseries", "milestone"))
-        delta.recordNewAndOld(("name", "priority", "definition_status",
-                               "target", "approver", "assignee", "drafter",
-                               "whiteboard", "workitems_text"))
+        delta.recordNewValues(
+            (
+                "title",
+                "summary",
+                "specurl",
+                "productseries",
+                "distroseries",
+                "milestone",
+            )
+        )
+        delta.recordNewAndOld(
+            (
+                "name",
+                "priority",
+                "definition_status",
+                "target",
+                "approver",
+                "assignee",
+                "drafter",
+                "whiteboard",
+                "workitems_text",
+            )
+        )
         delta.recordListAddedAndRemoved("bugs", "bugs_linked", "bugs_unlinked")
 
         if delta.changes:
@@ -713,14 +862,19 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         """For backwards compatibility:
         implemented as a value in implementation_status.
         """
-        return (self.implementation_status ==
-                SpecificationImplementationStatus.INFORMATIONAL)
+        return (
+            self.implementation_status
+            == SpecificationImplementationStatus.INFORMATIONAL
+        )
 
     # subscriptions
     def subscription(self, person):
         """See ISpecification."""
-        return IStore(SpecificationSubscription).find(
-            SpecificationSubscription, specification=self, person=person).one()
+        return (
+            IStore(SpecificationSubscription)
+            .find(SpecificationSubscription, specification=self, person=person)
+            .one()
+        )
 
     def getSubscriptionByName(self, name):
         """See ISpecification."""
@@ -742,28 +896,32 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
                 # 'essential' changes, there's no need to create a new
                 # subscription, but we modify the existing subscription
                 # and notify the user about the change.
-                with notify_modified(sub, ['essential'], user=subscribed_by):
+                with notify_modified(sub, ["essential"], user=subscribed_by):
                     sub.essential = essential
             return sub
         # since no previous subscription existed, create and return a new one
-        sub = SpecificationSubscription(specification=self,
-            person=person, essential=essential)
+        sub = SpecificationSubscription(
+            specification=self, person=person, essential=essential
+        )
         property_cache = get_property_cache(self)
-        if 'subscription' in property_cache:
+        if "subscription" in property_cache:
             from lp.registry.model.person import person_sort_key
+
             property_cache.subscriptions.append(sub)
             property_cache.subscriptions.sort(
-                key=lambda sub: person_sort_key(sub.person))
+                key=lambda sub: person_sort_key(sub.person)
+            )
         if self.information_type in PRIVATE_INFORMATION_TYPES:
             # Grant the subscriber access if they can't see the
             # specification.
-            service = getUtility(IService, 'sharing')
+            service = getUtility(IService, "sharing")
             shared_specs = service.getVisibleArtifacts(
-                person, specifications=[self],
-                ignore_permissions=True)["specifications"]
+                person, specifications=[self], ignore_permissions=True
+            )["specifications"]
             if not shared_specs:
                 service.ensureAccessGrants(
-                    [person], subscribed_by, specifications=[self])
+                    [person], subscribed_by, specifications=[self]
+                )
         notify(ObjectCreatedEvent(sub, user=subscribed_by))
         return sub
 
@@ -774,18 +932,22 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
             person = unsubscribed_by
         for sub in self.subscriptions:
             if sub.person.id == person.id:
-                if (not sub.canBeUnsubscribedByUser(unsubscribed_by) and
-                    not ignore_permissions):
+                if (
+                    not sub.canBeUnsubscribedByUser(unsubscribed_by)
+                    and not ignore_permissions
+                ):
                     raise UserCannotUnsubscribePerson(
-                        '%s does not have permission to unsubscribe %s.' % (
-                            unsubscribed_by.displayname,
-                            person.displayname))
+                        "%s does not have permission to unsubscribe %s."
+                        % (unsubscribed_by.displayname, person.displayname)
+                    )
                 get_property_cache(self).subscriptions.remove(sub)
                 IStore(SpecificationSubscription).remove(sub)
-                artifacts_to_delete = getUtility(
-                    IAccessArtifactSource).find([self])
+                artifacts_to_delete = getUtility(IAccessArtifactSource).find(
+                    [self]
+                )
                 getUtility(IAccessArtifactGrantSource).revokeByArtifact(
-                    artifacts_to_delete, [person])
+                    artifacts_to_delete, [person]
+                )
                 return
 
     def isSubscribed(self, person):
@@ -798,11 +960,16 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
     @property
     def bugs(self):
         from lp.bugs.model.bug import Bug
+
         bug_ids = [
-            int(id) for _, id in getUtility(IXRefSet).findFrom(
-                ('specification', str(self.id)), types=['bug'])]
-        return list(sorted(
-            bulk.load(Bug, bug_ids), key=operator.attrgetter('id')))
+            int(id)
+            for _, id in getUtility(IXRefSet).findFrom(
+                ("specification", str(self.id)), types=["bug"]
+            )
+        ]
+        return list(
+            sorted(bulk.load(Bug, bug_ids), key=operator.attrgetter("id"))
+        )
 
     def createBugLink(self, bug, props=None):
         """See BugLinkTargetMixin."""
@@ -810,25 +977,27 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
             props = {}
         # XXX: Should set creator.
         getUtility(IXRefSet).create(
-            {('specification', str(self.id)): {('bug', str(bug.id)): props}})
+            {("specification", str(self.id)): {("bug", str(bug.id)): props}}
+        )
 
     def deleteBugLink(self, bug):
         """See BugLinkTargetMixin."""
         getUtility(IXRefSet).delete(
-            {('specification', str(self.id)): [('bug', str(bug.id))]})
+            {("specification", str(self.id)): [("bug", str(bug.id))]}
+        )
 
     # sprint linking
     def linkSprint(self, sprint, user):
         """See ISpecification."""
-        from lp.blueprints.model.sprintspecification import (
-            SprintSpecification,
-            )
+        from lp.blueprints.model.sprintspecification import SprintSpecification
+
         for sprint_link in self.sprint_links:
             # sprints have unique names
             if sprint_link.sprint.name == sprint.name:
                 return sprint_link
-        sprint_link = SprintSpecification(specification=self,
-            sprint=sprint, registrant=user)
+        sprint_link = SprintSpecification(
+            specification=self, sprint=sprint, registrant=user
+        )
         if sprint.isDriver(user):
             sprint_link.acceptBy(user)
         return sprint_link
@@ -847,8 +1016,9 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         for deplink in self.spec_dependency_links:
             if deplink.dependency.id == specification.id:
                 return deplink
-        return SpecificationDependency(specification=self,
-            dependency=specification)
+        return SpecificationDependency(
+            specification=self, dependency=specification
+        )
 
     def removeDependency(self, specification):
         """See ISpecification."""
@@ -859,35 +1029,49 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
                 return deplink
 
     def all_deps(self, user=None):
-        return list(Store.of(self).with_(
-            SQL(recursive_dependent_query(user), params=(self.id,))).find(
-            Specification,
-            Specification.id != self.id,
-            Specification.id.is_in(SQL('select id from dependencies')),
-            ).order_by(Specification.name, Specification.id))
+        return list(
+            Store.of(self)
+            .with_(SQL(recursive_dependent_query(user), params=(self.id,)))
+            .find(
+                Specification,
+                Specification.id != self.id,
+                Specification.id.is_in(SQL("select id from dependencies")),
+            )
+            .order_by(Specification.name, Specification.id)
+        )
 
     def all_blocked(self, user=None):
         """See `ISpecification`."""
-        return list(Store.of(self).with_(
-            SQL(recursive_blocked_query(user), params=(self.id,))).find(
-            Specification,
-            Specification.id != self.id,
-            Specification.id.is_in(SQL('select id from blocked')),
-            ).order_by(Specification.name, Specification.id))
+        return list(
+            Store.of(self)
+            .with_(SQL(recursive_blocked_query(user), params=(self.id,)))
+            .find(
+                Specification,
+                Specification.id != self.id,
+                Specification.id.is_in(SQL("select id from blocked")),
+            )
+            .order_by(Specification.name, Specification.id)
+        )
 
     # branches
     def getBranchLink(self, branch):
-        return Store.of(self).find(
-            SpecificationBranch,
-            SpecificationBranch.specification == self,
-            SpecificationBranch.branch == branch).one()
+        return (
+            Store.of(self)
+            .find(
+                SpecificationBranch,
+                SpecificationBranch.specification == self,
+                SpecificationBranch.branch == branch,
+            )
+            .one()
+        )
 
     def linkBranch(self, branch, registrant):
         branch_link = self.getBranchLink(branch)
         if branch_link is not None:
             return branch_link
         branch_link = SpecificationBranch(
-            specification=self, branch=branch, registrant=registrant)
+            specification=self, branch=branch, registrant=registrant
+        )
         Store.of(self).flush()
         del get_property_cache(self).linked_branches
         notify(ObjectCreatedEvent(branch_link))
@@ -914,8 +1098,11 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         return filter_bugtasks_by_context(context, tasks)
 
     def __repr__(self):
-        return '<Specification %s %r for %r>' % (
-            self.id, self.name, self.target.name)
+        return "<Specification %s %r for %r>" % (
+            self.id,
+            self.name,
+            self.target.name,
+        )
 
     def getAllowedInformationTypes(self, who):
         """See `ISpecification`."""
@@ -926,24 +1113,31 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         # avoid circular imports.
         from lp.registry.model.accesspolicy import (
             reconcile_access_for_artifacts,
-            )
+        )
+
         if self.information_type == information_type:
             return False
         if information_type not in self.getAllowedInformationTypes(who):
             raise CannotChangeInformationType("Forbidden by project policy.")
         self.information_type = information_type
         reconcile_access_for_artifacts([self], information_type, [self.target])
-        if (information_type in PRIVATE_INFORMATION_TYPES and
-                not self.subscribers.is_empty()):
+        if (
+            information_type in PRIVATE_INFORMATION_TYPES
+            and not self.subscribers.is_empty()
+        ):
             # Grant the subscribers access if they do not have a
             # policy grant.
-            service = getUtility(IService, 'sharing')
+            service = getUtility(IService, "sharing")
             blind_subscribers = service.getPeopleWithoutAccess(
-                self, self.subscribers)
+                self, self.subscribers
+            )
             if len(blind_subscribers):
                 service.ensureAccessGrants(
-                    blind_subscribers, who, specifications=[self],
-                    ignore_permissions=True)
+                    blind_subscribers,
+                    who,
+                    specifications=[self],
+                    ignore_permissions=True,
+                )
         return True
 
     @cachedproperty
@@ -956,7 +1150,8 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         # Avoid circular imports.
         from lp.blueprints.model.specificationsearch import (
             get_specification_privacy_filter,
-            )
+        )
+
         if self.information_type in PUBLIC_INFORMATION_TYPES:
             return True
         if user is None:
@@ -964,9 +1159,15 @@ class Specification(SQLBase, BugLinkTargetMixin, InformationTypeMixin):
         if user.id in self._known_viewers:
             return True
 
-        if not Store.of(self).find(
-            Specification, Specification.id == self.id,
-            *get_specification_privacy_filter(user)).is_empty():
+        if (
+            not Store.of(self)
+            .find(
+                Specification,
+                Specification.id == self.id,
+                *get_specification_privacy_filter(user),
+            )
+            .is_empty()
+        ):
             self._known_viewers.add(user.id)
             return True
         return False
@@ -977,9 +1178,16 @@ class HasSpecificationsMixin:
     for other classes that have specifications.
     """
 
-    def specifications(self, user, sort=None, quantity=None, filter=None,
-                       need_people=True, need_branches=True,
-                       need_workitems=False):
+    def specifications(
+        self,
+        user,
+        sort=None,
+        quantity=None,
+        filter=None,
+        need_people=True,
+        need_branches=True,
+        need_workitems=False,
+    ):
         """See IHasSpecifications."""
         # This should be implemented by the actual context class.
         raise NotImplementedError
@@ -992,8 +1200,10 @@ class HasSpecificationsMixin:
         # sort by priority descending, by default
         if sort is None or sort == SpecificationSort.PRIORITY:
             return (
-                Desc(Specification.priority), Specification.definition_status,
-                Specification.name)
+                Desc(Specification.priority),
+                Specification.definition_status,
+                Specification.name,
+            )
         elif sort == SpecificationSort.DATE:
             return (Desc(Specification.datecreated), Specification.id)
 
@@ -1007,17 +1217,20 @@ class HasSpecificationsMixin:
         """See IHasSpecifications."""
         user = getUtility(ILaunchBag).user
         return self.specifications(
-            user, filter=[SpecificationFilter.VALID], **kwargs)
+            user, filter=[SpecificationFilter.VALID], **kwargs
+        )
 
     @property
     def api_valid_specifications(self):
         return self.valid_specifications(
-            need_people=True, need_branches=True, need_workitems=True)
+            need_people=True, need_branches=True, need_workitems=True
+        )
 
     def specificationCount(self, user):
         """See IHasSpecifications."""
         return self.specifications(
-            user, filter=[SpecificationFilter.ALL]).count()
+            user, filter=[SpecificationFilter.ALL]
+        ).count()
 
 
 @implementer(ISpecificationSet)
@@ -1026,31 +1239,55 @@ class SpecificationSet(HasSpecificationsMixin):
 
     def __init__(self):
         """See ISpecificationSet."""
-        self.title = 'Specifications registered in Launchpad'
-        self.displayname = 'All Specifications'
+        self.title = "Specifications registered in Launchpad"
+        self.displayname = "All Specifications"
 
     def getStatusCountsForProductSeries(self, product_series):
         """See `ISpecificationSet`."""
         # Find specs targeted to the series or a milestone in the
         # series. The milestone set is materialised client-side to
         # get a good plan for the specification query.
-        return list(IStore(Specification).find(
-            (Specification.implementation_status, Count()),
-            Or(
-                Specification.productseries == product_series,
-                Specification.milestoneID.is_in(list(
-                    product_series.all_milestones.values(Milestone.id)))))
-            .group_by(Specification.implementation_status))
-
-    def specifications(self, user, sort=None, quantity=None, filter=None,
-                       need_people=True, need_branches=True,
-                       need_workitems=False):
+        return list(
+            IStore(Specification)
+            .find(
+                (Specification.implementation_status, Count()),
+                Or(
+                    Specification.productseries == product_series,
+                    Specification.milestoneID.is_in(
+                        list(
+                            product_series.all_milestones.values(Milestone.id)
+                        )
+                    ),
+                ),
+            )
+            .group_by(Specification.implementation_status)
+        )
+
+    def specifications(
+        self,
+        user,
+        sort=None,
+        quantity=None,
+        filter=None,
+        need_people=True,
+        need_branches=True,
+        need_workitems=False,
+    ):
         from lp.blueprints.model.specificationsearch import (
             search_specifications,
-            )
+        )
+
         return search_specifications(
-            self, [], user, sort, quantity, filter, need_people=need_people,
-            need_branches=need_branches, need_workitems=need_workitems)
+            self,
+            [],
+            user,
+            sort,
+            quantity,
+            filter,
+            need_people=need_people,
+            need_branches=need_branches,
+            need_workitems=need_workitems,
+        )
 
     def getByURL(self, url):
         """See ISpecificationSet."""
@@ -1069,18 +1306,32 @@ class SpecificationSet(HasSpecificationsMixin):
     def coming_sprints(self):
         """See ISpecificationSet."""
         from lp.blueprints.model.sprint import Sprint
+
         rows = IStore(Sprint).find(Sprint, Sprint.time_ends > UTC_NOW)
         return rows.order_by(Sprint.time_starts).config(limit=5)
 
-    def new(self, name, title, specurl, summary, definition_status,
-            owner, target, approver=None, assignee=None, drafter=None,
-            whiteboard=None, information_type=None):
+    def new(
+        self,
+        name,
+        title,
+        specurl,
+        summary,
+        definition_status,
+        owner,
+        target,
+        approver=None,
+        assignee=None,
+        drafter=None,
+        whiteboard=None,
+        information_type=None,
+    ):
         """See ISpecificationSet."""
         # Calculates a the default information_type based on the context
         # specification_sharing_policy.
         if information_type is None:
             information_type = (
-                target.pillar.getDefaultSpecificationInformationType())
+                target.pillar.getDefaultSpecificationInformationType()
+            )
         # Adapt the NewSpecificationDefinitionStatus item to a
         # SpecificationDefinitionStatus item.
         status_name = definition_status.name
@@ -1088,13 +1339,21 @@ class SpecificationSet(HasSpecificationsMixin):
         if status_name not in status_names:
             raise AssertionError(
                 "definition_status must an item found in "
-                "NewSpecificationDefinitionStatus.")
+                "NewSpecificationDefinitionStatus."
+            )
         definition_status = SpecificationDefinitionStatus.items[status_name]
         spec = Specification(
-            name=name, title=title, specurl=specurl, summary=summary,
-            definition_status=definition_status, owner=owner,
-            _approver=approver, _assignee=assignee, _drafter=drafter,
-            whiteboard=whiteboard)
+            name=name,
+            title=title,
+            specurl=specurl,
+            summary=summary,
+            definition_status=definition_status,
+            owner=owner,
+            _approver=approver,
+            _assignee=assignee,
+            _drafter=drafter,
+            whiteboard=whiteboard,
+        )
         spec.setTarget(target)
         spec.transitionToInformationType(information_type, None)
         return spec
@@ -1106,7 +1365,10 @@ class SpecificationSet(HasSpecificationsMixin):
         if len(specification_ids) == 0:
             return {}
 
-        results = Store.of(specifications[0]).execute("""
+        results = (
+            Store.of(specifications[0])
+            .execute(
+                """
             SELECT SpecificationDependency.specification,
                    SpecificationDependency.dependency
             FROM SpecificationDependency, Specification
@@ -1114,7 +1376,11 @@ class SpecificationSet(HasSpecificationsMixin):
             AND SpecificationDependency.dependency = Specification.id
             ORDER BY Specification.priority DESC, Specification.name,
                      Specification.id
-        """ % sqlvalues(specification_ids)).get_all()
+        """
+                % sqlvalues(specification_ids)
+            )
+            .get_all()
+        )
 
         dependencies = {}
         for spec_id, dep_id in results:
diff --git a/lib/lp/blueprints/model/specificationbranch.py b/lib/lp/blueprints/model/specificationbranch.py
index 462f291..65f3f1a 100644
--- a/lib/lp/blueprints/model/specificationbranch.py
+++ b/lib/lp/blueprints/model/specificationbranch.py
@@ -6,21 +6,16 @@
 __all__ = [
     "SpecificationBranch",
     "SpecificationBranchSet",
-    ]
+]
 
 import pytz
-from storm.locals import (
-    DateTime,
-    Int,
-    Reference,
-    Store,
-    )
+from storm.locals import DateTime, Int, Reference, Store
 from zope.interface import implementer
 
 from lp.blueprints.interfaces.specificationbranch import (
     ISpecificationBranch,
     ISpecificationBranchSet,
-    )
+)
 from lp.registry.interfaces.person import validate_public_person
 from lp.services.database.constants import UTC_NOW
 from lp.services.database.interfaces import IStore
@@ -31,20 +26,22 @@ from lp.services.database.stormbase import StormBase
 class SpecificationBranch(StormBase):
     """See `ISpecificationBranch`."""
 
-    __storm_table__ = 'SpecificationBranch'
+    __storm_table__ = "SpecificationBranch"
 
     id = Int(primary=True)
 
     datecreated = DateTime(
-        name='datecreated', tzinfo=pytz.UTC, allow_none=False, default=UTC_NOW)
-    specification_id = Int(name='specification', allow_none=False)
-    specification = Reference(specification_id, 'Specification.id')
-    branch_id = Int(name='branch', allow_none=False)
-    branch = Reference(branch_id, 'Branch.id')
+        name="datecreated", tzinfo=pytz.UTC, allow_none=False, default=UTC_NOW
+    )
+    specification_id = Int(name="specification", allow_none=False)
+    specification = Reference(specification_id, "Specification.id")
+    branch_id = Int(name="branch", allow_none=False)
+    branch = Reference(branch_id, "Branch.id")
 
     registrant_id = Int(
-        name='registrant', allow_none=False, validator=validate_public_person)
-    registrant = Reference(registrant_id, 'Person.id')
+        name="registrant", allow_none=False, validator=validate_public_person
+    )
+    registrant = Reference(registrant_id, "Person.id")
 
     def __init__(self, specification, branch, registrant):
         super().__init__()
@@ -70,4 +67,5 @@ class SpecificationBranchSet:
         # method will need to be updated to enforce the privacy checks.
         return IStore(SpecificationBranch).find(
             SpecificationBranch,
-            SpecificationBranch.branch_id.is_in(branch_ids))
+            SpecificationBranch.branch_id.is_in(branch_ids),
+        )
diff --git a/lib/lp/blueprints/model/specificationdependency.py b/lib/lp/blueprints/model/specificationdependency.py
index 28d7414..365b4be 100644
--- a/lib/lp/blueprints/model/specificationdependency.py
+++ b/lib/lp/blueprints/model/specificationdependency.py
@@ -1,13 +1,13 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__all__ = ['SpecificationDependency']
+__all__ = ["SpecificationDependency"]
 
 from zope.interface import implementer
 
 from lp.blueprints.interfaces.specificationdependency import (
     ISpecificationDependency,
-    )
+)
 from lp.services.database.sqlbase import SQLBase
 from lp.services.database.sqlobject import ForeignKey
 
@@ -16,8 +16,10 @@ from lp.services.database.sqlobject import ForeignKey
 class SpecificationDependency(SQLBase):
     """A link between a spec and a bug."""
 
-    _table = 'SpecificationDependency'
-    specification = ForeignKey(dbName='specification',
-        foreignKey='Specification', notNull=True)
-    dependency = ForeignKey(dbName='dependency',
-        foreignKey='Specification', notNull=True)
+    _table = "SpecificationDependency"
+    specification = ForeignKey(
+        dbName="specification", foreignKey="Specification", notNull=True
+    )
+    dependency = ForeignKey(
+        dbName="dependency", foreignKey="Specification", notNull=True
+    )
diff --git a/lib/lp/blueprints/model/specificationmessage.py b/lib/lp/blueprints/model/specificationmessage.py
index f3da615..6edb0e7 100644
--- a/lib/lp/blueprints/model/specificationmessage.py
+++ b/lib/lp/blueprints/model/specificationmessage.py
@@ -1,10 +1,7 @@
 # Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__all__ = [
-    'SpecificationMessage',
-    'SpecificationMessageSet'
-    ]
+__all__ = ["SpecificationMessage", "SpecificationMessageSet"]
 
 from email.utils import make_msgid
 
@@ -13,27 +10,22 @@ from zope.interface import implementer
 from lp.blueprints.interfaces.specificationmessage import (
     ISpecificationMessage,
     ISpecificationMessageSet,
-    )
+)
 from lp.services.database.sqlbase import SQLBase
-from lp.services.database.sqlobject import (
-    BoolCol,
-    ForeignKey,
-    )
-from lp.services.messages.model.message import (
-    Message,
-    MessageChunk,
-    )
+from lp.services.database.sqlobject import BoolCol, ForeignKey
+from lp.services.messages.model.message import Message, MessageChunk
 
 
 @implementer(ISpecificationMessage)
 class SpecificationMessage(SQLBase):
     """A table linking specifications and messages."""
 
-    _table = 'SpecificationMessage'
+    _table = "SpecificationMessage"
 
     specification = ForeignKey(
-        dbName='specification', foreignKey='Specification', notNull=True)
-    message = ForeignKey(dbName='message', foreignKey='Message', notNull=True)
+        dbName="specification", foreignKey="Specification", notNull=True
+    )
+    message = ForeignKey(dbName="message", foreignKey="Message", notNull=True)
     visible = BoolCol(notNull=True, default=True)
 
 
@@ -44,7 +36,8 @@ class SpecificationMessageSet:
     def createMessage(self, subject, spec, owner, content=None):
         """See ISpecificationMessageSet."""
         msg = Message(
-            owner=owner, rfc822msgid=make_msgid('blueprint'), subject=subject)
+            owner=owner, rfc822msgid=make_msgid("blueprint"), subject=subject
+        )
         MessageChunk(message=msg, content=content, sequence=1)
         return SpecificationMessage(specification=spec, message=msg)
 
diff --git a/lib/lp/blueprints/model/specificationsearch.py b/lib/lp/blueprints/model/specificationsearch.py
index 10ddbb4..f522ce2 100644
--- a/lib/lp/blueprints/model/specificationsearch.py
+++ b/lib/lp/blueprints/model/specificationsearch.py
@@ -4,11 +4,11 @@
 """Helper methods to search specifications."""
 
 __all__ = [
-    'get_specification_filters',
-    'get_specification_active_product_filter',
-    'get_specification_privacy_filter',
-    'search_specifications',
-    ]
+    "get_specification_filters",
+    "get_specification_active_product_filter",
+    "get_specification_privacy_filter",
+    "search_specifications",
+]
 
 from collections import defaultdict
 from functools import reduce
@@ -24,11 +24,8 @@ from storm.expr import (
     Select,
     Table,
     With,
-    )
-from storm.locals import (
-    Desc,
-    SQL,
-    )
+)
+from storm.locals import SQL, Desc
 from zope.component import getUtility
 
 from lp.app.enums import PUBLIC_INFORMATION_TYPES
@@ -38,7 +35,7 @@ from lp.blueprints.enums import (
     SpecificationGoalStatus,
     SpecificationImplementationStatus,
     SpecificationSort,
-    )
+)
 from lp.blueprints.model.specification import Specification
 from lp.blueprints.model.specificationbranch import SpecificationBranch
 from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
@@ -57,24 +54,37 @@ from lp.services.database.stormexpr import (
     ArrayAgg,
     ArrayIntersects,
     fti_search,
-    )
+)
 from lp.services.propertycache import get_property_cache
 
 
-def search_specifications(context, base_clauses, user, sort=None,
-                          quantity=None, spec_filter=None, tables=[],
-                          default_acceptance=False, need_people=True,
-                          need_branches=True, need_workitems=False):
+def search_specifications(
+    context,
+    base_clauses,
+    user,
+    sort=None,
+    quantity=None,
+    spec_filter=None,
+    tables=[],
+    default_acceptance=False,
+    need_people=True,
+    need_branches=True,
+    need_workitems=False,
+):
     store = IStore(Specification)
     if not default_acceptance:
         default = SpecificationFilter.INCOMPLETE
         options = {
-            SpecificationFilter.COMPLETE, SpecificationFilter.INCOMPLETE}
+            SpecificationFilter.COMPLETE,
+            SpecificationFilter.INCOMPLETE,
+        }
     else:
         default = SpecificationFilter.ACCEPTED
         options = {
-            SpecificationFilter.ACCEPTED, SpecificationFilter.DECLINED,
-            SpecificationFilter.PROPOSED}
+            SpecificationFilter.ACCEPTED,
+            SpecificationFilter.DECLINED,
+            SpecificationFilter.PROPOSED,
+        }
     if not spec_filter:
         spec_filter = [default]
 
@@ -85,7 +95,8 @@ def search_specifications(context, base_clauses, user, sort=None,
         tables = [Specification]
     clauses = base_clauses
     product_tables, product_clauses = get_specification_active_product_filter(
-        context)
+        context
+    )
     tables.extend(product_tables)
     clauses.extend(product_clauses)
     # If there are any base or product clauses, they typically have good
@@ -93,24 +104,28 @@ def search_specifications(context, base_clauses, user, sort=None,
     # up-front rather than doing a sequential scan for visible
     # specifications.
     if clauses:
-        RelevantSpecification = Table('RelevantSpecification')
+        RelevantSpecification = Table("RelevantSpecification")
         relevant_specification_cte = With(
             RelevantSpecification.name,
-            Select(Specification.id, And(clauses), tables=tables))
+            Select(Specification.id, And(clauses), tables=tables),
+        )
         store = store.with_(relevant_specification_cte)
         tables = [Specification]
         clauses = [
             Specification.id.is_in(
-                Select(Column('id', RelevantSpecification))),
-            ]
+                Select(Column("id", RelevantSpecification))
+            ),
+        ]
     clauses.extend(get_specification_privacy_filter(user))
     clauses.extend(get_specification_filters(spec_filter))
 
     # Sort by priority descending, by default.
     if sort is None or sort == SpecificationSort.PRIORITY:
         order = [
-            Desc(Specification.priority), Specification.definition_status,
-            Specification.name]
+            Desc(Specification.priority),
+            Specification.definition_status,
+            Specification.name,
+        ]
     elif sort == SpecificationSort.DATE:
         if SpecificationFilter.COMPLETE in spec_filter:
             # If we are showing completed, we care about date completed.
@@ -120,7 +135,9 @@ def search_specifications(context, base_clauses, user, sort=None,
             # registered.
             order = []
             show_proposed = {
-                SpecificationFilter.ALL, SpecificationFilter.PROPOSED}
+                SpecificationFilter.ALL,
+                SpecificationFilter.PROPOSED,
+            }
             if default_acceptance and not (set(spec_filter) & show_proposed):
                 order.append(Desc(Specification.date_goal_decided))
             order.extend([Desc(Specification.datecreated), Specification.id])
@@ -135,27 +152,38 @@ def search_specifications(context, base_clauses, user, sort=None,
         for spec in rows:
             if need_people:
                 person_ids |= {
-                    spec._assigneeID, spec._approverID, spec._drafterID}
+                    spec._assigneeID,
+                    spec._approverID,
+                    spec._drafterID,
+                }
             if need_branches:
                 get_property_cache(spec).linked_branches = []
         if need_workitems:
             work_items = load_referencing(
-                SpecificationWorkItem, rows, ['specification_id'],
-                extra_conditions=[SpecificationWorkItem.deleted == False])
+                SpecificationWorkItem,
+                rows,
+                ["specification_id"],
+                extra_conditions=[SpecificationWorkItem.deleted == False],
+            )
             for workitem in work_items:
                 person_ids.add(workitem.assignee_id)
                 work_items_by_spec[workitem.specification_id].append(workitem)
         person_ids -= {None}
         if need_people:
-            list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-                person_ids, need_validity=True))
+            list(
+                getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                    person_ids, need_validity=True
+                )
+            )
         if need_workitems:
             for spec in rows:
                 get_property_cache(spec).work_items = sorted(
-                    work_items_by_spec[spec.id], key=lambda wi: wi.sequence)
+                    work_items_by_spec[spec.id], key=lambda wi: wi.sequence
+                )
         if need_branches:
             spec_branches = load_referencing(
-                SpecificationBranch, rows, ['specification_id'])
+                SpecificationBranch, rows, ["specification_id"]
+            )
             for sbranch in spec_branches:
                 spec_cache = get_property_cache(sbranch.specification)
                 spec_cache.linked_branches.append(sbranch)
@@ -163,31 +191,41 @@ def search_specifications(context, base_clauses, user, sort=None,
     decorators = []
     if user is not None and not IPersonRoles(user).in_admin:
         decorators.append(_make_cache_user_can_view_spec(user))
-    results = store.using(*tables).find(
-        Specification, *clauses).order_by(*order).config(limit=quantity)
+    results = (
+        store.using(*tables)
+        .find(Specification, *clauses)
+        .order_by(*order)
+        .config(limit=quantity)
+    )
     return DecoratedResultSet(
         results,
         lambda row: reduce(lambda task, dec: dec(task), decorators, row),
-        pre_iter_hook=preload_hook)
+        pre_iter_hook=preload_hook,
+    )
 
 
 def get_specification_active_product_filter(context):
-    if (IDistribution.providedBy(context) or IDistroSeries.providedBy(context)
-        or IProduct.providedBy(context) or IProductSeries.providedBy(context)):
+    if (
+        IDistribution.providedBy(context)
+        or IDistroSeries.providedBy(context)
+        or IProduct.providedBy(context)
+        or IProductSeries.providedBy(context)
+    ):
         return [], []
     from lp.registry.model.product import Product
-    tables = [
-        LeftJoin(Product, Specification.productID == Product.id)]
-    active_products = (
-        Or(Specification.product == None, Product.active == True))
+
+    tables = [LeftJoin(Product, Specification.productID == Product.id)]
+    active_products = Or(Specification.product == None, Product.active == True)
     return tables, [active_products]
 
 
 def get_specification_privacy_filter(user):
     # Circular imports.
     from lp.registry.model.accesspolicy import AccessPolicyGrant
-    public_spec_filter = (
-        Specification.information_type.is_in(PUBLIC_INFORMATION_TYPES))
+
+    public_spec_filter = Specification.information_type.is_in(
+        PUBLIC_INFORMATION_TYPES
+    )
 
     if user is None:
         return [public_spec_filter]
@@ -196,24 +234,34 @@ def get_specification_privacy_filter(user):
 
     artifact_grant_query = Coalesce(
         ArrayIntersects(
-            SQL('Specification.access_grants'),
+            SQL("Specification.access_grants"),
             Select(
                 ArrayAgg(TeamParticipation.teamID),
                 tables=TeamParticipation,
-                where=(TeamParticipation.person == user)
-            )), False)
+                where=(TeamParticipation.person == user),
+            ),
+        ),
+        False,
+    )
 
     policy_grant_query = Coalesce(
         ArrayIntersects(
-            Array(SQL('Specification.access_policy')),
+            Array(SQL("Specification.access_policy")),
             Select(
                 ArrayAgg(AccessPolicyGrant.policy_id),
-                tables=(AccessPolicyGrant,
-                        Join(TeamParticipation,
-                            TeamParticipation.teamID ==
-                            AccessPolicyGrant.grantee_id)),
-                where=(TeamParticipation.person == user)
-            )), False)
+                tables=(
+                    AccessPolicyGrant,
+                    Join(
+                        TeamParticipation,
+                        TeamParticipation.teamID
+                        == AccessPolicyGrant.grantee_id,
+                    ),
+                ),
+                where=(TeamParticipation.person == user),
+            ),
+        ),
+        False,
+    )
 
     return [Or(public_spec_filter, artifact_grant_query, policy_grant_query)]
 
@@ -231,8 +279,9 @@ def get_specification_filters(filter, goalstatus=True):
     # Look for informational specs.
     if SpecificationFilter.INFORMATIONAL in filter:
         clauses.append(
-            Specification.implementation_status ==
-            SpecificationImplementationStatus.INFORMATIONAL)
+            Specification.implementation_status
+            == SpecificationImplementationStatus.INFORMATIONAL
+        )
     # Filter based on completion.  See the implementation of
     # Specification.is_complete() for more details.
     if SpecificationFilter.COMPLETE in filter:
@@ -258,9 +307,16 @@ def get_specification_filters(filter, goalstatus=True):
     # Filter for validity. If we want valid specs only, then we should exclude
     # all OBSOLETE or SUPERSEDED specs.
     if SpecificationFilter.VALID in filter:
-        clauses.append(Not(Specification.definition_status.is_in([
-            SpecificationDefinitionStatus.OBSOLETE,
-            SpecificationDefinitionStatus.SUPERSEDED])))
+        clauses.append(
+            Not(
+                Specification.definition_status.is_in(
+                    [
+                        SpecificationDefinitionStatus.OBSOLETE,
+                        SpecificationDefinitionStatus.SUPERSEDED,
+                    ]
+                )
+            )
+        )
     # Filter for specification text.
     for constraint in filter:
         if isinstance(constraint, str):
@@ -275,31 +331,45 @@ def _make_cache_user_can_view_spec(user):
     def cache_user_can_view_spec(spec):
         get_property_cache(spec)._known_viewers = {userid}
         return spec
+
     return cache_user_can_view_spec
 
 
 def get_specification_started_clause():
-    return Or(Not(Specification.implementation_status.is_in([
-        SpecificationImplementationStatus.UNKNOWN,
-        SpecificationImplementationStatus.NOTSTARTED,
-        SpecificationImplementationStatus.DEFERRED,
-        SpecificationImplementationStatus.INFORMATIONAL])),
-        And(Specification.implementation_status ==
-                SpecificationImplementationStatus.INFORMATIONAL,
-            Specification.definition_status ==
-                SpecificationDefinitionStatus.APPROVED))
+    return Or(
+        Not(
+            Specification.implementation_status.is_in(
+                [
+                    SpecificationImplementationStatus.UNKNOWN,
+                    SpecificationImplementationStatus.NOTSTARTED,
+                    SpecificationImplementationStatus.DEFERRED,
+                    SpecificationImplementationStatus.INFORMATIONAL,
+                ]
+            )
+        ),
+        And(
+            Specification.implementation_status
+            == SpecificationImplementationStatus.INFORMATIONAL,
+            Specification.definition_status
+            == SpecificationDefinitionStatus.APPROVED,
+        ),
+    )
 
 
 def get_specification_completeness_clause():
     return Or(
-        Specification.implementation_status ==
-            SpecificationImplementationStatus.IMPLEMENTED,
-        Specification.definition_status.is_in([
-            SpecificationDefinitionStatus.OBSOLETE,
-            SpecificationDefinitionStatus.SUPERSEDED,
-            ]),
+        Specification.implementation_status
+        == SpecificationImplementationStatus.IMPLEMENTED,
+        Specification.definition_status.is_in(
+            [
+                SpecificationDefinitionStatus.OBSOLETE,
+                SpecificationDefinitionStatus.SUPERSEDED,
+            ]
+        ),
         And(
-            Specification.implementation_status ==
-                SpecificationImplementationStatus.INFORMATIONAL,
-            Specification.definition_status ==
-                SpecificationDefinitionStatus.APPROVED))
+            Specification.implementation_status
+            == SpecificationImplementationStatus.INFORMATIONAL,
+            Specification.definition_status
+            == SpecificationDefinitionStatus.APPROVED,
+        ),
+    )
diff --git a/lib/lp/blueprints/model/specificationsubscription.py b/lib/lp/blueprints/model/specificationsubscription.py
index 98e18bf..1f5c268 100644
--- a/lib/lp/blueprints/model/specificationsubscription.py
+++ b/lib/lp/blueprints/model/specificationsubscription.py
@@ -1,23 +1,19 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__all__ = ['SpecificationSubscription']
+__all__ = ["SpecificationSubscription"]
 
-from storm.locals import (
-    Bool,
-    Int,
-    Reference,
-    )
+from storm.locals import Bool, Int, Reference
 from zope.component import getUtility
 from zope.interface import implementer
 
 from lp.blueprints.interfaces.specificationsubscription import (
     ISpecificationSubscription,
-    )
+)
 from lp.registry.interfaces.accesspolicy import (
     IAccessArtifactGrantSource,
     IAccessArtifactSource,
-    )
+)
 from lp.registry.interfaces.person import validate_person
 from lp.registry.interfaces.role import IPersonRoles
 from lp.services.database.stormbase import StormBase
@@ -27,12 +23,12 @@ from lp.services.database.stormbase import StormBase
 class SpecificationSubscription(StormBase):
     """A subscription for person to a spec."""
 
-    __storm_table__ = 'SpecificationSubscription'
+    __storm_table__ = "SpecificationSubscription"
     id = Int(primary=True)
-    specification_id = Int(name='specification', allow_none=False)
-    specification = Reference(specification_id, 'Specification.id')
-    person_id = Int(name='person', validator=validate_person, allow_none=False)
-    person = Reference(person_id, 'Person.id')
+    specification_id = Int(name="specification", allow_none=False)
+    specification = Reference(specification_id, "Specification.id")
+    person_id = Int(name="person", validator=validate_person, allow_none=False)
+    person = Reference(person_id, "Person.id")
     essential = Bool(allow_none=False, default=False)
 
     def __init__(self, specification, person, essential=False):
@@ -48,9 +44,10 @@ class SpecificationSubscription(StormBase):
         if not IPersonRoles.providedBy(user):
             user = IPersonRoles(user)
         if (
-            user.inTeam(self.specification.owner) or
-            user.inTeam(self.person) or
-            user.in_admin):
+            user.inTeam(self.specification.owner)
+            or user.inTeam(self.person)
+            or user.in_admin
+        ):
             return True
         # XXX Abel Deuring 2012-11-21, bug=1081677
         # People who subscribed users should be able to unsubscribe
@@ -61,7 +58,8 @@ class SpecificationSubscription(StormBase):
         # somebody else, but if the specification is private, we can
         # check who issued the artifact grant.
         artifacts = getUtility(IAccessArtifactSource).find(
-            [self.specification])
+            [self.specification]
+        )
         wanted = [(artifact, self.person) for artifact in artifacts]
         if len(wanted) == 0:
             return False
diff --git a/lib/lp/blueprints/model/specificationworkitem.py b/lib/lp/blueprints/model/specificationworkitem.py
index 1e2aed2..a7e5ec1 100644
--- a/lib/lp/blueprints/model/specificationworkitem.py
+++ b/lib/lp/blueprints/model/specificationworkitem.py
@@ -2,15 +2,10 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'SpecificationWorkItem',
-    ]
+    "SpecificationWorkItem",
+]
 
-from storm.locals import (
-    Bool,
-    Int,
-    Reference,
-    Unicode,
-    )
+from storm.locals import Bool, Int, Reference, Unicode
 from storm.store import Store
 from zope.interface import implementer
 
@@ -18,7 +13,7 @@ from lp.blueprints.enums import SpecificationWorkItemStatus
 from lp.blueprints.interfaces.specificationworkitem import (
     ISpecificationWorkItem,
     ISpecificationWorkItemSet,
-    )
+)
 from lp.registry.interfaces.person import validate_public_person
 from lp.services.database.constants import DEFAULT
 from lp.services.database.datetimecol import UtcDateTimeCol
@@ -30,32 +25,39 @@ from lp.services.helpers import backslashreplace
 @implementer(ISpecificationWorkItem)
 class SpecificationWorkItem(StormBase):
 
-    __storm_table__ = 'SpecificationWorkItem'
-    __storm_order__ = 'id'
+    __storm_table__ = "SpecificationWorkItem"
+    __storm_order__ = "id"
 
     id = Int(primary=True)
     title = Unicode(allow_none=False)
-    specification_id = Int(name='specification')
-    specification = Reference(specification_id, 'Specification.id')
-    assignee_id = Int(name='assignee', validator=validate_public_person)
-    assignee = Reference(assignee_id, 'Person.id')
-    milestone_id = Int(name='milestone')
-    milestone = Reference(milestone_id, 'Milestone.id')
+    specification_id = Int(name="specification")
+    specification = Reference(specification_id, "Specification.id")
+    assignee_id = Int(name="assignee", validator=validate_public_person)
+    assignee = Reference(assignee_id, "Person.id")
+    milestone_id = Int(name="milestone")
+    milestone = Reference(milestone_id, "Milestone.id")
     status = DBEnum(
         enum=SpecificationWorkItemStatus,
-        allow_none=False, default=SpecificationWorkItemStatus.TODO)
+        allow_none=False,
+        default=SpecificationWorkItemStatus.TODO,
+    )
     date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
     sequence = Int(allow_none=False)
     deleted = Bool(allow_none=False, default=False)
 
     def __repr__(self):
         title = backslashreplace(self.title)
-        assignee = getattr(self.assignee, 'name', None)
-        return '<SpecificationWorkItem [%s] %s: %s of %s>' % (
-            assignee, title, self.status.name, self.specification)
+        assignee = getattr(self.assignee, "name", None)
+        return "<SpecificationWorkItem [%s] %s: %s of %s>" % (
+            assignee,
+            title,
+            self.status.name,
+            self.specification,
+        )
 
-    def __init__(self, title, status, specification, assignee, milestone,
-                 sequence):
+    def __init__(
+        self, title, status, specification, assignee, milestone, sequence
+    ):
         self.title = title
         self.status = status
         self.specification = specification
@@ -71,9 +73,8 @@ class SpecificationWorkItem(StormBase):
 
 @implementer(ISpecificationWorkItemSet)
 class SpecificationWorkItemSet:
-
     def unlinkMilestone(self, milestone):
         """See `ISpecificationWorkItemSet`."""
         Store.of(milestone).find(
-            SpecificationWorkItem, milestone_id=milestone.id).set(
-                milestone_id=None)
+            SpecificationWorkItem, milestone_id=milestone.id
+        ).set(milestone_id=None)
diff --git a/lib/lp/blueprints/model/sprint.py b/lib/lp/blueprints/model/sprint.py
index ac002db..8d31d8d 100644
--- a/lib/lp/blueprints/model/sprint.py
+++ b/lib/lp/blueprints/model/sprint.py
@@ -2,10 +2,10 @@
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 __all__ = [
-    'Sprint',
-    'SprintSet',
-    'HasSprintsMixin',
-    ]
+    "Sprint",
+    "SprintSet",
+    "HasSprintsMixin",
+]
 
 import pytz
 from storm.locals import (
@@ -18,7 +18,7 @@ from storm.locals import (
     Reference,
     Store,
     Unicode,
-    )
+)
 from zope.component import getUtility
 from zope.interface import implementer
 
@@ -27,36 +27,27 @@ from lp.app.interfaces.launchpad import (
     IHasLogo,
     IHasMugshot,
     ILaunchpadCelebrities,
-    )
+)
 from lp.blueprints.enums import (
     SpecificationFilter,
     SpecificationSort,
     SprintSpecificationStatus,
-    )
-from lp.blueprints.interfaces.sprint import (
-    ISprint,
-    ISprintSet,
-    )
+)
+from lp.blueprints.interfaces.sprint import ISprint, ISprintSet
 from lp.blueprints.model.specification import (
     HasSpecificationsMixin,
     Specification,
-    )
+)
 from lp.blueprints.model.specificationsearch import (
     get_specification_active_product_filter,
     get_specification_filters,
     get_specification_privacy_filter,
-    )
+)
 from lp.blueprints.model.sprintattendance import SprintAttendance
 from lp.blueprints.model.sprintspecification import SprintSpecification
-from lp.registry.interfaces.person import (
-    IPersonSet,
-    validate_public_person,
-    )
+from lp.registry.interfaces.person import IPersonSet, validate_public_person
 from lp.registry.model.hasdrivers import HasDriversMixin
-from lp.services.database.constants import (
-    DEFAULT,
-    UTC_NOW,
-    )
+from lp.services.database.constants import DEFAULT, UTC_NOW
 from lp.services.database.interfaces import IStore
 from lp.services.database.sqlbase import flush_database_updates
 from lp.services.database.stormbase import StormBase
@@ -67,27 +58,28 @@ from lp.services.propertycache import cachedproperty
 class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
     """See `ISprint`."""
 
-    __storm_table__ = 'Sprint'
-    __storm_order__ = ['name']
+    __storm_table__ = "Sprint"
+    __storm_order__ = ["name"]
 
     # db field names
     id = Int(primary=True)
     owner_id = Int(
-        name='owner', validator=validate_public_person, allow_none=False)
-    owner = Reference(owner_id, 'Person.id')
+        name="owner", validator=validate_public_person, allow_none=False
+    )
+    owner = Reference(owner_id, "Person.id")
     name = Unicode(allow_none=False)
     title = Unicode(allow_none=False)
     summary = Unicode(allow_none=False)
-    driver_id = Int(name='driver', validator=validate_public_person)
-    driver = Reference(driver_id, 'Person.id')
+    driver_id = Int(name="driver", validator=validate_public_person)
+    driver = Reference(driver_id, "Person.id")
     home_page = Unicode(allow_none=True, default=None)
     homepage_content = Unicode(default=None)
-    icon_id = Int(name='icon', default=None)
-    icon = Reference(icon_id, 'LibraryFileAlias.id')
-    logo_id = Int(name='logo', default=None)
-    logo = Reference(logo_id, 'LibraryFileAlias.id')
-    mugshot_id = Int(name='mugshot', default=None)
-    mugshot = Reference(mugshot_id, 'LibraryFileAlias.id')
+    icon_id = Int(name="icon", default=None)
+    icon = Reference(icon_id, "LibraryFileAlias.id")
+    logo_id = Int(name="logo", default=None)
+    logo = Reference(logo_id, "LibraryFileAlias.id")
+    mugshot_id = Int(name="mugshot", default=None)
+    mugshot = Reference(mugshot_id, "LibraryFileAlias.id")
     address = Unicode(allow_none=True, default=None)
     datecreated = DateTime(tzinfo=pytz.UTC, allow_none=False, default=DEFAULT)
     time_zone = Unicode(allow_none=False)
@@ -95,9 +87,23 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
     time_ends = DateTime(tzinfo=pytz.UTC, allow_none=False)
     is_physical = Bool(allow_none=False, default=True)
 
-    def __init__(self, owner, name, title, time_zone, time_starts, time_ends,
-                 summary, address=None, driver=None, home_page=None,
-                 mugshot=None, logo=None, icon=None, is_physical=True):
+    def __init__(
+        self,
+        owner,
+        name,
+        title,
+        time_zone,
+        time_starts,
+        time_ends,
+        summary,
+        address=None,
+        driver=None,
+        home_page=None,
+        mugshot=None,
+        logo=None,
+        icon=None,
+        is_physical=True,
+    ):
         super().__init__()
         self.owner = owner
         self.name = name
@@ -144,12 +150,16 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
         """
         # Avoid circular imports.
         from lp.blueprints.model.specification import Specification
+
         tables, query = get_specification_active_product_filter(self)
         tables.insert(0, Specification)
         query.append(get_specification_privacy_filter(user))
-        tables.append(Join(
-            SprintSpecification,
-            SprintSpecification.specification == Specification.id))
+        tables.append(
+            Join(
+                SprintSpecification,
+                SprintSpecification.specification == Specification.id,
+            )
+        )
         query.append(SprintSpecification.sprint == self)
 
         if not filter:
@@ -174,8 +184,9 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
             sprint_status.append(SprintSpecificationStatus.PROPOSED)
         if SpecificationFilter.DECLINED in filter:
             sprint_status.append(SprintSpecificationStatus.DECLINED)
-        statuses = [SprintSpecification.status == status for status in
-                    sprint_status]
+        statuses = [
+            SprintSpecification.status == status for status in sprint_status
+        ]
         if len(statuses) > 0:
             query.append(Or(*statuses))
         # Filter for specification text
@@ -185,9 +196,16 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
     def all_specifications(self, user):
         return self.specifications(user, filter=[SpecificationFilter.ALL])
 
-    def specifications(self, user, sort=None, quantity=None, filter=None,
-                       need_people=False, need_branches=False,
-                       need_workitems=False):
+    def specifications(
+        self,
+        user,
+        sort=None,
+        quantity=None,
+        filter=None,
+        need_people=False,
+        need_branches=False,
+        need_workitems=False,
+    ):
         """See IHasSpecifications."""
         # need_* is provided only for interface compatibility and
         # need_*=True is not implemented.
@@ -196,14 +214,17 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
         tables, query = self.spec_filter_clause(user, filter)
         # import here to avoid circular deps
         from lp.blueprints.model.specification import Specification
+
         results = Store.of(self).using(*tables).find(Specification, *query)
         if sort == SpecificationSort.DATE:
             order = (Desc(SprintSpecification.date_created), Specification.id)
             distinct = [SprintSpecification.date_created, Specification.id]
             # we need to establish if the listing will show specs that have
             # been decided only, or will include proposed specs.
-            if (SpecificationFilter.ALL not in filter and
-                SpecificationFilter.PROPOSED not in filter):
+            if (
+                SpecificationFilter.ALL not in filter
+                and SpecificationFilter.PROPOSED not in filter
+            ):
                 # this will show only decided specs so use the date the spec
                 # was accepted or declined for the sprint
                 order = (Desc(SprintSpecification.date_decided),) + order
@@ -232,7 +253,7 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
         distros.
         """
         speclink = Store.of(self).get(SprintSpecification, speclink_id)
-        assert (speclink.sprint.id == self.id)
+        assert speclink.sprint.id == self.id
         return speclink
 
     def acceptSpecificationLinks(self, idlist, decider):
@@ -246,8 +267,9 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
         # queue
         flush_database_updates()
 
-        return self.specifications(decider,
-                        filter=[SpecificationFilter.PROPOSED]).count()
+        return self.specifications(
+            decider, filter=[SpecificationFilter.PROPOSED]
+        ).count()
 
     def declineSpecificationLinks(self, idlist, decider):
         """See `ISprint`."""
@@ -260,17 +282,23 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
         # queue
         flush_database_updates()
 
-        return self.specifications(decider,
-                        filter=[SpecificationFilter.PROPOSED]).count()
+        return self.specifications(
+            decider, filter=[SpecificationFilter.PROPOSED]
+        ).count()
 
     # attendance
     def attend(self, person, time_starts, time_ends, is_physical):
         """See `ISprint`."""
         # First see if a relevant attendance exists, and if so, update it.
-        attendance = Store.of(self).find(
-            SprintAttendance,
-            SprintAttendance.sprint == self,
-            SprintAttendance.attendee == person).one()
+        attendance = (
+            Store.of(self)
+            .find(
+                SprintAttendance,
+                SprintAttendance.sprint == self,
+                SprintAttendance.attendee == person,
+            )
+            .one()
+        )
         if attendance is None:
             # Since no previous attendance existed, create a new one.
             attendance = SprintAttendance(sprint=self, attendee=person)
@@ -284,34 +312,42 @@ class Sprint(StormBase, HasDriversMixin, HasSpecificationsMixin):
         Store.of(self).find(
             SprintAttendance,
             SprintAttendance.sprint == self,
-            SprintAttendance.attendee == person).remove()
+            SprintAttendance.attendee == person,
+        ).remove()
 
     @property
     def attendances(self):
-        result = list(Store.of(self).find(
-            SprintAttendance,
-            SprintAttendance.sprint == self))
+        result = list(
+            Store.of(self).find(
+                SprintAttendance, SprintAttendance.sprint == self
+            )
+        )
         people = [a.attendeeID for a in result]
         # In order to populate the person cache we need to materialize the
         # result set.  Listification should do.
-        list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
-                people, need_validity=True))
+        list(
+            getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+                people, need_validity=True
+            )
+        )
         return sorted(result, key=lambda a: a.attendee.displayname.lower())
 
     def isDriver(self, user):
         """See `ISprint`."""
         admins = getUtility(ILaunchpadCelebrities).admin
-        return (user.inTeam(self.owner) or
-                user.inTeam(self.driver) or
-                user.inTeam(admins))
+        return (
+            user.inTeam(self.owner)
+            or user.inTeam(self.driver)
+            or user.inTeam(admins)
+        )
 
     def destroySelf(self):
         Store.of(self).find(
-            SprintSpecification,
-            SprintSpecification.sprint == self).remove()
+            SprintSpecification, SprintSpecification.sprint == self
+        ).remove()
         Store.of(self).find(
-            SprintAttendance,
-            SprintAttendance.sprint == self).remove()
+            SprintAttendance, SprintAttendance.sprint == self
+        ).remove()
         Store.of(self).remove(self)
 
 
@@ -321,7 +357,7 @@ class SprintSet:
 
     def __init__(self):
         """See `ISprintSet`."""
-        self.title = 'Sprints and meetings'
+        self.title = "Sprints and meetings"
 
     def __getitem__(self, name):
         """See `ISprintSet`."""
@@ -329,22 +365,50 @@ class SprintSet:
 
     def __iter__(self):
         """See `ISprintSet`."""
-        return iter(IStore(Sprint).find(
-            Sprint, Sprint.time_ends > UTC_NOW).order_by(Sprint.time_starts))
+        return iter(
+            IStore(Sprint)
+            .find(Sprint, Sprint.time_ends > UTC_NOW)
+            .order_by(Sprint.time_starts)
+        )
 
     @property
     def all(self):
         return IStore(Sprint).find(Sprint).order_by(Sprint.time_starts)
 
-    def new(self, owner, name, title, time_zone, time_starts, time_ends,
-            summary, address=None, driver=None, home_page=None,
-            mugshot=None, logo=None, icon=None, is_physical=True):
+    def new(
+        self,
+        owner,
+        name,
+        title,
+        time_zone,
+        time_starts,
+        time_ends,
+        summary,
+        address=None,
+        driver=None,
+        home_page=None,
+        mugshot=None,
+        logo=None,
+        icon=None,
+        is_physical=True,
+    ):
         """See `ISprintSet`."""
-        return Sprint(owner=owner, name=name, title=title,
-            time_zone=time_zone, time_starts=time_starts,
-            time_ends=time_ends, summary=summary, driver=driver,
-            home_page=home_page, mugshot=mugshot, icon=icon,
-            logo=logo, address=address, is_physical=is_physical)
+        return Sprint(
+            owner=owner,
+            name=name,
+            title=title,
+            time_zone=time_zone,
+            time_starts=time_starts,
+            time_ends=time_ends,
+            summary=summary,
+            driver=driver,
+            home_page=home_page,
+            mugshot=mugshot,
+            icon=icon,
+            logo=logo,
+            address=address,
+            is_physical=is_physical,
+        )
 
 
 class HasSprintsMixin:
@@ -369,12 +433,16 @@ class HasSprintsMixin:
             Specification.id == SprintSpecification.specification_id,
             SprintSpecification.sprint == Sprint.id,
             SprintSpecification.status == SprintSpecificationStatus.ACCEPTED,
-            ]
+        ]
 
     def getSprints(self):
         clauses = self._getBaseClausesForQueryingSprints()
-        return IStore(Sprint).find(Sprint, *clauses).order_by(
-            Desc(Sprint.time_starts)).config(distinct=True)
+        return (
+            IStore(Sprint)
+            .find(Sprint, *clauses)
+            .order_by(Desc(Sprint.time_starts))
+            .config(distinct=True)
+        )
 
     @cachedproperty
     def sprints(self):
@@ -384,8 +452,12 @@ class HasSprintsMixin:
     def getComingSprints(self):
         clauses = self._getBaseClausesForQueryingSprints()
         clauses.append(Sprint.time_ends > UTC_NOW)
-        return IStore(Sprint).find(Sprint, *clauses).order_by(
-            Sprint.time_starts).config(distinct=True, limit=5)
+        return (
+            IStore(Sprint)
+            .find(Sprint, *clauses)
+            .order_by(Sprint.time_starts)
+            .config(distinct=True, limit=5)
+        )
 
     @cachedproperty
     def coming_sprints(self):
@@ -397,5 +469,9 @@ class HasSprintsMixin:
         """See IHasSprints."""
         clauses = self._getBaseClausesForQueryingSprints()
         clauses.append(Sprint.time_ends <= UTC_NOW)
-        return IStore(Sprint).find(Sprint, *clauses).order_by(
-            Desc(Sprint.time_starts)).config(distinct=True)
+        return (
+            IStore(Sprint)
+            .find(Sprint, *clauses)
+            .order_by(Desc(Sprint.time_starts))
+            .config(distinct=True)
+        )
diff --git a/lib/lp/blueprints/model/sprintattendance.py b/lib/lp/blueprints/model/sprintattendance.py
index 1ae9fa0..693d4f3 100644
--- a/lib/lp/blueprints/model/sprintattendance.py
+++ b/lib/lp/blueprints/model/sprintattendance.py
@@ -1,13 +1,9 @@
 # Copyright 2009 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__all__ = ['SprintAttendance']
+__all__ = ["SprintAttendance"]
 
-from storm.locals import (
-    Bool,
-    Int,
-    Reference,
-    )
+from storm.locals import Bool, Int, Reference
 from zope.interface import implementer
 
 from lp.blueprints.interfaces.sprintattendance import ISprintAttendance
@@ -20,19 +16,19 @@ from lp.services.database.stormbase import StormBase
 class SprintAttendance(StormBase):
     """A record of the attendance of a person at a sprint."""
 
-    __storm_table__ = 'SprintAttendance'
+    __storm_table__ = "SprintAttendance"
 
     id = Int(primary=True)
 
-    sprint_id = Int(name='sprint')
-    sprint = Reference(sprint_id, 'Sprint.id')
+    sprint_id = Int(name="sprint")
+    sprint = Reference(sprint_id, "Sprint.id")
 
-    attendeeID = Int(name='attendee', validator=validate_public_person)
-    attendee = Reference(attendeeID, 'Person.id')
+    attendeeID = Int(name="attendee", validator=validate_public_person)
+    attendee = Reference(attendeeID, "Person.id")
 
     time_starts = UtcDateTimeCol(notNull=True)
     time_ends = UtcDateTimeCol(notNull=True)
-    _is_physical = Bool(name='is_physical', default=True)
+    _is_physical = Bool(name="is_physical", default=True)
 
     def __init__(self, sprint, attendee):
         self.sprint = sprint
diff --git a/lib/lp/blueprints/model/sprintspecification.py b/lib/lp/blueprints/model/sprintspecification.py
index 1493e86..9d90b13 100644
--- a/lib/lp/blueprints/model/sprintspecification.py
+++ b/lib/lp/blueprints/model/sprintspecification.py
@@ -1,25 +1,16 @@
 # Copyright 2009-2020 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
-__all__ = ['SprintSpecification']
+__all__ = ["SprintSpecification"]
 
 import pytz
-from storm.locals import (
-    DateTime,
-    Int,
-    Reference,
-    Store,
-    Unicode,
-    )
+from storm.locals import DateTime, Int, Reference, Store, Unicode
 from zope.interface import implementer
 
 from lp.blueprints.enums import SprintSpecificationStatus
 from lp.blueprints.interfaces.sprintspecification import ISprintSpecification
 from lp.registry.interfaces.person import validate_public_person
-from lp.services.database.constants import (
-    DEFAULT,
-    UTC_NOW,
-    )
+from lp.services.database.constants import DEFAULT, UTC_NOW
 from lp.services.database.enumcol import DBEnum
 from lp.services.database.stormbase import StormBase
 
@@ -28,26 +19,32 @@ from lp.services.database.stormbase import StormBase
 class SprintSpecification(StormBase):
     """A link between a sprint and a specification."""
 
-    __storm_table__ = 'SprintSpecification'
+    __storm_table__ = "SprintSpecification"
 
     id = Int(primary=True)
 
-    sprint_id = Int(name='sprint', allow_none=False)
-    sprint = Reference(sprint_id, 'Sprint.id')
-    specification_id = Int(name='specification', allow_none=False)
-    specification = Reference(specification_id, 'Specification.id')
+    sprint_id = Int(name="sprint", allow_none=False)
+    sprint = Reference(sprint_id, "Sprint.id")
+    specification_id = Int(name="specification", allow_none=False)
+    specification = Reference(specification_id, "Specification.id")
     status = DBEnum(
-        enum=SprintSpecificationStatus, allow_none=False,
-        default=SprintSpecificationStatus.PROPOSED)
+        enum=SprintSpecificationStatus,
+        allow_none=False,
+        default=SprintSpecificationStatus.PROPOSED,
+    )
     whiteboard = Unicode(allow_none=True, default=None)
     registrant_id = Int(
-        name='registrant', validator=validate_public_person, allow_none=False)
-    registrant = Reference(registrant_id, 'Person.id')
+        name="registrant", validator=validate_public_person, allow_none=False
+    )
+    registrant = Reference(registrant_id, "Person.id")
     date_created = DateTime(tzinfo=pytz.UTC, allow_none=False, default=DEFAULT)
     decider_id = Int(
-        name='decider', validator=validate_public_person, allow_none=True,
-        default=None)
-    decider = Reference(decider_id, 'Person.id')
+        name="decider",
+        validator=validate_public_person,
+        allow_none=True,
+        default=None,
+    )
+    decider = Reference(decider_id, "Person.id")
     date_decided = DateTime(tzinfo=pytz.UTC, allow_none=True, default=None)
 
     def __init__(self, sprint, specification, registrant):
diff --git a/lib/lp/blueprints/model/tests/test_specification.py b/lib/lp/blueprints/model/tests/test_specification.py
index 5de7970..f383166 100644
--- a/lib/lp/blueprints/model/tests/test_specification.py
+++ b/lib/lp/blueprints/model/tests/test_specification.py
@@ -3,12 +3,9 @@
 
 """Unit tests for blueprints here."""
 
-from testtools.matchers import (
-    Equals,
-    MatchesStructure,
-    )
-from testtools.testcase import ExpectedException
 import transaction
+from testtools.matchers import Equals, MatchesStructure
+from testtools.testcase import ExpectedException
 from zope.component import getUtility
 from zope.security.interfaces import Unauthorized
 from zope.security.proxy import removeSecurityProxy
@@ -20,12 +17,9 @@ from lp.blueprints.interfaces.specification import ISpecification
 from lp.blueprints.interfaces.specificationworkitem import (
     ISpecificationWorkItemSet,
     SpecificationWorkItemStatus,
-    )
+)
 from lp.blueprints.model.specificationworkitem import SpecificationWorkItem
-from lp.registry.enums import (
-    SharingPermission,
-    SpecificationSharingPolicy,
-    )
+from lp.registry.enums import SharingPermission, SpecificationSharingPolicy
 from lp.registry.errors import CannotChangeInformationType
 from lp.registry.model.milestone import Milestone
 from lp.services.mail import stub
@@ -34,11 +28,11 @@ from lp.services.webapp import canonical_url
 from lp.services.webapp.snapshot import notify_modified
 from lp.testing import (
     ANONYMOUS,
+    TestCaseWithFactory,
     login,
     login_person,
     person_logged_in,
-    TestCaseWithFactory,
-    )
+)
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
@@ -89,13 +83,17 @@ class TestSpecificationDependencies(TestCaseWithFactory):
         do_last.createDependency(do_next_lhs)
         do_last.createDependency(do_next_rhs)
         self.assertContentEqual(
-            [do_next_lhs, do_next_rhs], do_first.getBlockedSpecs())
+            [do_next_lhs, do_next_rhs], do_first.getBlockedSpecs()
+        )
         self.assertContentEqual(
-            [do_next_lhs, do_next_rhs, do_last], do_first.all_blocked())
+            [do_next_lhs, do_next_rhs, do_last], do_first.all_blocked()
+        )
         self.assertContentEqual(
-            [do_next_lhs, do_next_rhs], do_last.getDependencies())
+            [do_next_lhs, do_next_rhs], do_last.getDependencies()
+        )
         self.assertContentEqual(
-            [do_first, do_next_lhs, do_next_rhs], do_last.all_deps())
+            [do_first, do_next_lhs, do_next_rhs], do_last.all_deps()
+        )
 
     def test_all_deps_filters(self):
         # all_deps, when provided a user, shows only the dependencies the user
@@ -103,10 +101,12 @@ class TestSpecificationDependencies(TestCaseWithFactory):
         sharing_policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         owner = self.factory.makePerson()
         product = self.factory.makeProduct(
-            owner=owner, specification_sharing_policy=sharing_policy)
+            owner=owner, specification_sharing_policy=sharing_policy
+        )
         root = self.factory.makeBlueprint(product=product)
         proprietary_dep = self.factory.makeBlueprint(
-            product=product, information_type=InformationType.PROPRIETARY)
+            product=product, information_type=InformationType.PROPRIETARY
+        )
         public_dep = self.factory.makeBlueprint(product=product)
         root.createDependency(proprietary_dep)
         root.createDependency(public_dep)
@@ -114,10 +114,12 @@ class TestSpecificationDependencies(TestCaseWithFactory):
         self.assertEqual([public_dep], root.all_deps())
         # The owner of the product can see everything.
         self.assertEqual(
-            [proprietary_dep, public_dep], root.all_deps(user=owner))
+            [proprietary_dep, public_dep], root.all_deps(user=owner)
+        )
         # A random person can't see the proprietary dependency.
         self.assertEqual(
-            [public_dep], root.all_deps(user=self.factory.makePerson()))
+            [public_dep], root.all_deps(user=self.factory.makePerson())
+        )
 
     def test_all_blocked_filters(self):
         # all_blocked, when provided a user, shows only the blocked specs the
@@ -125,25 +127,26 @@ class TestSpecificationDependencies(TestCaseWithFactory):
         sharing_policy = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         owner = self.factory.makePerson()
         product = self.factory.makeProduct(
-            owner=owner, specification_sharing_policy=sharing_policy)
+            owner=owner, specification_sharing_policy=sharing_policy
+        )
         root = self.factory.makeBlueprint(product=product)
         proprietary_blocked = self.factory.makeBlueprint(
-            product=product, information_type=InformationType.PROPRIETARY)
+            product=product, information_type=InformationType.PROPRIETARY
+        )
         public_blocked = self.factory.makeBlueprint(product=product)
         with person_logged_in(owner):
             proprietary_blocked.createDependency(root)
         public_blocked.createDependency(root)
         # Anonymous (no user) requests only get public blocked specs.
-        self.assertEqual(
-            [public_blocked], root.all_blocked())
+        self.assertEqual([public_blocked], root.all_blocked())
         # The owner of the product can see everything.
         self.assertEqual(
-            [proprietary_blocked, public_blocked],
-            root.all_blocked(user=owner))
+            [proprietary_blocked, public_blocked], root.all_blocked(user=owner)
+        )
         # A random person can't see the proprietary blocked spec.
         self.assertEqual(
-            [public_blocked],
-            root.all_blocked(user=self.factory.makePerson()))
+            [public_blocked], root.all_blocked(user=self.factory.makePerson())
+        )
 
 
 class TestSpecificationSubscriptionSort(TestCaseWithFactory):
@@ -154,14 +157,17 @@ class TestSpecificationSubscriptionSort(TestCaseWithFactory):
         # Subscriptions are sorted by subscriber's displayname without regard
         # to case
         spec = self.factory.makeBlueprint()
-        bob = self.factory.makePerson(name='zbob', displayname='Bob')
-        ced = self.factory.makePerson(name='xed', displayname='ced')
-        dave = self.factory.makePerson(name='wdave', displayname='Dave')
+        bob = self.factory.makePerson(name="zbob", displayname="Bob")
+        ced = self.factory.makePerson(name="xed", displayname="ced")
+        dave = self.factory.makePerson(name="wdave", displayname="Dave")
         spec.subscribe(bob, bob, True)
         spec.subscribe(ced, bob, True)
         spec.subscribe(dave, bob, True)
-        sorted_subscriptions = [bob.displayname, ced.displayname,
-            dave.displayname]
+        sorted_subscriptions = [
+            bob.displayname,
+            ced.displayname,
+            dave.displayname,
+        ]
         people = [sub.person.displayname for sub in spec.subscriptions]
         self.assertEqual(sorted_subscriptions, people)
 
@@ -171,126 +177,138 @@ class TestSpecificationValidation(TestCaseWithFactory):
     layer = DatabaseFunctionalLayer
 
     def test_specurl_validation_duplicate(self):
-        existing = self.factory.makeSpecification(specurl='http://ubuntu.com')
+        existing = self.factory.makeSpecification(specurl="http://ubuntu.com";)
         spec = self.factory.makeSpecification()
         url = canonical_url(existing)
-        field = ISpecification['specurl'].bind(spec)
+        field = ISpecification["specurl"].bind(spec)
         e = self.assertRaises(
-            LaunchpadValidationError, field.validate, 'http://ubuntu.com')
+            LaunchpadValidationError, field.validate, "http://ubuntu.com";
+        )
         self.assertEqual(
             '%s is already registered by <a href="%s">%s</a>.'
-            % ('http://ubuntu.com', url, existing.title), str(e))
+            % ("http://ubuntu.com";, url, existing.title),
+            str(e),
+        )
 
     def test_specurl_validation_valid(self):
         spec = self.factory.makeSpecification()
-        field = ISpecification['specurl'].bind(spec)
-        field.validate('http://example.com/nigelb')
+        field = ISpecification["specurl"].bind(spec)
+        field.validate("http://example.com/nigelb";)
 
     def test_specurl_validation_escape(self):
         existing = self.factory.makeSpecification(
-            specurl='http://ubuntu.com/foo',
-            title='<script>alert("foo");</script>')
-        cleaned_title = '&lt;script&gt;alert(&quot;foo&quot;);&lt;/script&gt;'
+            specurl="http://ubuntu.com/foo";,
+            title='<script>alert("foo");</script>',
+        )
+        cleaned_title = "&lt;script&gt;alert(&quot;foo&quot;);&lt;/script&gt;"
         spec = self.factory.makeSpecification()
         url = canonical_url(existing)
-        field = ISpecification['specurl'].bind(spec)
+        field = ISpecification["specurl"].bind(spec)
         e = self.assertRaises(
-            LaunchpadValidationError, field.validate, 'http://ubuntu.com/foo')
+            LaunchpadValidationError, field.validate, "http://ubuntu.com/foo";
+        )
         self.assertEqual(
             '%s is already registered by <a href="%s">%s</a>.'
-            % ('http://ubuntu.com/foo', url, cleaned_title), str(e))
+            % ("http://ubuntu.com/foo";, url, cleaned_title),
+            str(e),
+        )
 
 
 class TestSpecificationWorkItemsNotifications(TestCaseWithFactory):
-    """ Test the notification related to SpecificationWorkItems on
+    """Test the notification related to SpecificationWorkItems on
     ISpecification."""
 
     layer = DatabaseFunctionalLayer
 
     def test_workitems_added_notification_message(self):
-        """ Test that we get a notification for setting work items on a new
+        """Test that we get a notification for setting work items on a new
         specification."""
         stub.test_emails = []
         spec = self.factory.makeSpecification()
         # For API requests, lazr.restful does the notification; for this
         # test we need to call ourselves.
-        with notify_modified(spec, ['workitems_text']):
+        with notify_modified(spec, ["workitems_text"]):
             new_work_item = {
-                'title': 'A work item',
-                'status': SpecificationWorkItemStatus.TODO,
-                'assignee': None,
-                'milestone': None,
-                'sequence': 0
+                "title": "A work item",
+                "status": SpecificationWorkItemStatus.TODO,
+                "assignee": None,
+                "milestone": None,
+                "sequence": 0,
             }
             login_person(spec.owner)
             spec.updateWorkItems([new_work_item])
         transaction.commit()
 
         self.assertEqual(1, len(stub.test_emails))
-        rationale = 'Work items set to:\nWork items:\n%s: %s' % (
-            new_work_item['title'],
-            new_work_item['status'].name)
+        rationale = "Work items set to:\nWork items:\n%s: %s" % (
+            new_work_item["title"],
+            new_work_item["status"].name,
+        )
         [email] = stub.test_emails
         # Actual message is part 2 of the email.
-        msg = email[2].decode('UTF-8')
+        msg = email[2].decode("UTF-8")
         self.assertIn(rationale, msg)
 
     def test_workitems_deleted_notification_message(self):
-        """ Test that we get a notification for deleting a work item."""
+        """Test that we get a notification for deleting a work item."""
         stub.test_emails = []
         wi = self.factory.makeSpecificationWorkItem()
         spec = wi.specification
         # In production this notification is fired by lazr.restful, but we
         # need to do it ourselves in this test.
-        with notify_modified(spec, ['workitems_text']):
+        with notify_modified(spec, ["workitems_text"]):
             login_person(spec.owner)
             spec.updateWorkItems([])
         transaction.commit()
 
         self.assertEqual(1, len(stub.test_emails))
-        rationale = '- %s: %s' % (wi.title, wi.status.name)
+        rationale = "- %s: %s" % (wi.title, wi.status.name)
         [email] = stub.test_emails
         # Actual message is part 2 of the email.
-        msg = email[2].decode('UTF-8')
+        msg = email[2].decode("UTF-8")
         self.assertIn(rationale, msg)
 
     def test_workitems_changed_notification_message(self):
-        """ Test that we get a notification about a work item status change.
+        """Test that we get a notification about a work item status change.
         This will be in the form of a line added and one deleted."""
         spec = self.factory.makeSpecification()
         original_status = SpecificationWorkItemStatus.TODO
         new_status = SpecificationWorkItemStatus.DONE
         original_work_item = {
-            'title': 'The same work item',
-            'status': original_status,
-            'assignee': None,
-            'milestone': None,
-            'sequence': 0
+            "title": "The same work item",
+            "status": original_status,
+            "assignee": None,
+            "milestone": None,
+            "sequence": 0,
         }
         new_work_item = {
-            'title': 'The same work item',
-            'status': new_status,
-            'assignee': None,
-            'milestone': None,
-            'sequence': 0
+            "title": "The same work item",
+            "status": new_status,
+            "assignee": None,
+            "milestone": None,
+            "sequence": 0,
         }
         login_person(spec.owner)
         spec.updateWorkItems([original_work_item])
         # In production this notification is fired by lazr.restful, but we
         # need to do it ourselves in this test.
-        with notify_modified(spec, ['workitems_text']):
+        with notify_modified(spec, ["workitems_text"]):
             stub.test_emails = []
             spec.updateWorkItems([new_work_item])
         transaction.commit()
 
         self.assertEqual(1, len(stub.test_emails))
-        rationale_removed = '- %s: %s' % (
-            original_work_item['title'], original_work_item['status'].name)
-        rationale_added = '+ %s: %s' % (
-            new_work_item['title'], new_work_item['status'].name)
+        rationale_removed = "- %s: %s" % (
+            original_work_item["title"],
+            original_work_item["status"].name,
+        )
+        rationale_added = "+ %s: %s" % (
+            new_work_item["title"],
+            new_work_item["status"].name,
+        )
         [email] = stub.test_emails
         # Actual message is part 2 of the email.
-        msg = email[2].decode('UTF-8')
+        msg = email[2].decode("UTF-8")
         self.assertIn(rationale_removed, msg)
         self.assertIn(rationale_added, msg)
 
@@ -303,17 +321,19 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
     def setUp(self):
         super().setUp()
         self.wi_header = self.factory.makeMilestone(
-            name='none-milestone-as-header')
+            name="none-milestone-as-header"
+        )
 
     def assertWorkItemsTextContains(self, spec, items):
         expected_lines = []
         for item in items:
             if isinstance(item, SpecificationWorkItem):
-                line = ''
+                line = ""
                 if item.assignee is not None:
                     line = "[%s] " % item.assignee.name
-                expected_lines.append("%s%s: %s" % (line, item.title,
-                                                    item.status.name))
+                expected_lines.append(
+                    "%s%s: %s" % (line, item.title, item.status.name)
+                )
             else:
                 self.assertIsInstance(item, Milestone)
                 if expected_lines != []:
@@ -328,24 +348,28 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
     def test_anonymous_newworkitem_not_allowed(self):
         spec = self.factory.makeSpecification()
         login(ANONYMOUS)
-        self.assertRaises(Unauthorized, getattr, spec, 'newWorkItem')
+        self.assertRaises(Unauthorized, getattr, spec, "newWorkItem")
 
     def test_owner_newworkitem_allowed(self):
         spec = self.factory.makeSpecification()
         login_person(spec.owner)
-        work_item = spec.newWorkItem(title='new-work-item', sequence=0)
+        work_item = spec.newWorkItem(title="new-work-item", sequence=0)
         self.assertIsInstance(work_item, SpecificationWorkItem)
 
     def test_newworkitem_uses_passed_arguments(self):
-        title = 'new-work-item'
+        title = "new-work-item"
         spec = self.factory.makeSpecification()
         assignee = self.factory.makePerson()
         milestone = self.factory.makeMilestone(product=spec.product)
         status = SpecificationWorkItemStatus.DONE
         login_person(spec.owner)
         work_item = spec.newWorkItem(
-            title=title, assignee=assignee, milestone=milestone,
-            status=status, sequence=0)
+            title=title,
+            assignee=assignee,
+            milestone=milestone,
+            status=status,
+            sequence=0,
+        )
         self.assertEqual(spec, work_item.specification)
         self.assertEqual(assignee, work_item.assignee)
         self.assertEqual(status, work_item.status)
@@ -354,41 +378,55 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
 
     def test_workitems_text_no_workitems(self):
         spec = self.factory.makeSpecification()
-        self.assertEqual('', spec.workitems_text)
+        self.assertEqual("", spec.workitems_text)
 
     def test_workitems_text_deleted_workitem(self):
         work_item = self.factory.makeSpecificationWorkItem(deleted=True)
-        self.assertEqual('', work_item.specification.workitems_text)
+        self.assertEqual("", work_item.specification.workitems_text)
 
     def test_workitems_text_single_workitem(self):
         work_item = self.factory.makeSpecificationWorkItem()
-        self.assertWorkItemsTextContains(work_item.specification,
-                                         [self.wi_header, work_item])
+        self.assertWorkItemsTextContains(
+            work_item.specification, [self.wi_header, work_item]
+        )
 
     def test_workitems_text_multi_workitems_all_statuses(self):
         spec = self.factory.makeSpecification()
-        work_item1 = self.factory.makeSpecificationWorkItem(specification=spec,
-            status=SpecificationWorkItemStatus.TODO)
-        work_item2 = self.factory.makeSpecificationWorkItem(specification=spec,
-            status=SpecificationWorkItemStatus.DONE)
-        work_item3 = self.factory.makeSpecificationWorkItem(specification=spec,
-            status=SpecificationWorkItemStatus.POSTPONED)
-        work_item4 = self.factory.makeSpecificationWorkItem(specification=spec,
-            status=SpecificationWorkItemStatus.INPROGRESS)
-        work_item5 = self.factory.makeSpecificationWorkItem(specification=spec,
-            status=SpecificationWorkItemStatus.BLOCKED)
-        work_items = [self.wi_header, work_item1, work_item2, work_item3,
-                      work_item4, work_item5]
+        work_item1 = self.factory.makeSpecificationWorkItem(
+            specification=spec, status=SpecificationWorkItemStatus.TODO
+        )
+        work_item2 = self.factory.makeSpecificationWorkItem(
+            specification=spec, status=SpecificationWorkItemStatus.DONE
+        )
+        work_item3 = self.factory.makeSpecificationWorkItem(
+            specification=spec, status=SpecificationWorkItemStatus.POSTPONED
+        )
+        work_item4 = self.factory.makeSpecificationWorkItem(
+            specification=spec, status=SpecificationWorkItemStatus.INPROGRESS
+        )
+        work_item5 = self.factory.makeSpecificationWorkItem(
+            specification=spec, status=SpecificationWorkItemStatus.BLOCKED
+        )
+        work_items = [
+            self.wi_header,
+            work_item1,
+            work_item2,
+            work_item3,
+            work_item4,
+            work_item5,
+        ]
         self.assertWorkItemsTextContains(spec, work_items)
 
     def test_workitems_text_with_milestone(self):
         spec = self.factory.makeSpecification()
         milestone = self.factory.makeMilestone(product=spec.product)
         login_person(spec.owner)
-        work_item = self.factory.makeSpecificationWorkItem(specification=spec,
-            title='new-work-item',
+        work_item = self.factory.makeSpecificationWorkItem(
+            specification=spec,
+            title="new-work-item",
             status=SpecificationWorkItemStatus.TODO,
-            milestone=milestone)
+            milestone=milestone,
+        )
         items = [milestone, work_item]
         self.assertWorkItemsTextContains(spec, items)
 
@@ -396,14 +434,18 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         spec = self.factory.makeSpecification()
         milestone = self.factory.makeMilestone(product=spec.product)
         login_person(spec.owner)
-        work_item1 = self.factory.makeSpecificationWorkItem(specification=spec,
-            title='Work item with default milestone',
+        work_item1 = self.factory.makeSpecificationWorkItem(
+            specification=spec,
+            title="Work item with default milestone",
             status=SpecificationWorkItemStatus.TODO,
-            milestone=None)
-        work_item2 = self.factory.makeSpecificationWorkItem(specification=spec,
-            title='Work item with set milestone',
+            milestone=None,
+        )
+        work_item2 = self.factory.makeSpecificationWorkItem(
+            specification=spec,
+            title="Work item with set milestone",
             status=SpecificationWorkItemStatus.TODO,
-            milestone=milestone)
+            milestone=milestone,
+        )
         items = [self.wi_header, work_item1, milestone, work_item2]
         self.assertWorkItemsTextContains(spec, items)
 
@@ -411,14 +453,18 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         spec = self.factory.makeSpecification()
         milestone = self.factory.makeMilestone(product=spec.product)
         login_person(spec.owner)
-        work_item1 = self.factory.makeSpecificationWorkItem(specification=spec,
-            title='Work item with set milestone',
+        work_item1 = self.factory.makeSpecificationWorkItem(
+            specification=spec,
+            title="Work item with set milestone",
             status=SpecificationWorkItemStatus.TODO,
-            milestone=milestone)
-        work_item2 = self.factory.makeSpecificationWorkItem(specification=spec,
-            title='Work item with default milestone',
+            milestone=milestone,
+        )
+        work_item2 = self.factory.makeSpecificationWorkItem(
+            specification=spec,
+            title="Work item with default milestone",
             status=SpecificationWorkItemStatus.TODO,
-            milestone=None)
+            milestone=None,
+        )
         items = [milestone, work_item1, self.wi_header, work_item2]
         self.assertWorkItemsTextContains(spec, items)
 
@@ -427,14 +473,18 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         milestone1 = self.factory.makeMilestone(product=spec.product)
         milestone2 = self.factory.makeMilestone(product=spec.product)
         login_person(spec.owner)
-        work_item1 = self.factory.makeSpecificationWorkItem(specification=spec,
-            title='Work item with first milestone',
+        work_item1 = self.factory.makeSpecificationWorkItem(
+            specification=spec,
+            title="Work item with first milestone",
             status=SpecificationWorkItemStatus.TODO,
-            milestone=milestone1)
-        work_item2 = self.factory.makeSpecificationWorkItem(specification=spec,
-            title='Work item with second milestone',
+            milestone=milestone1,
+        )
+        work_item2 = self.factory.makeSpecificationWorkItem(
+            specification=spec,
+            title="Work item with second milestone",
             status=SpecificationWorkItemStatus.TODO,
-            milestone=milestone2)
+            milestone=milestone2,
+        )
         items = [milestone1, work_item1, milestone2, work_item2]
         self.assertWorkItemsTextContains(spec, items)
 
@@ -442,18 +492,22 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         assignee = self.factory.makePerson()
         work_item = self.factory.makeSpecificationWorkItem(assignee=assignee)
         self.assertWorkItemsTextContains(
-            work_item.specification, [self.wi_header, work_item])
+            work_item.specification, [self.wi_header, work_item]
+        )
 
     def test_work_items_property(self):
         spec = self.factory.makeSpecification()
         wi1 = self.factory.makeSpecificationWorkItem(
-            specification=spec, sequence=2)
+            specification=spec, sequence=2
+        )
         wi2 = self.factory.makeSpecificationWorkItem(
-            specification=spec, sequence=1)
+            specification=spec, sequence=1
+        )
         # This work item won't be included in the results of spec.work_items
         # because it is deleted.
         self.factory.makeSpecificationWorkItem(
-            specification=spec, sequence=3, deleted=True)
+            specification=spec, sequence=3, deleted=True
+        )
         # This work item belongs to a different spec so it won't be returned
         # by spec.work_items.
         self.factory.makeSpecificationWorkItem()
@@ -464,14 +518,21 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         a new entry for every element in the list given to it.
         """
         spec = self.factory.makeSpecification(
-            product=self.factory.makeProduct())
+            product=self.factory.makeProduct()
+        )
         milestone = self.factory.makeMilestone(product=spec.product)
         work_item1_data = dict(
-            title='Foo Bar', status=SpecificationWorkItemStatus.DONE,
-            assignee=spec.owner, milestone=None)
+            title="Foo Bar",
+            status=SpecificationWorkItemStatus.DONE,
+            assignee=spec.owner,
+            milestone=None,
+        )
         work_item2_data = dict(
-            title='Bar Foo', status=SpecificationWorkItemStatus.TODO,
-            assignee=None, milestone=milestone)
+            title="Bar Foo",
+            status=SpecificationWorkItemStatus.TODO,
+            assignee=None,
+            milestone=milestone,
+        )
 
         # We start with no work items.
         self.assertEqual([], list(spec.work_items))
@@ -485,20 +546,23 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         # The data dicts we pass to updateWorkItems() have no sequence because
         # that's taken from their position on the list, so we update our data
         # dicts with the sequence we expect our work items to have.
-        work_item1_data['sequence'] = 0
-        work_item2_data['sequence'] = 1
+        work_item1_data["sequence"] = 0
+        work_item2_data["sequence"] = 1
 
         # Assert that the work items ultimately inserted in the DB are exactly
         # what we expect them to be.
         created_wi1, created_wi2 = list(spec.work_items)
         self.assertThat(
-            created_wi1, MatchesStructure.byEquality(**work_item1_data))
+            created_wi1, MatchesStructure.byEquality(**work_item1_data)
+        )
         self.assertThat(
-            created_wi2, MatchesStructure.byEquality(**work_item2_data))
+            created_wi2, MatchesStructure.byEquality(**work_item2_data)
+        )
 
     def test_updateWorkItems_merges_with_existing_ones(self):
         spec = self.factory.makeSpecification(
-            product=self.factory.makeProduct())
+            product=self.factory.makeProduct()
+        )
         login_person(spec.owner)
         # Create two work-items in our database.
         wi1_data = self._createWorkItemAndReturnDataDict(spec)
@@ -507,11 +571,17 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
 
         # These are the work items we'll be inserting.
         new_wi1_data = dict(
-            title='Some Title', status=SpecificationWorkItemStatus.TODO,
-            assignee=None, milestone=None)
+            title="Some Title",
+            status=SpecificationWorkItemStatus.TODO,
+            assignee=None,
+            milestone=None,
+        )
         new_wi2_data = dict(
-            title='Other title', status=SpecificationWorkItemStatus.TODO,
-            assignee=None, milestone=None)
+            title="Other title",
+            status=SpecificationWorkItemStatus.TODO,
+            assignee=None,
+            milestone=None,
+        )
 
         # We want to insert the two work items above in the first and third
         # positions respectively, so the existing ones to be moved around
@@ -521,10 +591,10 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
 
         # Update our data dicts with the sequences we expect the work items in
         # our DB to have.
-        new_wi1_data['sequence'] = 0
-        wi1_data['sequence'] = 1
-        new_wi2_data['sequence'] = 2
-        wi2_data['sequence'] = 3
+        new_wi1_data["sequence"] = 0
+        wi1_data["sequence"] = 1
+        new_wi2_data["sequence"] = 2
+        wi2_data["sequence"] = 3
 
         self.assertEqual(4, len(spec.work_items))
         for data, obj in zip(work_items, list(spec.work_items)):
@@ -532,7 +602,8 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
 
     def _dup_work_items_set_up(self):
         spec = self.factory.makeSpecification(
-            product=self.factory.makeProduct())
+            product=self.factory.makeProduct()
+        )
         login_person(spec.owner)
         # Create two work-items in our database.
         wi1_data = self._createWorkItemAndReturnDataDict(spec)
@@ -541,15 +612,15 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         # Create a duplicate and a near duplicate, insert into DB.
         new_wi1_data = wi2_data.copy()
         new_wi2_data = new_wi1_data.copy()
-        new_wi2_data['status'] = SpecificationWorkItemStatus.DONE
+        new_wi2_data["status"] = SpecificationWorkItemStatus.DONE
         work_items = [new_wi1_data, wi1_data, new_wi2_data, wi2_data]
         spec.updateWorkItems(work_items)
 
         # Update our data dicts with the sequences to match data in DB
-        new_wi1_data['sequence'] = 0
-        wi1_data['sequence'] = 1
-        new_wi2_data['sequence'] = 2
-        wi2_data['sequence'] = 3
+        new_wi1_data["sequence"] = 0
+        wi1_data["sequence"] = 1
+        new_wi2_data["sequence"] = 2
+        wi2_data["sequence"] = 3
 
         self.assertEqual(4, len(spec.work_items))
         for data, obj in zip(work_items, spec.work_items):
@@ -562,7 +633,7 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
 
         # Test that we can insert another duplicate work item.
         new_wi3_data = work_items[0].copy()
-        new_wi3_data['sequence'] = 4
+        new_wi3_data["sequence"] = 4
         work_items.append(new_wi3_data)
         spec.updateWorkItems(work_items)
 
@@ -590,13 +661,15 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
 
         # This time we're only changing the existing work item; we'll change
         # its assignee and status.
-        wi_data.update(dict(status=SpecificationWorkItemStatus.DONE,
-                            assignee=spec.owner))
+        wi_data.update(
+            dict(status=SpecificationWorkItemStatus.DONE, assignee=spec.owner)
+        )
         spec.updateWorkItems([wi_data])
 
         self.assertEqual(1, len(spec.work_items))
         self.assertThat(
-            spec.work_items[0], MatchesStructure.byEquality(**wi_data))
+            spec.work_items[0], MatchesStructure.byEquality(**wi_data)
+        )
 
     def test_updateWorkItems_deletes_all_if_given_empty_list(self):
         work_item = self.factory.makeSpecificationWorkItem()
@@ -617,9 +690,10 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         # of the second will be changed.
         spec.updateWorkItems([wi2_data])
         self.assertEqual(1, len(spec.work_items))
-        wi2_data['sequence'] = 0
+        wi2_data["sequence"] = 0
         self.assertThat(
-            spec.work_items[0], MatchesStructure.byEquality(**wi2_data))
+            spec.work_items[0], MatchesStructure.byEquality(**wi2_data)
+        )
 
     def _createWorkItemAndReturnDataDict(self, spec):
         """Create a new work item for the given spec using the next available
@@ -634,21 +708,29 @@ class TestSpecificationWorkItems(TestCaseWithFactory):
         else:
             sequence = max(wi.sequence for wi in spec.work_items) + 1
         wi = self.factory.makeSpecificationWorkItem(
-            specification=spec, sequence=sequence)
+            specification=spec, sequence=sequence
+        )
         del get_property_cache(spec).work_items
         return dict(
-            title=wi.title, status=wi.status, assignee=wi.assignee,
-            milestone=wi.milestone, sequence=sequence)
+            title=wi.title,
+            status=wi.status,
+            assignee=wi.assignee,
+            milestone=wi.milestone,
+            sequence=sequence,
+        )
 
     def test_workitemspecificationset_can_unlink_milestones(self):
         milestone_a = self.factory.makeMilestone()
         milestone_b = self.factory.makeMilestone()
         work_item_1 = self.factory.makeSpecificationWorkItem(
-            milestone=milestone_a)
+            milestone=milestone_a
+        )
         work_item_2 = self.factory.makeSpecificationWorkItem(
-            milestone=milestone_a)
+            milestone=milestone_a
+        )
         work_item_3 = self.factory.makeSpecificationWorkItem(
-            milestone=milestone_b)
+            milestone=milestone_b
+        )
 
         self.assertEqual(milestone_a, work_item_1.milestone)
         self.assertEqual(milestone_a, work_item_2.milestone)
@@ -669,31 +751,36 @@ class TestSpecificationInformationType(TestCaseWithFactory):
         """Ensure transitionToInformationType works."""
         public_private = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         product = self.factory.makeProduct(
-            specification_sharing_policy=public_private)
+            specification_sharing_policy=public_private
+        )
         spec = self.factory.makeSpecification(product=product)
         self.assertEqual(InformationType.PUBLIC, spec.information_type)
         removeSecurityProxy(spec.target)._ensurePolicies(
-            [InformationType.PROPRIETARY])
+            [InformationType.PROPRIETARY]
+        )
         with person_logged_in(spec.owner):
             result = spec.transitionToInformationType(
-                InformationType.PROPRIETARY, spec.owner)
+                InformationType.PROPRIETARY, spec.owner
+            )
             self.assertEqual(
-                InformationType.PROPRIETARY, spec.information_type)
+                InformationType.PROPRIETARY, spec.information_type
+            )
         self.assertTrue(result)
 
     def test_transitionToInformationType_no_change(self):
         """Return False on no change."""
         spec = self.factory.makeSpecification()
         with person_logged_in(spec.owner):
-            result = spec.transitionToInformationType(InformationType.PUBLIC,
-                                                      spec.owner)
+            result = spec.transitionToInformationType(
+                InformationType.PUBLIC, spec.owner
+            )
         self.assertFalse(result)
 
     def test_transitionToInformationType_forbidden(self):
         """Raise if specified type is not supported."""
         spec = self.factory.makeSpecification()
         with person_logged_in(spec.owner):
-            with ExpectedException(CannotChangeInformationType, '.*'):
+            with ExpectedException(CannotChangeInformationType, ".*"):
                 spec.transitionToInformationType(None, spec.owner)
 
     def test_transitionToInformationType_adds_grants_for_subscribers(self):
@@ -702,38 +789,54 @@ class TestSpecificationInformationType(TestCaseWithFactory):
         owner = self.factory.makePerson()
         public_private = SpecificationSharingPolicy.PUBLIC_OR_PROPRIETARY
         product = self.factory.makeProduct(
-            owner=owner,
-            specification_sharing_policy=public_private)
+            owner=owner, specification_sharing_policy=public_private
+        )
         spec = self.factory.makeSpecification(product=product)
         subscriber_with_policy_grant = self.factory.makePerson()
         subscriber_without_policy_grant = self.factory.makePerson()
-        service = getUtility(IService, 'sharing')
+        service = getUtility(IService, "sharing")
         with person_logged_in(owner):
             service.sharePillarInformation(
-                product, subscriber_with_policy_grant, owner,
+                product,
+                subscriber_with_policy_grant,
+                owner,
                 permissions={
-            InformationType.PROPRIETARY: SharingPermission.ALL,
-            })
+                    InformationType.PROPRIETARY: SharingPermission.ALL,
+                },
+            )
             spec.subscribe(subscriber_with_policy_grant, owner)
             spec.subscribe(subscriber_without_policy_grant, owner)
 
             # The specification is public, hence subscribers do not need
             #  and do not have access grants.
             self.assertEqual(
-                [], service.getSharedSpecifications(
-                    product, subscriber_without_policy_grant, owner))
+                [],
+                service.getSharedSpecifications(
+                    product, subscriber_without_policy_grant, owner
+                ),
+            )
             self.assertEqual(
-                [], service.getSharedSpecifications(
-                    product, subscriber_with_policy_grant, owner))
+                [],
+                service.getSharedSpecifications(
+                    product, subscriber_with_policy_grant, owner
+                ),
+            )
 
             spec.transitionToInformationType(
-                InformationType.PROPRIETARY, owner)
+                InformationType.PROPRIETARY, owner
+            )
             # transitionToInformationType() added an artifact grant for
             # subscriber_without_policy_grant.
             self.assertEqual(
-                [spec], service.getSharedSpecifications(
-                    product, subscriber_without_policy_grant, owner))
+                [spec],
+                service.getSharedSpecifications(
+                    product, subscriber_without_policy_grant, owner
+                ),
+            )
             # No access grant was created for subscriber_with_policy_grant.
             self.assertEqual(
-                [], service.getSharedSpecifications(
-                    product, subscriber_with_policy_grant, owner))
+                [],
+                service.getSharedSpecifications(
+                    product, subscriber_with_policy_grant, owner
+                ),
+            )
diff --git a/lib/lp/blueprints/model/tests/test_sprint.py b/lib/lp/blueprints/model/tests/test_sprint.py
index 4e6b483..4f186fa 100644
--- a/lib/lp/blueprints/model/tests/test_sprint.py
+++ b/lib/lp/blueprints/model/tests/test_sprint.py
@@ -16,12 +16,9 @@ from lp.blueprints.enums import (
     SpecificationFilter,
     SpecificationPriority,
     SpecificationSort,
-    )
+)
 from lp.registry.interfaces.accesspolicy import IAccessPolicySource
-from lp.testing import (
-    person_logged_in,
-    TestCaseWithFactory,
-    )
+from lp.testing import TestCaseWithFactory, person_logged_in
 from lp.testing.layers import DatabaseFunctionalLayer
 
 
@@ -38,15 +35,27 @@ class TestSpecifications(TestCaseWithFactory):
         super().setUp()
         self.date_decided = datetime.datetime.now(utc)
 
-    def makeSpec(self, sprint=None, date_decided=0, date_created=0,
-                 proposed=False, declined=False, title=None,
-                 status=NewSpecificationDefinitionStatus.NEW,
-                 name=None, priority=None, information_type=None):
+    def makeSpec(
+        self,
+        sprint=None,
+        date_decided=0,
+        date_created=0,
+        proposed=False,
+        declined=False,
+        title=None,
+        status=NewSpecificationDefinitionStatus.NEW,
+        name=None,
+        priority=None,
+        information_type=None,
+    ):
         if sprint is None:
             sprint = self.factory.makeSprint()
         blueprint = self.factory.makeSpecification(
-            title=title, status=status, name=name,
-            information_type=information_type)
+            title=title,
+            status=status,
+            name=name,
+            information_type=information_type,
+        )
         owner = removeSecurityProxy(blueprint).owner
         if priority is not None:
             removeSecurityProxy(blueprint).priority = priority
@@ -132,15 +141,21 @@ class TestSpecifications(TestCaseWithFactory):
     def test_priority_sort(self):
         # Sorting by priority works and is the default.
         # When priority is supplied, status is ignored.
-        blueprint1 = self.makeSpec(priority=SpecificationPriority.UNDEFINED,
-                                   status=SpecificationDefinitionStatus.NEW)
+        blueprint1 = self.makeSpec(
+            priority=SpecificationPriority.UNDEFINED,
+            status=SpecificationDefinitionStatus.NEW,
+        )
         sprint = blueprint1.sprints[0]
         blueprint2 = self.makeSpec(
-            sprint, priority=SpecificationPriority.NOTFORUS,
-            status=SpecificationDefinitionStatus.APPROVED)
+            sprint,
+            priority=SpecificationPriority.NOTFORUS,
+            status=SpecificationDefinitionStatus.APPROVED,
+        )
         blueprint3 = self.makeSpec(
-            sprint, priority=SpecificationPriority.LOW,
-            status=SpecificationDefinitionStatus.OBSOLETE)
+            sprint,
+            priority=SpecificationPriority.LOW,
+            status=SpecificationDefinitionStatus.OBSOLETE,
+        )
         result = sprint.specifications(None)
         self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
         result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
@@ -150,12 +165,15 @@ class TestSpecifications(TestCaseWithFactory):
         # Sorting by priority falls back to defintion_status.
         # When status is supplied, name is ignored.
         blueprint1 = self.makeSpec(
-            status=SpecificationDefinitionStatus.OBSOLETE, name='a')
+            status=SpecificationDefinitionStatus.OBSOLETE, name="a"
+        )
         sprint = blueprint1.sprints[0]
         blueprint2 = self.makeSpec(
-            sprint, status=SpecificationDefinitionStatus.APPROVED, name='c')
+            sprint, status=SpecificationDefinitionStatus.APPROVED, name="c"
+        )
         blueprint3 = self.makeSpec(
-            sprint, status=SpecificationDefinitionStatus.NEW, name='b')
+            sprint, status=SpecificationDefinitionStatus.NEW, name="b"
+        )
         result = sprint.specifications(None)
         self.assertEqual([blueprint2, blueprint3, blueprint1], list(result))
         result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
@@ -163,10 +181,10 @@ class TestSpecifications(TestCaseWithFactory):
 
     def test_priority_sort_fallback_name(self):
         # Sorting by priority falls back to name
-        blueprint1 = self.makeSpec(name='b')
+        blueprint1 = self.makeSpec(name="b")
         sprint = blueprint1.sprints[0]
-        blueprint2 = self.makeSpec(sprint, name='c')
-        blueprint3 = self.makeSpec(sprint, name='a')
+        blueprint2 = self.makeSpec(sprint, name="c")
+        blueprint3 = self.makeSpec(sprint, name="a")
         result = sprint.specifications(None)
         self.assertEqual([blueprint3, blueprint1, blueprint2], list(result))
         result = sprint.specifications(None, sort=SpecificationSort.PRIORITY)
@@ -174,12 +192,12 @@ class TestSpecifications(TestCaseWithFactory):
 
     def test_text_search(self):
         # Text searches work.
-        blueprint1 = self.makeSpec(title='abc')
+        blueprint1 = self.makeSpec(title="abc")
         sprint = blueprint1.sprints[0]
-        blueprint2 = self.makeSpec(sprint, title='def')
-        result = list_result(sprint, ['abc'])
+        blueprint2 = self.makeSpec(sprint, title="def")
+        result = list_result(sprint, ["abc"])
         self.assertEqual([blueprint1], result)
-        result = list_result(sprint, ['def'])
+        result = list_result(sprint, ["def"])
         self.assertEqual([blueprint2], result)
 
     def test_declined(self):
@@ -193,28 +211,37 @@ class TestSpecifications(TestCaseWithFactory):
     def test_proprietary_not_listed(self):
         # Proprietary blueprints are not listed for random users
         blueprint1 = self.makeSpec(
-            information_type=InformationType.PROPRIETARY)
+            information_type=InformationType.PROPRIETARY
+        )
         sprint = removeSecurityProxy(blueprint1).sprints[0]
         self.assertEqual([], list_result(sprint))
 
     def test_proprietary_listed_for_artifact_grant(self):
         # Proprietary blueprints are listed for users with an artifact grant.
         blueprint1 = self.makeSpec(
-            information_type=InformationType.PROPRIETARY)
+            information_type=InformationType.PROPRIETARY
+        )
         sprint = removeSecurityProxy(blueprint1).sprints[0]
         grant = self.factory.makeAccessArtifactGrant(
-            concrete_artifact=blueprint1)
+            concrete_artifact=blueprint1
+        )
         self.assertEqual([blueprint1], list_result(sprint, user=grant.grantee))
 
     def test_proprietary_listed_for_policy_grant(self):
         # Proprietary blueprints are listed for users with a policy grant.
         blueprint1 = self.makeSpec(
-            information_type=InformationType.PROPRIETARY)
+            information_type=InformationType.PROPRIETARY
+        )
         sprint = removeSecurityProxy(blueprint1).sprints[0]
         policy_source = getUtility(IAccessPolicySource)
         (policy,) = policy_source.find(
-