launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28618
[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><script>window.alert('XSS')</script>"
- "</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 = '<script>alert("foo");</script>'
+ specurl="http://ubuntu.com/foo",
+ title='<script>alert("foo");</script>',
+ )
+ cleaned_title = "<script>alert("foo");</script>"
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(
-