launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28865
[Merge] ~cjwatson/launchpad:black-registry into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:black-registry into launchpad:master.
Commit message:
lp.registry: Apply black
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/427243
--
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-registry into launchpad:master.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 1166ab6..fd2e058 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -78,3 +78,5 @@ ed7d7b97b8fb4ebe92799f922b0fa9c4bd1714e8
1e6ead9387a1d073eea9271fe8dc646bb22fa3d2
# apply black to lp.oci
06b1048510a0b65bb0a6fc46d4e063510b52b5f6
+# apply black to lp.registry
+0a9f5f09fc0c930b73619e77524e60f2740595ed
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 43f0476..7db349d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -53,6 +53,7 @@ repos:
|codehosting
|coop
|oci
+ |registry
)/
- repo: https://github.com/PyCQA/isort
rev: 5.9.2
@@ -82,6 +83,7 @@ repos:
|codehosting
|coop
|oci
+ |registry
)/
- id: isort
alias: isort-black
@@ -101,6 +103,7 @@ repos:
|codehosting
|coop
|oci
+ |registry
)/
- repo: https://github.com/PyCQA/flake8
rev: 3.9.2
diff --git a/lib/lp/registry/adapters.py b/lib/lp/registry/adapters.py
index ef21952..de7ce44 100644
--- a/lib/lp/registry/adapters.py
+++ b/lib/lp/registry/adapters.py
@@ -4,11 +4,11 @@
"""Adapters for registry objects."""
__all__ = [
- 'distroseries_to_distribution',
- 'PollSubset',
- 'productseries_to_product',
- 'sourcepackage_to_distribution',
- ]
+ "distroseries_to_distribution",
+ "PollSubset",
+ "productseries_to_product",
+ "sourcepackage_to_distribution",
+]
from zope.component import getUtility
@@ -22,7 +22,7 @@ from lp.registry.interfaces.poll import (
IPollSubset,
PollAlgorithm,
PollStatus,
- )
+)
from lp.services.webapp.interfaces import ILaunchpadPrincipal
@@ -63,56 +63,85 @@ def person_from_principal(principal):
class PollSubset:
"""Adapt an `IPoll` to an `IPollSubset`."""
- title = 'Team polls'
+ title = "Team polls"
def __init__(self, team=None):
self.team = team
- def new(self, name, title, proposition, dateopens, datecloses,
- secrecy, allowspoilt, poll_type=PollAlgorithm.SIMPLE):
+ def new(
+ self,
+ name,
+ title,
+ proposition,
+ dateopens,
+ datecloses,
+ secrecy,
+ allowspoilt,
+ poll_type=PollAlgorithm.SIMPLE,
+ ):
"""See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
+ assert (
+ self.team is not None
+ ), "team cannot be None to call this method."
return getUtility(IPollSet).new(
- self.team, name, title, proposition, dateopens,
- datecloses, secrecy, allowspoilt, poll_type)
+ self.team,
+ name,
+ title,
+ proposition,
+ dateopens,
+ datecloses,
+ secrecy,
+ allowspoilt,
+ poll_type,
+ )
def getByName(self, name, default=None):
"""See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
+ assert (
+ self.team is not None
+ ), "team cannot be None to call this method."
pollset = getUtility(IPollSet)
return pollset.getByTeamAndName(self.team, name, default)
def getAll(self):
"""See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
+ assert (
+ self.team is not None
+ ), "team cannot be None to call this method."
return getUtility(IPollSet).findByTeam(self.team)
def getOpenPolls(self, when=None):
"""See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
+ assert (
+ self.team is not None
+ ), "team cannot be None to call this method."
return getUtility(IPollSet).findByTeam(
- self.team, [PollStatus.OPEN],
- order_by=PollSort.CLOSING, when=when)
+ self.team, [PollStatus.OPEN], order_by=PollSort.CLOSING, when=when
+ )
def getClosedPolls(self, when=None):
"""See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
+ assert (
+ self.team is not None
+ ), "team cannot be None to call this method."
return getUtility(IPollSet).findByTeam(
- self.team, [PollStatus.CLOSED],
- order_by=PollSort.CLOSING, when=when)
+ self.team,
+ [PollStatus.CLOSED],
+ order_by=PollSort.CLOSING,
+ when=when,
+ )
def getNotYetOpenedPolls(self, when=None):
"""See IPollSubset."""
- assert self.team is not None, (
- 'team cannot be None to call this method.')
+ assert (
+ self.team is not None
+ ), "team cannot be None to call this method."
return getUtility(IPollSet).findByTeam(
- self.team, [PollStatus.NOT_YET_OPENED],
- order_by=PollSort.OPENING, when=when)
+ self.team,
+ [PollStatus.NOT_YET_OPENED],
+ order_by=PollSort.OPENING,
+ when=when,
+ )
def productseries_to_product(productseries):
diff --git a/lib/lp/registry/browser/__init__.py b/lib/lp/registry/browser/__init__.py
index c9b6f86..403345b 100644
--- a/lib/lp/registry/browser/__init__.py
+++ b/lib/lp/registry/browser/__init__.py
@@ -4,42 +4,39 @@
"""Common registry browser helpers and mixins."""
__all__ = [
- 'add_subscribe_link',
- 'BaseRdfView',
- 'get_status_counts',
- 'MilestoneOverlayMixin',
- 'RDFIndexView',
- 'RegistryEditFormView',
- 'RegistryDeleteViewMixin',
- 'StatusCount',
- ]
+ "add_subscribe_link",
+ "BaseRdfView",
+ "get_status_counts",
+ "MilestoneOverlayMixin",
+ "RDFIndexView",
+ "RegistryEditFormView",
+ "RegistryDeleteViewMixin",
+ "StatusCount",
+]
-from operator import attrgetter
import os
+from operator import attrgetter
from storm.store import Store
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
from lp.app.browser.folder import ExportedFolder
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadEditFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadEditFormView, action
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.blueprints.interfaces.specificationworkitem import (
ISpecificationWorkItemSet,
- )
+)
from lp.bugs.interfaces.bugtask import IBugTaskSet
from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
from lp.registry.interfaces.productseries import IProductSeries
from lp.registry.interfaces.series import SeriesStatus
from lp.services.webapp.publisher import (
- canonical_url,
DataDownloadView,
LaunchpadView,
- )
+ canonical_url,
+)
class StatusCount:
@@ -55,7 +52,7 @@ class StatusCount:
self.count = count
-def get_status_counts(workitems, status_attr, key='sortkey'):
+def get_status_counts(workitems, status_attr, key="sortkey"):
"""Return a list StatusCounts summarising the workitem."""
statuses = {}
for workitem in workitems:
@@ -68,12 +65,13 @@ def get_status_counts(workitems, status_attr, key='sortkey'):
statuses[status] += 1
return [
StatusCount(status, statuses[status])
- for status in sorted(statuses, key=attrgetter(key))]
+ for status in sorted(statuses, key=attrgetter(key))
+ ]
def add_subscribe_link(links):
"""Add the subscription-related links."""
- links.extend(['subscribe_to_bug_mail', 'edit_bug_mail'])
+ links.extend(["subscribe_to_bug_mail", "edit_bug_mail"])
class MilestoneOverlayMixin:
@@ -84,7 +82,7 @@ class MilestoneOverlayMixin:
@property
def milestone_form_uri(self):
"""URI for form displayed by the formoverlay widget."""
- return canonical_url(self.context) + '/+addmilestone/++form++'
+ return canonical_url(self.context) + "/+addmilestone/++form++"
@property
def series_api_uri(self):
@@ -95,11 +93,11 @@ class MilestoneOverlayMixin:
def milestone_table_class(self):
"""The milestone table will be hidden if there are no milestones."""
if self.context.has_milestones:
- return 'listing'
+ return "listing"
else:
# The page can remove the 'hidden' class to make the table
# visible.
- return 'listing hidden'
+ return "listing hidden"
@property
def milestone_row_uri_template(self):
@@ -108,17 +106,18 @@ class MilestoneOverlayMixin:
else:
pillar = self.context.distribution
uri = canonical_url(pillar, path_only_if_possible=True)
- return '%s/+milestone/{name}/+productseries-table-row' % uri
+ return "%s/+milestone/{name}/+productseries-table-row" % uri
@property
def register_milestone_script(self):
"""Return the script to enable milestone creation via AJAX."""
uris = {
- 'series_api_uri': self.series_api_uri,
- 'milestone_form_uri': self.milestone_form_uri,
- 'milestone_row_uri': self.milestone_row_uri_template,
- }
- return """
+ "series_api_uri": self.series_api_uri,
+ "milestone_form_uri": self.milestone_form_uri,
+ "milestone_row_uri": self.milestone_row_uri_template,
+ }
+ return (
+ """
LPJS.use(
'node', 'lp.registry.milestoneoverlay',
'lp.registry.milestonetable',
@@ -148,7 +147,9 @@ class MilestoneOverlayMixin:
Y.lp.registry.milestonetable.setup(table_config);
});
});
- """ % uris
+ """
+ % uris
+ )
class RegistryDeleteViewMixin:
@@ -166,8 +167,8 @@ class RegistryDeleteViewMixin:
params.setProductSeries(target)
else:
params = BugTaskSearchParams(
- milestone=target, user=self.user,
- ignore_privacy=ignore_privacy)
+ milestone=target, user=self.user, ignore_privacy=ignore_privacy
+ )
bugtasks = getUtility(IBugTaskSet).search(params)
return list(bugtasks)
@@ -221,9 +222,12 @@ class RegistryDeleteViewMixin:
# Series are not deleted because some objects like translations are
# problematic. The series is assigned to obsolete-junk. They must be
# renamed to avoid name collision.
- date_time = series.datecreated.strftime('%Y%m%d-%H%M%S')
- series.name = '%s-%s-%s' % (
- series.product.name, series.name, date_time)
+ date_time = series.datecreated.strftime("%Y%m%d-%H%M%S")
+ series.name = "%s-%s-%s" % (
+ series.product.name,
+ series.name,
+ date_time,
+ )
series.status = SeriesStatus.OBSOLETE
series.releasefileglob = None
series.product = getUtility(ILaunchpadCelebrities).obsolete_junk
@@ -268,7 +272,7 @@ class RegistryEditFormView(LaunchpadEditFormView):
next_url = cancel_url
- @action("Change", name='change')
+ @action("Change", name="change")
def change_action(self, action, data):
self.updateContextFromData(data)
@@ -290,16 +294,19 @@ class BaseRdfView(DataDownloadView):
As a side-effect, HTTP headers are set for the mime type
and filename for download."""
unicodedata = self.template()
- encodeddata = unicodedata.encode('utf-8')
+ encodeddata = unicodedata.encode("utf-8")
return encodeddata
class RDFIndexView(LaunchpadView):
"""View for /rdf page."""
+
page_title = label = "Launchpad RDF"
class RDFFolder(ExportedFolder):
"""Export the Launchpad RDF schemas."""
+
folder = os.path.join(
- os.path.dirname(os.path.realpath(__file__)), '../rdfspec/')
+ os.path.dirname(os.path.realpath(__file__)), "../rdfspec/"
+ )
diff --git a/lib/lp/registry/browser/announcement.py b/lib/lp/registry/browser/announcement.py
index 7592db6..ef7957e 100644
--- a/lib/lp/registry/browser/announcement.py
+++ b/lib/lp/registry/browser/announcement.py
@@ -4,31 +4,22 @@
"""Announcement views."""
__all__ = [
- 'AnnouncementAddView',
- 'AnnouncementRetargetView',
- 'AnnouncementPublishView',
- 'AnnouncementRetractView',
- 'AnnouncementDeleteView',
- 'AnnouncementEditView',
- 'AnnouncementSetView',
- 'HasAnnouncementsView',
- 'AnnouncementView',
- ]
-
-from zope.interface import (
- implementer,
- Interface,
- )
-from zope.schema import (
- Choice,
- TextLine,
- )
+ "AnnouncementAddView",
+ "AnnouncementRetargetView",
+ "AnnouncementPublishView",
+ "AnnouncementRetractView",
+ "AnnouncementDeleteView",
+ "AnnouncementEditView",
+ "AnnouncementSetView",
+ "HasAnnouncementsView",
+ "AnnouncementView",
+]
+
+from zope.interface import Interface, implementer
+from zope.schema import Choice, TextLine
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.app.validators.url import valid_webref
from lp.app.widgets.announcementdate import AnnouncementDateWidget
from lp.registry.interfaces.announcement import IAnnouncement
@@ -37,70 +28,63 @@ from lp.services.feeds.browser import (
AnnouncementsFeedLink,
FeedsMixin,
RootAnnouncementsFeedLink,
- )
-from lp.services.fields import (
- AnnouncementDate,
- Summary,
- Title,
- )
+)
+from lp.services.fields import AnnouncementDate, Summary, Title
from lp.services.propertycache import cachedproperty
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,
NavigationMenu,
- )
-from lp.services.webapp.publisher import (
- canonical_url,
- LaunchpadView,
- )
+ enabled_with_permission,
+)
+from lp.services.webapp.publisher import LaunchpadView, canonical_url
class AnnouncementMenuMixin:
"""A mixin of links common to many menus."""
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
- text = 'Modify announcement'
- return Link('+edit', text, icon='edit')
+ text = "Modify announcement"
+ return Link("+edit", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def retarget(self):
- text = 'Move announcement'
- return Link('+retarget', text, icon='edit')
+ text = "Move announcement"
+ return Link("+retarget", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def publish(self):
- text = 'Publish announcement'
+ text = "Publish announcement"
enabled = not self.context.published
- return Link('+publish', text, icon='edit', enabled=enabled)
+ return Link("+publish", text, icon="edit", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def retract(self):
- text = 'Retract announcement'
+ text = "Retract announcement"
enabled = self.context.published
- return Link('+retract', text, icon='remove', enabled=enabled)
+ return Link("+retract", text, icon="remove", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def delete(self):
- text = 'Delete announcement'
- return Link('+delete', text, icon='trash-icon')
+ text = "Delete announcement"
+ return Link("+delete", text, icon="trash-icon")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def announce(self):
- text = 'Make announcement'
- summary = 'Create an item of news for this project'
- return Link('+announce', text, summary, icon='add')
+ text = "Make announcement"
+ summary = "Create an item of news for this project"
+ return Link("+announce", text, summary, icon="add")
class AnnouncementEditNavigationMenu(NavigationMenu, AnnouncementMenuMixin):
"""A sub-menu for different aspects of modifying an announcement."""
usedfor = IAnnouncement
- facet = 'overview'
- title = 'Change announcement'
- links = ['edit', 'retarget', 'publish', 'retract', 'delete']
+ facet = "overview"
+ title = "Change announcement"
+ links = ["edit", "retarget", "publish", "retract", "delete"]
class IAnnouncementCreateMenu(Interface):
@@ -111,9 +95,9 @@ class AnnouncementCreateNavigationMenu(NavigationMenu, AnnouncementMenuMixin):
"""A sub-menu for different aspects of modifying an announcement."""
usedfor = IAnnouncementCreateMenu
- facet = 'overview'
- title = 'Create announcement'
- links = ['announce']
+ facet = "overview"
+ title = "Create announcement"
+ links = ["announce"]
class AnnouncementFormMixin:
@@ -126,17 +110,21 @@ class AnnouncementFormMixin:
@property
def cancel_url(self):
"""The announcements URL."""
- return canonical_url(self.context.target, view_name='+announcements')
+ return canonical_url(self.context.target, view_name="+announcements")
class AddAnnouncementForm(Interface):
"""Form definition for the view which creates new Announcements."""
- title = Title(title=_('Headline'), required=True)
- summary = Summary(title=_('Summary'), required=True)
- url = TextLine(title=_('URL'), required=False, constraint=valid_webref,
- description=_("The web location of your announcement."))
- publication_date = AnnouncementDate(title=_('Date'), required=True)
+ title = Title(title=_("Headline"), required=True)
+ summary = Summary(title=_("Summary"), required=True)
+ url = TextLine(
+ title=_("URL"),
+ required=False,
+ constraint=valid_webref,
+ description=_("The web location of your announcement."),
+ )
+ publication_date = AnnouncementDate(title=_("Date"), required=True)
class AnnouncementAddView(LaunchpadFormView):
@@ -148,15 +136,16 @@ class AnnouncementAddView(LaunchpadFormView):
custom_widget_publication_date = AnnouncementDateWidget
- @action(_('Make announcement'), name='announce')
+ @action(_("Make announcement"), name="announce")
def announce_action(self, action, data):
"""Registers a new announcement."""
self.context.announce(
user=self.user,
- title=data.get('title'),
- summary=data.get('summary'),
- url=data.get('url'),
- publication_date=data.get('publication_date'))
+ title=data.get("title"),
+ summary=data.get("summary"),
+ url=data.get("url"),
+ publication_date=data.get("publication_date"),
+ )
self.next_url = canonical_url(self.context)
@property
@@ -173,23 +162,29 @@ class AnnouncementEditView(AnnouncementFormMixin, LaunchpadFormView):
"""A view which allows you to edit the announcement."""
schema = AddAnnouncementForm
- field_names = ['title', 'summary', 'url', ]
- page_title = 'Modify announcement'
+ field_names = [
+ "title",
+ "summary",
+ "url",
+ ]
+ page_title = "Modify announcement"
@property
def initial_values(self):
return {
- 'title': self.context.title,
- 'summary': self.context.summary,
- 'url': self.context.url,
- }
+ "title": self.context.title,
+ "summary": self.context.summary,
+ "url": self.context.url,
+ }
- @action(_('Modify'), name='modify')
+ @action(_("Modify"), name="modify")
def modify_action(self, action, data):
- self.context.modify(title=data.get('title'),
- summary=data.get('summary'),
- url=data.get('url'))
- self.next_url = canonical_url(self.context.target) + '/+announcements'
+ self.context.modify(
+ title=data.get("title"),
+ summary=data.get("summary"),
+ url=data.get("url"),
+ )
+ self.next_url = canonical_url(self.context.target) + "/+announcements"
class AnnouncementRetargetForm(Interface):
@@ -198,89 +193,95 @@ class AnnouncementRetargetForm(Interface):
target = Choice(
title=_("For"),
description=_("The project where this announcement is being made."),
- required=True, vocabulary='DistributionOrProductOrProjectGroup')
+ required=True,
+ vocabulary="DistributionOrProductOrProjectGroup",
+ )
class AnnouncementRetargetView(AnnouncementFormMixin, LaunchpadFormView):
"""A view to move an annoucement to another project."""
schema = AnnouncementRetargetForm
- field_names = ['target']
- page_title = 'Move announcement'
+ field_names = ["target"]
+ page_title = "Move announcement"
def validate(self, data):
"""Ensure that the person can publish announcement at the new
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
- if not check_permission('launchpad.Edit', target):
- self.setFieldError('target',
+ if not check_permission("launchpad.Edit", target):
+ self.setFieldError(
+ "target",
"You don't have permission to make announcements for "
- "%s. Please check that name and try again." %
- target.displayname)
+ "%s. Please check that name and try again."
+ % target.displayname,
+ )
return
- @action(_('Retarget'), name='retarget')
+ @action(_("Retarget"), name="retarget")
def retarget_action(self, action, data):
- target = data.get('target')
+ target = data.get("target")
self.context.retarget(target)
- self.next_url = canonical_url(self.context.target) + '/+announcements'
+ self.next_url = canonical_url(self.context.target) + "/+announcements"
class AnnouncementPublishView(AnnouncementFormMixin, LaunchpadFormView):
"""A view to publish an annoucement."""
schema = AddAnnouncementForm
- field_names = ['publication_date']
- page_title = 'Publish announcement'
+ field_names = ["publication_date"]
+ page_title = "Publish announcement"
custom_widget_publication_date = AnnouncementDateWidget
- @action(_('Publish'), name='publish')
+ @action(_("Publish"), name="publish")
def publish_action(self, action, data):
- publication_date = data['publication_date']
+ publication_date = data["publication_date"]
self.context.setPublicationDate(publication_date)
- self.next_url = canonical_url(self.context.target) + '/+announcements'
+ self.next_url = canonical_url(self.context.target) + "/+announcements"
class AnnouncementRetractView(AnnouncementFormMixin, LaunchpadFormView):
"""A view to unpublish an announcement."""
schema = IAnnouncement
- page_title = 'Retract announcement'
+ page_title = "Retract announcement"
- @action(_('Retract'), name='retract')
+ @action(_("Retract"), name="retract")
def retract_action(self, action, data):
self.context.retract()
- self.next_url = canonical_url(self.context.target) + '/+announcements'
+ self.next_url = canonical_url(self.context.target) + "/+announcements"
class AnnouncementDeleteView(AnnouncementFormMixin, LaunchpadFormView):
"""A view to delete an annoucement."""
schema = IAnnouncement
- page_title = 'Delete announcement'
+ page_title = "Delete announcement"
- @action(_("Delete"), name="delete", validator='validate_cancel')
+ @action(_("Delete"), name="delete", validator="validate_cancel")
def action_delete(self, action, data):
self.context.destroySelf()
- self.next_url = canonical_url(self.context.target) + '/+announcements'
+ self.next_url = canonical_url(self.context.target) + "/+announcements"
@implementer(IAnnouncementCreateMenu)
class HasAnnouncementsView(LaunchpadView, FeedsMixin):
"""A view class for pillars which have announcements."""
- page_title = 'News and announcements'
+ page_title = "News and announcements"
batch_size = config.launchpad.announcement_batch_size
@cachedproperty
@@ -294,15 +295,19 @@ class HasAnnouncementsView(LaunchpadView, FeedsMixin):
@cachedproperty
def announcements(self):
- published_only = not check_permission('launchpad.Edit', self.context)
+ published_only = not check_permission("launchpad.Edit", self.context)
return self.context.getAnnouncements(
- limit=None, published_only=published_only)
+ limit=None, published_only=published_only
+ )
@cachedproperty
def latest_announcements(self):
- published_only = not check_permission('launchpad.Edit', self.context)
- return list(self.context.getAnnouncements(
- limit=5, published_only=published_only))
+ published_only = not check_permission("launchpad.Edit", self.context)
+ return list(
+ self.context.getAnnouncements(
+ limit=5, published_only=published_only
+ )
+ )
@cachedproperty
def has_announcements(self):
@@ -310,14 +315,15 @@ class HasAnnouncementsView(LaunchpadView, FeedsMixin):
@cachedproperty
def show_announcements(self):
- return (len(self.latest_announcements) > 0
- or check_permission('launchpad.Edit', self.context))
+ return len(self.latest_announcements) > 0 or check_permission(
+ "launchpad.Edit", self.context
+ )
@cachedproperty
def announcement_nav(self):
return BatchNavigator(
- self.announcements, self.request,
- size=self.batch_size)
+ self.announcements, self.request, size=self.batch_size
+ )
class AnnouncementSetView(HasAnnouncementsView):
@@ -326,12 +332,13 @@ class AnnouncementSetView(HasAnnouncementsView):
All other feed links should be disabled on this page by
overriding the feed_types class variable.
"""
+
feed_types = (
AnnouncementsFeedLink,
RootAnnouncementsFeedLink,
- )
+ )
- page_title = 'Announcements from all projects hosted in Launchpad'
+ page_title = "Announcements from all projects hosted in Launchpad"
label = page_title
diff --git a/lib/lp/registry/browser/branding.py b/lib/lp/registry/browser/branding.py
index 58008f5..8c53e1f 100644
--- a/lib/lp/registry/browser/branding.py
+++ b/lib/lp/registry/browser/branding.py
@@ -4,15 +4,12 @@
"""Browser views for items that can be displayed as images."""
__all__ = [
- 'BrandingChangeView',
- ]
+ "BrandingChangeView",
+]
from zope.formlib.widget import CustomWidgetFactory
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadEditFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadEditFormView, action
from lp.app.widgets.image import ImageChangeWidget
from lp.services.webapp import canonical_url
@@ -28,19 +25,24 @@ class BrandingChangeView(LaunchpadEditFormView):
@property
def label(self):
- return ('Change the images used to represent %s in Launchpad'
- % self.context.displayname)
+ return (
+ "Change the images used to represent %s in Launchpad"
+ % self.context.displayname
+ )
page_title = "Change branding"
custom_widget_icon = CustomWidgetFactory(
- ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+ ImageChangeWidget, ImageChangeWidget.EDIT_STYLE
+ )
custom_widget_logo = CustomWidgetFactory(
- ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+ ImageChangeWidget, ImageChangeWidget.EDIT_STYLE
+ )
custom_widget_mugshot = CustomWidgetFactory(
- ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+ ImageChangeWidget, ImageChangeWidget.EDIT_STYLE
+ )
- @action("Change Branding", name='change')
+ @action("Change Branding", name="change")
def change_action(self, action, data):
self.updateContextFromData(data)
diff --git a/lib/lp/registry/browser/codeofconduct.py b/lib/lp/registry/browser/codeofconduct.py
index cda21f9..3c47070 100644
--- a/lib/lp/registry/browser/codeofconduct.py
+++ b/lib/lp/registry/browser/codeofconduct.py
@@ -4,48 +4,45 @@
"""View classes to handle signed Codes of Conduct."""
__all__ = [
- 'AffirmCodeOfConductView',
- 'SignedCodeOfConductSetNavigation',
- 'CodeOfConductSetNavigation',
- 'CodeOfConductOverviewMenu',
- 'CodeOfConductSetOverviewMenu',
- 'SignedCodeOfConductSetOverviewMenu',
- 'SignedCodeOfConductOverviewMenu',
- 'CodeOfConductView',
- 'CodeOfConductDownloadView',
- 'CodeOfConductSetView',
- 'SignedCodeOfConductAddView',
- 'SignedCodeOfConductAckView',
- 'SignedCodeOfConductView',
- 'SignedCodeOfConductAdminView',
- 'SignedCodeOfConductActiveView',
- 'SignedCodeOfConductDeactiveView',
- ]
+ "AffirmCodeOfConductView",
+ "SignedCodeOfConductSetNavigation",
+ "CodeOfConductSetNavigation",
+ "CodeOfConductOverviewMenu",
+ "CodeOfConductSetOverviewMenu",
+ "SignedCodeOfConductSetOverviewMenu",
+ "SignedCodeOfConductOverviewMenu",
+ "CodeOfConductView",
+ "CodeOfConductDownloadView",
+ "CodeOfConductSetView",
+ "SignedCodeOfConductAddView",
+ "SignedCodeOfConductAckView",
+ "SignedCodeOfConductView",
+ "SignedCodeOfConductAdminView",
+ "SignedCodeOfConductActiveView",
+ "SignedCodeOfConductDeactiveView",
+]
from lazr.restful.interface import copy_field
from zope.component import getUtility
from zope.interface import Interface
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.registry.interfaces.codeofconduct import (
ICodeOfConduct,
ICodeOfConductConf,
ICodeOfConductSet,
ISignedCodeOfConduct,
ISignedCodeOfConductSet,
- )
+)
from lp.services.webapp import (
ApplicationMenu,
- canonical_url,
- enabled_with_permission,
GetitemNavigation,
LaunchpadView,
Link,
- )
+ canonical_url,
+ enabled_with_permission,
+)
from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.publisher import DataDownloadView
@@ -63,66 +60,68 @@ class CodeOfConductSetNavigation(GetitemNavigation):
class CodeOfConductOverviewMenu(ApplicationMenu):
usedfor = ICodeOfConduct
- facet = 'overview'
- links = ['sign', 'download']
+ facet = "overview"
+ links = ["sign", "download"]
def sign(self):
- text = 'Sign it'
- if (self.context.current and
- self.user and
- not self.user.is_ubuntu_coc_signer):
+ text = "Sign it"
+ if (
+ self.context.current
+ and self.user
+ and not self.user.is_ubuntu_coc_signer
+ ):
# Then...
enabled = True
else:
enabled = False
- return Link('+sign', text, enabled=enabled, icon='edit')
+ return Link("+sign", text, enabled=enabled, icon="edit")
def download(self):
- text = 'Download this version'
+ text = "Download this version"
is_current = self.context.current
- return Link('+download', text, enabled=is_current, icon='download')
+ return Link("+download", text, enabled=is_current, icon="download")
class CodeOfConductSetOverviewMenu(ApplicationMenu):
usedfor = ICodeOfConductSet
- facet = 'overview'
- links = ['admin']
+ facet = "overview"
+ links = ["admin"]
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def admin(self):
- text = 'Administration console'
- return Link('console', text, icon='edit')
+ text = "Administration console"
+ return Link("console", text, icon="edit")
class SignedCodeOfConductSetOverviewMenu(ApplicationMenu):
usedfor = ISignedCodeOfConductSet
- facet = 'overview'
- links = ['register']
+ facet = "overview"
+ links = ["register"]
def register(self):
text = "Register Someone's Signature"
- return Link('+new', text, icon='add')
+ return Link("+new", text, icon="add")
class SignedCodeOfConductOverviewMenu(ApplicationMenu):
usedfor = ISignedCodeOfConduct
- facet = 'overview'
- links = ['activation', 'adminconsole']
+ facet = "overview"
+ links = ["activation", "adminconsole"]
def activation(self):
if self.context.active:
- text = 'deactivate'
- return Link('+deactivate', text, icon='edit')
+ text = "deactivate"
+ return Link("+deactivate", text, icon="edit")
else:
- text = 'activate'
- return Link('+activate', text, icon='edit')
+ text = "activate"
+ return Link("+activate", text, icon="edit")
def adminconsole(self):
- text = 'Administration console'
- return Link('../', text, icon='info')
+ text = "Administration console"
+ return Link("../", text, icon="info")
class CodeOfConductView(LaunchpadView):
@@ -153,13 +152,13 @@ class CodeOfConductDownloadView(DataDownloadView):
def filename(self):
# Build a fancy filename:
# - Use title with no spaces and append '.txt'
- return self.context.title.replace(' ', '') + '.txt'
+ return self.context.title.replace(" ", "") + ".txt"
class CodeOfConductSetView(LaunchpadView):
"""Simple view class for CoCSet page."""
- page_title = 'Ubuntu Codes of Conduct'
+ page_title = "Ubuntu Codes of Conduct"
class AffirmCodeOfConductView(LaunchpadFormView):
@@ -175,9 +174,11 @@ class AffirmCodeOfConductView(LaunchpadFormView):
affirmed = copy_field(
ISignedCodeOfConduct["affirmed"],
- title=_("I agree to this Code of Conduct"), description="")
+ title=_("I agree to this Code of Conduct"),
+ description="",
+ )
- field_names = ['affirmed']
+ field_names = ["affirmed"]
@property
def page_title(self):
@@ -187,28 +188,30 @@ class AffirmCodeOfConductView(LaunchpadFormView):
def code_of_conduct(self):
return self.context.content
- @action('Affirm', name='affirm')
+ @action("Affirm", name="affirm")
def affirm_action(self, action, data):
- if data.get('affirmed'):
+ if data.get("affirmed"):
signedcocset = getUtility(ISignedCodeOfConductSet)
error_message = signedcocset.affirmAndStore(
- self.user, self.context.content)
+ self.user, self.context.content
+ )
if error_message:
self.addError(error_message)
return
- self.next_url = canonical_url(self.user) + '/+codesofconduct'
+ self.next_url = canonical_url(self.user) + "/+codesofconduct"
class SignedCodeOfConductAddView(LaunchpadFormView):
"""Add a new SignedCodeOfConduct Entry."""
+
schema = ISignedCodeOfConduct
- field_names = ['signedcode']
+ field_names = ["signedcode"]
@property
def page_title(self):
- return 'Sign %s' % self.context.title
+ return "Sign %s" % self.context.title
- @action('Continue', name='continue')
+ @action("Continue", name="continue")
def continue_action(self, action, data):
signedcode = data["signedcode"]
signedcocset = getUtility(ISignedCodeOfConductSet)
@@ -219,7 +222,7 @@ class SignedCodeOfConductAddView(LaunchpadFormView):
if error_message:
self.addError(error_message)
return
- self.next_url = canonical_url(self.user) + '/+codesofconduct'
+ self.next_url = canonical_url(self.user) + "/+codesofconduct"
@property
def current(self):
@@ -231,9 +234,10 @@ class SignedCodeOfConductAddView(LaunchpadFormView):
class SignedCodeOfConductAckView(LaunchpadFormView):
"""Acknowledge a Paper Submitted CoC."""
+
schema = ISignedCodeOfConduct
- field_names = ['owner']
- label = 'Register a code of conduct signature'
+ field_names = ["owner"]
+ label = "Register a code of conduct signature"
page_title = label
@property
@@ -242,11 +246,12 @@ class SignedCodeOfConductAckView(LaunchpadFormView):
cancel_url = next_url
- @action('Register', name='add')
+ @action("Register", name="add")
def createAndAdd(self, action, data):
"""Verify and Add the Acknowledge SignedCoC entry."""
self.context.acknowledgeSignature(
- user=data['owner'], recipient=self.user)
+ user=data["owner"], recipient=self.user
+ )
class SignedCodeOfConductView(CodeOfConductView):
@@ -256,7 +261,7 @@ class SignedCodeOfConductView(CodeOfConductView):
class SignedCodeOfConductAdminView(LaunchpadView):
"""Admin Console for SignedCodeOfConduct Entries."""
- page_title = 'Administer Codes of Conduct'
+ page_title = "Administer Codes of Conduct"
def __init__(self, context, request):
self.context = context
@@ -266,26 +271,30 @@ class SignedCodeOfConductAdminView(LaunchpadView):
def search(self):
"""Search Signed CoC by Owner Displayname"""
- name = self.request.form.get('name')
- searchfor = self.request.form.get('searchfor')
+ name = self.request.form.get("name")
+ searchfor = self.request.form.get("searchfor")
- if (self.request.method != "POST" or
- self.request.form.get("search") != "Search"):
+ if (
+ self.request.method != "POST"
+ or self.request.form.get("search") != "Search"
+ ):
return
# use utility to query on SignedCoCs
sCoC_util = getUtility(ISignedCodeOfConductSet)
self.results = list(
- sCoC_util.searchByDisplayname(name, searchfor=searchfor))
+ sCoC_util.searchByDisplayname(name, searchfor=searchfor)
+ )
return True
class SignedCodeOfConductActiveView(LaunchpadFormView):
"""Active a SignedCodeOfConduct Entry."""
+
schema = ISignedCodeOfConduct
- field_names = ['admincomment']
- label = 'Activate code of conduct signature'
+ field_names = ["admincomment"]
+ label = "Activate code of conduct signature"
page_title = label
state = True
@@ -296,24 +305,28 @@ class SignedCodeOfConductActiveView(LaunchpadFormView):
cancel_url = next_url
def _change(self, action, data):
- admincomment = data['admincomment']
+ admincomment = data["admincomment"]
sCoC_util = getUtility(ISignedCodeOfConductSet)
sCoC_util.modifySignature(
- sign_id=self.context.id, recipient=self.user,
- admincomment=admincomment, state=self.state)
+ sign_id=self.context.id,
+ recipient=self.user,
+ admincomment=admincomment,
+ state=self.state,
+ )
self.request.response.redirect(self.next_url)
- @action('Activate', name='change')
+ @action("Activate", name="change")
def activate(self, action, data):
self._change(action, data)
class SignedCodeOfConductDeactiveView(SignedCodeOfConductActiveView):
"""Deactivate a SignedCodeOfConduct Entry."""
- label = 'Deactivate code of conduct signature'
+
+ label = "Deactivate code of conduct signature"
page_title = label
state = False
- @action('Deactivate', name='change')
+ @action("Deactivate", name="change")
def deactivate(self, action, data):
self._change(action, data)
diff --git a/lib/lp/registry/browser/distribution.py b/lib/lp/registry/browser/distribution.py
index 436783f..7180941 100644
--- a/lib/lp/registry/browser/distribution.py
+++ b/lib/lp/registry/browser/distribution.py
@@ -4,41 +4,41 @@
"""Browser views for distributions."""
__all__ = [
- 'DistributionAddView',
- 'DistributionAdminView',
- 'DistributionArchiveMirrorsRSSView',
- 'DistributionArchiveMirrorsView',
- 'DistributionArchivesView',
- 'DistributionChangeMembersView',
- 'DistributionChangeMirrorAdminView',
- 'DistributionChangeOCIProjectAdminView',
- 'DistributionChangeSecurityAdminView',
- 'DistributionCountryArchiveMirrorsView',
- 'DistributionDisabledMirrorsView',
- 'DistributionEditView',
- 'DistributionFacets',
- 'DistributionNavigation',
- 'DistributionPPASearchView',
- 'DistributionPackageSearchView',
- 'DistributionPendingReviewMirrorsView',
- 'DistributionPublisherConfigView',
- 'DistributionReassignmentView',
- 'DistributionSeriesView',
- 'DistributionDerivativesView',
- 'DistributionSeriesMirrorsRSSView',
- 'DistributionSeriesMirrorsView',
- 'DistributionSetActionNavigationMenu',
- 'DistributionSetBreadcrumb',
- 'DistributionSetContextMenu',
- 'DistributionSetNavigation',
- 'DistributionSetView',
- 'DistributionSpecificationsMenu',
- 'DistributionUnofficialMirrorsView',
- 'DistributionView',
- ]
+ "DistributionAddView",
+ "DistributionAdminView",
+ "DistributionArchiveMirrorsRSSView",
+ "DistributionArchiveMirrorsView",
+ "DistributionArchivesView",
+ "DistributionChangeMembersView",
+ "DistributionChangeMirrorAdminView",
+ "DistributionChangeOCIProjectAdminView",
+ "DistributionChangeSecurityAdminView",
+ "DistributionCountryArchiveMirrorsView",
+ "DistributionDisabledMirrorsView",
+ "DistributionEditView",
+ "DistributionFacets",
+ "DistributionNavigation",
+ "DistributionPPASearchView",
+ "DistributionPackageSearchView",
+ "DistributionPendingReviewMirrorsView",
+ "DistributionPublisherConfigView",
+ "DistributionReassignmentView",
+ "DistributionSeriesView",
+ "DistributionDerivativesView",
+ "DistributionSeriesMirrorsRSSView",
+ "DistributionSeriesMirrorsView",
+ "DistributionSetActionNavigationMenu",
+ "DistributionSetBreadcrumb",
+ "DistributionSetContextMenu",
+ "DistributionSetNavigation",
+ "DistributionSetView",
+ "DistributionSpecificationsMenu",
+ "DistributionUnofficialMirrorsView",
+ "DistributionView",
+]
-from collections import defaultdict
import datetime
+from collections import defaultdict
from lazr.restful.utils import smartquote
from zope.component import getUtility
@@ -55,10 +55,10 @@ from zope.security.interfaces import Unauthorized
from lp.answers.browser.faqtarget import FAQTargetNavigationMixin
from lp.answers.browser.questiontarget import QuestionTargetTraversalMixin
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.browser.lazrjs import InlinePersonEditPickerWidget
from lp.app.browser.tales import format_link
from lp.app.enums import PILLAR_INFORMATION_TYPES
@@ -68,77 +68,71 @@ from lp.app.widgets.image import ImageChangeWidget
from lp.app.widgets.itemswidgets import (
LabeledMultiCheckBoxWidget,
LaunchpadRadioWidgetWithDescription,
- )
+)
from lp.archivepublisher.interfaces.publisherconfig import (
IPublisherConfig,
IPublisherConfigSet,
- )
+)
from lp.blueprints.browser.specificationtarget import (
HasSpecificationsMenuMixin,
- )
+)
from lp.bugs.browser.bugtask import BugTargetTraversalMixin
from lp.bugs.browser.structuralsubscription import (
- expose_structural_subscription_data_to_js,
StructuralSubscriptionMenuMixin,
StructuralSubscriptionTargetTraversalMixin,
- )
+ expose_structural_subscription_data_to_js,
+)
from lp.buildmaster.interfaces.processor import IProcessorSet
from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
-from lp.registry.browser import (
- add_subscribe_link,
- RegistryEditFormView,
- )
+from lp.registry.browser import RegistryEditFormView, add_subscribe_link
from lp.registry.browser.announcement import HasAnnouncementsView
from lp.registry.browser.menu import (
IRegistryCollectionNavigationMenu,
RegistryCollectionActionMenuBase,
- )
+)
from lp.registry.browser.objectreassignment import ObjectReassignmentView
from lp.registry.browser.pillar import (
PillarBugsMenu,
PillarNavigationMixin,
PillarViewMixin,
- )
+)
from lp.registry.browser.widgets.ocicredentialswidget import (
OCICredentialsWidget,
- )
+)
from lp.registry.enums import DistributionDefaultTraversalPolicy
from lp.registry.interfaces.distribution import (
IDistribution,
IDistributionMirrorMenuMarker,
IDistributionSet,
- )
+)
from lp.registry.interfaces.distributionmirror import (
MirrorContent,
MirrorSpeed,
- )
+)
from lp.registry.interfaces.ociproject import (
- IOCIProjectSet,
OCI_PROJECT_ALLOW_CREATE,
- )
+ IOCIProjectSet,
+)
from lp.registry.interfaces.series import SeriesStatus
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.features import getFeatureFlag
from lp.services.feeds.browser import FeedsMixin
-from lp.services.geoip.helpers import (
- ipaddress_from_request,
- request_country,
- )
+from lp.services.geoip.helpers import ipaddress_from_request, request_country
from lp.services.helpers import english_list
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
ApplicationMenu,
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
NavigationMenu,
- redirection,
StandardLaunchpadFacets,
+ canonical_url,
+ enabled_with_permission,
+ redirection,
stepthrough,
- )
+)
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import Breadcrumb
@@ -150,59 +144,70 @@ from lp.soyuz.interfaces.archive import IArchiveSet
class DistributionNavigation(
- Navigation, BugTargetTraversalMixin, QuestionTargetTraversalMixin,
- FAQTargetNavigationMixin, StructuralSubscriptionTargetTraversalMixin,
- PillarNavigationMixin, TargetDefaultVCSNavigationMixin):
+ Navigation,
+ BugTargetTraversalMixin,
+ QuestionTargetTraversalMixin,
+ FAQTargetNavigationMixin,
+ StructuralSubscriptionTargetTraversalMixin,
+ PillarNavigationMixin,
+ TargetDefaultVCSNavigationMixin,
+):
usedfor = IDistribution
- @redirection('+source', status=301)
+ @redirection("+source", status=301)
def redirect_source(self):
return canonical_url(self.context)
- @stepthrough('+mirror')
+ @stepthrough("+mirror")
def traverse_mirrors(self, name):
return self.context.getMirrorByName(name)
- @stepthrough('+source')
+ @stepthrough("+source")
def traverse_sources(self, name):
dsp = self.context.getSourcePackage(name)
policy = self.context.default_traversal_policy
- if (policy == DistributionDefaultTraversalPolicy.SOURCE_PACKAGE and
- not self.context.redirect_default_traversal):
+ if (
+ policy == DistributionDefaultTraversalPolicy.SOURCE_PACKAGE
+ and not self.context.redirect_default_traversal
+ ):
return self.redirectSubTree(
- canonical_url(dsp, request=self.request), status=303)
+ canonical_url(dsp, request=self.request), status=303
+ )
else:
return dsp
- @stepthrough('+oci')
+ @stepthrough("+oci")
def traverse_oci(self, name):
oci_project = self.context.getOCIProject(name)
policy = self.context.default_traversal_policy
- if (policy == DistributionDefaultTraversalPolicy.OCI_PROJECT and
- not self.context.redirect_default_traversal):
+ if (
+ policy == DistributionDefaultTraversalPolicy.OCI_PROJECT
+ and not self.context.redirect_default_traversal
+ ):
return self.redirectSubTree(
- canonical_url(oci_project, request=self.request), status=303)
+ canonical_url(oci_project, request=self.request), status=303
+ )
else:
return oci_project
- @stepthrough('+milestone')
+ @stepthrough("+milestone")
def traverse_milestone(self, name):
return self.context.getMilestone(name)
- @stepthrough('+announcement')
+ @stepthrough("+announcement")
def traverse_announcement(self, name):
return self.context.getAnnouncement(name)
- @stepthrough('+spec')
+ @stepthrough("+spec")
def traverse_spec(self, name):
return self.context.getSpecification(name)
- @stepthrough('+archive')
+ @stepthrough("+archive")
def traverse_archive(self, name):
return self.context.getArchive(name)
- @stepthrough('+commercialsubscription')
+ @stepthrough("+commercialsubscription")
def traverse_commercialsubscription(self, name):
return self.context.commercial_subscription
@@ -213,21 +218,24 @@ class DistributionNavigation(
resolved = self.context.resolveSeriesAlias(name)
return resolved, True
- @stepthrough('+series')
+ @stepthrough("+series")
def traverse_series(self, name):
series, redirect = self._resolveSeries(name)
if not redirect:
policy = self.context.default_traversal_policy
- if (policy == DistributionDefaultTraversalPolicy.SERIES and
- not self.context.redirect_default_traversal):
+ if (
+ policy == DistributionDefaultTraversalPolicy.SERIES
+ and not self.context.redirect_default_traversal
+ ):
redirect = True
if redirect:
return self.redirectSubTree(
- canonical_url(series, request=self.request), status=303)
+ canonical_url(series, request=self.request), status=303
+ )
else:
return series
- @stepthrough('+vulnerability')
+ @stepthrough("+vulnerability")
def traverse_vulnerability(self, id):
try:
id = int(id)
@@ -249,12 +257,14 @@ class DistributionNavigation(
redirect = False
else:
raise AssertionError(
- "Unknown default traversal policy %r" % policy)
+ "Unknown default traversal policy %r" % policy
+ )
if obj is None:
return None
if redirect or self.context.redirect_default_traversal:
return self.redirectSubTree(
- canonical_url(obj, request=self.request), status=303)
+ canonical_url(obj, request=self.request), status=303
+ )
else:
return obj
@@ -278,37 +288,39 @@ class DistributionFacets(StandardLaunchpadFacets):
class DistributionSetBreadcrumb(Breadcrumb):
"""Builds a breadcrumb for an `IDistributionSet`."""
- text = 'Distributions'
+
+ text = "Distributions"
class DistributionSetContextMenu(ContextMenu):
usedfor = IDistributionSet
- links = ['products', 'distributions', 'people', 'meetings']
+ links = ["products", "distributions", "people", "meetings"]
def distributions(self):
- return Link('/distros/', 'View distributions')
+ return Link("/distros/", "View distributions")
def products(self):
- return Link('/projects/', 'View projects')
+ return Link("/projects/", "View projects")
def people(self):
- return Link('/people/', 'View people')
+ return Link("/people/", "View people")
def meetings(self):
- return Link('/sprints/', 'View meetings')
+ return Link("/sprints/", "View meetings")
class DistributionMirrorsNavigationMenu(NavigationMenu):
usedfor = IDistributionMirrorMenuMarker
- facet = 'overview'
- links = ('cdimage_mirrors',
- 'archive_mirrors',
- 'disabled_mirrors',
- 'pending_review_mirrors',
- 'unofficial_mirrors',
- )
+ facet = "overview"
+ links = (
+ "cdimage_mirrors",
+ "archive_mirrors",
+ "disabled_mirrors",
+ "pending_review_mirrors",
+ "unofficial_mirrors",
+ )
@property
def distribution(self):
@@ -319,54 +331,58 @@ class DistributionMirrorsNavigationMenu(NavigationMenu):
return self.context.context
def cdimage_mirrors(self):
- text = 'CD mirrors'
- return Link('+cdmirrors', text, icon='info')
+ text = "CD mirrors"
+ return Link("+cdmirrors", text, icon="info")
def archive_mirrors(self):
- text = 'Archive mirrors'
- return Link('+archivemirrors', text, icon='info')
+ text = "Archive mirrors"
+ return Link("+archivemirrors", text, icon="info")
def newmirror(self):
- text = 'Register mirror'
- return Link('+newmirror', text, icon='add')
+ text = "Register mirror"
+ return Link("+newmirror", text, icon="add")
def _userCanSeeNonPublicMirrorListings(self):
"""Does the user have rights to see non-public mirrors listings?"""
user = getUtility(ILaunchBag).user
- return (self.distribution.supports_mirrors
- and user is not None
- and user.inTeam(self.distribution.mirror_admin))
+ return (
+ self.distribution.supports_mirrors
+ and user is not None
+ and user.inTeam(self.distribution.mirror_admin)
+ )
def disabled_mirrors(self):
- text = 'Disabled mirrors'
+ text = "Disabled mirrors"
enabled = self._userCanSeeNonPublicMirrorListings()
- return Link('+disabledmirrors', text, enabled=enabled, icon='info')
+ return Link("+disabledmirrors", text, enabled=enabled, icon="info")
def pending_review_mirrors(self):
- text = 'Pending-review mirrors'
+ text = "Pending-review mirrors"
enabled = self._userCanSeeNonPublicMirrorListings()
return Link(
- '+pendingreviewmirrors', text, enabled=enabled, icon='info')
+ "+pendingreviewmirrors", text, enabled=enabled, icon="info"
+ )
def unofficial_mirrors(self):
- text = 'Unofficial mirrors'
+ text = "Unofficial mirrors"
enabled = self._userCanSeeNonPublicMirrorListings()
- return Link('+unofficialmirrors', text, enabled=enabled, icon='info')
+ return Link("+unofficialmirrors", text, enabled=enabled, icon="info")
class DistributionLinksMixin(StructuralSubscriptionMenuMixin):
"""A mixin to provide common links to menus."""
- @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")
class DistributionNavigationMenu(NavigationMenu, DistributionLinksMixin):
"""A menu of context actions."""
+
usedfor = IDistribution
- facet = 'overview'
+ facet = "overview"
@enabled_with_permission("launchpad.Admin")
def admin(self):
@@ -378,221 +394,232 @@ class DistributionNavigationMenu(NavigationMenu, DistributionLinksMixin):
text = "Configure publisher"
return Link("+pubconf", text, icon="edit")
- @enabled_with_permission('launchpad.Driver')
+ @enabled_with_permission("launchpad.Driver")
def sharing(self):
- return Link('+sharing', 'Sharing', icon='edit')
+ return Link("+sharing", "Sharing", icon="edit")
def new_oci_project(self):
- text = 'Create an OCI project'
- link = Link('+new-oci-project', text, icon='add')
- link.enabled = (
- bool(getFeatureFlag(OCI_PROJECT_ALLOW_CREATE))
- and self.context.canAdministerOCIProjects(self.user))
+ text = "Create an OCI project"
+ link = Link("+new-oci-project", text, icon="add")
+ link.enabled = bool(
+ getFeatureFlag(OCI_PROJECT_ALLOW_CREATE)
+ ) and self.context.canAdministerOCIProjects(self.user)
return link
def search_oci_project(self):
oci_projects = getUtility(IOCIProjectSet).findByPillarAndName(
- self.context, '')
- text = 'Search for OCI project'
- link = Link('+search-oci-project', text, icon='info')
+ self.context, ""
+ )
+ text = "Search for OCI project"
+ link = Link("+search-oci-project", text, icon="info")
link.enabled = not oci_projects.is_empty()
return link
@cachedproperty
def links(self):
return [
- 'edit', 'admin', 'pubconf', 'subscribe_to_bug_mail',
- 'edit_bug_mail', 'sharing', 'new_oci_project',
- 'search_oci_project']
+ "edit",
+ "admin",
+ "pubconf",
+ "subscribe_to_bug_mail",
+ "edit_bug_mail",
+ "sharing",
+ "new_oci_project",
+ "search_oci_project",
+ ]
class DistributionOverviewMenu(ApplicationMenu, DistributionLinksMixin):
usedfor = IDistribution
- facet = 'overview'
+ facet = "overview"
links = [
- 'edit',
- 'branding',
- 'driver',
- 'search',
- 'members',
- 'mirror_admin',
- 'oci_project_admin',
- 'security_admin',
- 'reassign',
- 'addseries',
- 'series',
- 'derivatives',
- 'milestones',
- 'top_contributors',
- 'builds',
- 'cdimage_mirrors',
- 'archive_mirrors',
- 'pending_review_mirrors',
- 'disabled_mirrors',
- 'unofficial_mirrors',
- 'newmirror',
- 'announce',
- 'announcements',
- 'ppas',
- 'configure_answers',
- 'configure_blueprints',
- 'configure_translations',
- ]
+ "edit",
+ "branding",
+ "driver",
+ "search",
+ "members",
+ "mirror_admin",
+ "oci_project_admin",
+ "security_admin",
+ "reassign",
+ "addseries",
+ "series",
+ "derivatives",
+ "milestones",
+ "top_contributors",
+ "builds",
+ "cdimage_mirrors",
+ "archive_mirrors",
+ "pending_review_mirrors",
+ "disabled_mirrors",
+ "unofficial_mirrors",
+ "newmirror",
+ "announce",
+ "announcements",
+ "ppas",
+ "configure_answers",
+ "configure_blueprints",
+ "configure_translations",
+ ]
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def branding(self):
- text = 'Change branding'
- return Link('+branding', text, icon='edit')
+ text = "Change branding"
+ return Link("+branding", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def driver(self):
- text = 'Appoint driver'
- summary = 'Someone with permission to set goals for all series'
- return Link('+driver', text, summary, icon='edit')
+ text = "Appoint driver"
+ summary = "Someone with permission to set goals for all series"
+ return Link("+driver", text, summary, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def reassign(self):
- text = 'Change maintainer'
- return Link('+reassign', text, icon='edit')
+ text = "Change maintainer"
+ return Link("+reassign", text, icon="edit")
def newmirror(self):
- text = 'Register a new mirror'
+ text = "Register a new mirror"
enabled = self.context.supports_mirrors
- return Link('+newmirror', text, enabled=enabled, icon='add')
+ return Link("+newmirror", text, enabled=enabled, icon="add")
def top_contributors(self):
- text = 'More contributors'
- return Link('+topcontributors', text, icon='info')
+ text = "More contributors"
+ return Link("+topcontributors", text, icon="info")
def cdimage_mirrors(self):
- text = 'CD mirrors'
- return Link('+cdmirrors', text, icon='info')
+ text = "CD mirrors"
+ return Link("+cdmirrors", text, icon="info")
def archive_mirrors(self):
- text = 'Archive mirrors'
- return Link('+archivemirrors', text, icon='info')
+ text = "Archive mirrors"
+ return Link("+archivemirrors", text, icon="info")
def _userCanSeeNonPublicMirrorListings(self):
"""Does the user have rights to see non-public mirrors listings?"""
user = getUtility(ILaunchBag).user
- return (self.context.supports_mirrors
- and user is not None
- and user.inTeam(self.context.mirror_admin))
+ return (
+ self.context.supports_mirrors
+ and user is not None
+ and user.inTeam(self.context.mirror_admin)
+ )
def disabled_mirrors(self):
- text = 'Disabled mirrors'
+ text = "Disabled mirrors"
enabled = self._userCanSeeNonPublicMirrorListings()
- return Link('+disabledmirrors', text, enabled=enabled, icon='info')
+ return Link("+disabledmirrors", text, enabled=enabled, icon="info")
def pending_review_mirrors(self):
- text = 'Pending-review mirrors'
+ text = "Pending-review mirrors"
enabled = self._userCanSeeNonPublicMirrorListings()
return Link(
- '+pendingreviewmirrors', text, enabled=enabled, icon='info')
+ "+pendingreviewmirrors", text, enabled=enabled, icon="info"
+ )
def unofficial_mirrors(self):
- text = 'Unofficial mirrors'
+ text = "Unofficial mirrors"
enabled = self._userCanSeeNonPublicMirrorListings()
- return Link('+unofficialmirrors', text, enabled=enabled, icon='info')
+ return Link("+unofficialmirrors", text, enabled=enabled, icon="info")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def members(self):
- text = 'Change members team'
- return Link('+selectmemberteam', text, icon='edit')
+ text = "Change members team"
+ return Link("+selectmemberteam", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def mirror_admin(self):
- text = 'Change mirror admins'
+ text = "Change mirror admins"
enabled = self.context.supports_mirrors
- return Link('+selectmirroradmins', text, enabled=enabled, icon='edit')
+ return Link("+selectmirroradmins", text, enabled=enabled, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def oci_project_admin(self):
- text = 'Change OCI project admins'
- return Link('+select-oci-project-admins', text, icon='edit')
+ text = "Change OCI project admins"
+ return Link("+select-oci-project-admins", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def security_admin(self):
- text = 'Change security admins'
- return Link('+select-security-admins', text, icon='edit')
+ text = "Change security admins"
+ return Link("+select-security-admins", text, icon="edit")
def search(self):
- text = 'Search packages'
- return Link('+search', text, icon='search')
+ text = "Search packages"
+ return Link("+search", text, icon="search")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def addseries(self):
- text = 'Add series'
- return Link('+addseries', text, icon='add')
+ text = "Add series"
+ return Link("+addseries", text, icon="add")
def series(self):
- text = 'All series'
- return Link('+series', text, icon='info')
+ text = "All series"
+ return Link("+series", text, icon="info")
def derivatives(self):
- text = 'All derivatives'
- return Link('+derivatives', text, icon='info')
+ text = "All derivatives"
+ return Link("+derivatives", text, icon="info")
def milestones(self):
- text = 'All milestones'
- return Link('+milestones', text, icon='info')
+ text = "All milestones"
+ return Link("+milestones", text, icon="info")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def announce(self):
- text = 'Make announcement'
- summary = 'Publish an item of news for this project'
- return Link('+announce', text, summary, icon='add')
+ text = "Make announcement"
+ summary = "Publish an item of news for this project"
+ return Link("+announce", text, summary, icon="add")
def announcements(self):
- text = 'Read all announcements'
+ text = "Read all announcements"
enabled = bool(self.context.getAnnouncements())
- return Link('+announcements', text, icon='info', enabled=enabled)
+ return Link("+announcements", text, icon="info", enabled=enabled)
def builds(self):
- text = 'Builds'
- return Link('+builds', text, icon='info')
+ text = "Builds"
+ return Link("+builds", text, icon="info")
def ppas(self):
- text = 'Personal Package Archives'
- return Link('+ppas', text, icon='info')
+ text = "Personal Package Archives"
+ return Link("+ppas", text, icon="info")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def configure_answers(self):
- text = 'Configure support tracker'
- summary = 'Allow users to ask questions on this project'
- return Link('+edit', text, summary, icon='edit')
+ text = "Configure support tracker"
+ summary = "Allow users to ask questions on this project"
+ return Link("+edit", text, summary, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def configure_blueprints(self):
- text = 'Configure blueprints'
- summary = 'Enable tracking of feature planning.'
- return Link('+edit', text, summary, icon='edit')
+ text = "Configure blueprints"
+ summary = "Enable tracking of feature planning."
+ return Link("+edit", text, summary, icon="edit")
- @enabled_with_permission('launchpad.TranslationsAdmin')
+ @enabled_with_permission("launchpad.TranslationsAdmin")
def configure_translations(self):
- text = 'Configure translations'
- summary = 'Allow users to provide translations for this project.'
- return Link('+configure-translations', text, summary, icon='edit')
+ text = "Configure translations"
+ summary = "Allow users to provide translations for this project."
+ return Link("+configure-translations", text, summary, icon="edit")
class DistributionBugsMenu(PillarBugsMenu):
usedfor = IDistribution
- facet = 'bugs'
+ facet = "bugs"
@property
def links(self):
- links = ['bugsupervisor', 'cve', 'filebug']
+ links = ["bugsupervisor", "cve", "filebug"]
add_subscribe_link(links)
return links
-class DistributionSpecificationsMenu(NavigationMenu,
- HasSpecificationsMenuMixin):
+class DistributionSpecificationsMenu(
+ NavigationMenu, HasSpecificationsMenuMixin
+):
usedfor = IDistribution
- facet = 'specifications'
- links = ['listall', 'doc', 'assignments', 'new', 'register_sprint']
+ facet = "specifications"
+ links = ["listall", "doc", "assignments", "new", "register_sprint"]
class DistributionPackageSearchView(PackageSearchViewBase):
@@ -606,9 +633,9 @@ class DistributionPackageSearchView(PackageSearchViewBase):
# default to searches on binary names, but allow the user to
# select.
if self.context.has_published_binaries:
- self.search_type = self.request.get("search_type", 'binary')
+ self.search_type = self.request.get("search_type", "binary")
else:
- self.search_type = 'source'
+ self.search_type = "source"
def contextSpecificSearch(self):
"""See `AbstractPackageSearchView`."""
@@ -617,7 +644,8 @@ class DistributionPackageSearchView(PackageSearchViewBase):
return self.context.searchBinaryPackages(self.text)
else:
non_exact_matches = self.context.searchSourcePackageCaches(
- self.text)
+ self.text
+ )
# The searchBinaryPackageCaches() method returns tuples, so we
# use the DecoratedResultSet here to just get the
@@ -626,7 +654,8 @@ class DistributionPackageSearchView(PackageSearchViewBase):
return cache_name_tuple[0]
non_exact_matches = DecoratedResultSet(
- non_exact_matches, tuple_to_package_cache)
+ non_exact_matches, tuple_to_package_cache
+ )
return non_exact_matches.config(distinct=True)
@@ -653,13 +682,14 @@ class DistributionPackageSearchView(PackageSearchViewBase):
"""
return "%s/+search?search_type=source&%s" % (
canonical_url(self.context),
- self.request.get('QUERY_STRING'),
- )
+ self.request.get("QUERY_STRING"),
+ )
@cachedproperty
def exact_matches(self):
return self.context.searchBinaryPackages(
- self.text, exact_match=True).order_by('name')
+ self.text, exact_match=True
+ ).order_by("name")
@property
def has_exact_matches(self):
@@ -676,20 +706,20 @@ class DistributionPackageSearchView(PackageSearchViewBase):
for package_cache in self.batchnav.currentBatch():
names[package_cache.name] = self._listFirstFiveMatchingNames(
- self.text, package_cache.binpkgnames)
+ self.text, package_cache.binpkgnames
+ )
return names
def _listFirstFiveMatchingNames(self, match_text, space_separated_list):
"""Returns a comma-separated list of the first five matching items"""
- name_list = space_separated_list.split(' ')
+ name_list = space_separated_list.split(" ")
- matching_names = [
- name for name in name_list if match_text in name]
+ matching_names = [name for name in name_list if match_text in name]
if len(matching_names) > 5:
matching_names = matching_names[:5]
- matching_names.append('...')
+ matching_names.append("...")
return ", ".join(matching_names)
@@ -704,8 +734,9 @@ class DistributionPackageSearchView(PackageSearchViewBase):
# create a list, convert the list to a set and back again:
distroseries_list = [
pubrec.distroseries.name
- for pubrec in package.current_publishing_records
- if pubrec.distroseries.active]
+ for pubrec in package.current_publishing_records
+ if pubrec.distroseries.active
+ ]
distroseries_list = list(set(distroseries_list))
# Yay for alphabetical series names.
@@ -732,92 +763,110 @@ class DistributionView(PillarViewMixin, HasAnnouncementsView, FeedsMixin):
def initialize(self):
super().initialize()
expose_structural_subscription_data_to_js(
- self.context, self.request, self.user)
+ self.context, self.request, self.user
+ )
@property
def page_title(self):
- return '%s in Launchpad' % self.context.displayname
+ return "%s in Launchpad" % self.context.displayname
@property
def maintainer_widget(self):
return InlinePersonEditPickerWidget(
- self.context, IDistribution['owner'],
+ self.context,
+ IDistribution["owner"],
format_link(self.context.owner),
- header='Change maintainer', edit_view='+reassign',
- step_title='Select a new maintainer', show_create_team=True)
+ header="Change maintainer",
+ edit_view="+reassign",
+ step_title="Select a new maintainer",
+ show_create_team=True,
+ )
@property
def driver_widget(self):
- if canWrite(self.context, 'driver'):
- empty_value = 'Specify a driver'
+ if canWrite(self.context, "driver"):
+ empty_value = "Specify a driver"
else:
- empty_value = 'None'
+ empty_value = "None"
return InlinePersonEditPickerWidget(
- self.context, IDistribution['driver'],
+ self.context,
+ IDistribution["driver"],
format_link(self.context.driver, empty_value=empty_value),
- header='Change driver', edit_view='+driver',
+ header="Change driver",
+ edit_view="+driver",
null_display_value=empty_value,
- step_title='Select a new driver', show_create_team=True)
+ step_title="Select a new driver",
+ show_create_team=True,
+ )
@property
def members_widget(self):
- if canWrite(self.context, 'members'):
- empty_value = ' Specify the members team'
+ if canWrite(self.context, "members"):
+ empty_value = " Specify the members team"
else:
- empty_value = 'None'
+ empty_value = "None"
return InlinePersonEditPickerWidget(
- self.context, IDistribution['members'],
+ self.context,
+ IDistribution["members"],
format_link(self.context.members, empty_value=empty_value),
- header='Change the members team', edit_view='+selectmemberteam',
+ header="Change the members team",
+ edit_view="+selectmemberteam",
null_display_value=empty_value,
- step_title='Select a new members team')
+ step_title="Select a new members team",
+ )
@property
def mirror_admin_widget(self):
- if canWrite(self.context, 'mirror_admin'):
- empty_value = ' Specify a mirror administrator'
+ if canWrite(self.context, "mirror_admin"):
+ empty_value = " Specify a mirror administrator"
else:
- empty_value = 'None'
+ empty_value = "None"
return InlinePersonEditPickerWidget(
- self.context, IDistribution['mirror_admin'],
+ self.context,
+ IDistribution["mirror_admin"],
format_link(self.context.mirror_admin, empty_value=empty_value),
- header='Change the mirror administrator',
- edit_view='+selectmirroradmins', null_display_value=empty_value,
- step_title='Select a new mirror administrator')
+ header="Change the mirror administrator",
+ edit_view="+selectmirroradmins",
+ null_display_value=empty_value,
+ step_title="Select a new mirror administrator",
+ )
@property
def oci_project_admin_widget(self):
- if canWrite(self.context, 'oci_project_admin'):
- empty_value = ' Specify an OCI project administrator'
+ if canWrite(self.context, "oci_project_admin"):
+ empty_value = " Specify an OCI project administrator"
else:
- empty_value = 'None'
+ empty_value = "None"
return InlinePersonEditPickerWidget(
- self.context, IDistribution['oci_project_admin'],
+ self.context,
+ IDistribution["oci_project_admin"],
format_link(
- self.context.oci_project_admin, empty_value=empty_value),
- header='Change the OCI project administrator',
- edit_view='+select-oci-project-admins',
+ self.context.oci_project_admin, empty_value=empty_value
+ ),
+ header="Change the OCI project administrator",
+ edit_view="+select-oci-project-admins",
null_display_value=empty_value,
- step_title='Select a new OCI project administrator')
+ step_title="Select a new OCI project administrator",
+ )
@property
def security_admin_widget(self):
- if canWrite(self.context, 'security_admin'):
- empty_value = ' Specify a security administrator'
+ if canWrite(self.context, "security_admin"):
+ empty_value = " Specify a security administrator"
else:
- empty_value = 'None'
+ empty_value = "None"
return InlinePersonEditPickerWidget(
self.context,
- IDistribution['security_admin'],
+ IDistribution["security_admin"],
format_link(
self.context.security_admin,
empty_value=empty_value,
),
- header='Change the security administrator',
- edit_view='+select-security-admins',
+ header="Change the security administrator",
+ edit_view="+select-security-admins",
null_display_value=empty_value,
- step_title='Select a new security administrator'
+ step_title="Select a new security administrator",
)
def linkedMilestonesForSeries(self, series):
@@ -830,8 +879,9 @@ class DistributionView(PillarViewMixin, HasAnnouncementsView, FeedsMixin):
linked_milestones = []
for milestone in milestones:
linked_milestones.append(
- "<a href=%s>%s</a>" % (
- canonical_url(milestone), milestone.name))
+ "<a href=%s>%s</a>"
+ % (canonical_url(milestone), milestone.name)
+ )
return english_list(linked_milestones)
@@ -849,15 +899,15 @@ class DistributionView(PillarViewMixin, HasAnnouncementsView, FeedsMixin):
first two are allowed via the Launchpad.Edit permission. The latter
is allowed via Launchpad.Commercial.
"""
- return (check_permission('launchpad.Edit', self.context) or
- check_permission('launchpad.Commercial', self.context))
+ return check_permission(
+ "launchpad.Edit", self.context
+ ) or check_permission("launchpad.Commercial", self.context)
class DistributionArchivesView(LaunchpadView):
-
@property
def page_title(self):
- return '%s Copy Archives' % self.context.title
+ return "%s Copy Archives" % self.context.title
@property
def batchnav(self):
@@ -871,9 +921,12 @@ class DistributionArchivesView(LaunchpadView):
The context may be an IDistroSeries or a users archives.
"""
results = getUtility(IArchiveSet).getArchivesForDistribution(
- self.context, purposes=[ArchivePurpose.COPY], user=self.user,
- exclude_disabled=False)
- return results.order_by('date_created DESC')
+ self.context,
+ purposes=[ArchivePurpose.COPY],
+ user=self.user,
+ exclude_disabled=False,
+ )
+ return results.order_by("date_created DESC")
class DistributionPPASearchView(LaunchpadView):
@@ -882,7 +935,7 @@ class DistributionPPASearchView(LaunchpadView):
page_title = "Personal Package Archives"
def initialize(self):
- self.name_filter = self.request.get('name_filter')
+ self.name_filter = self.request.get("name_filter")
if isinstance(self.name_filter, list):
# This happens if someone hand-hacks the URL so that it has
# more than one name_filter field. We could do something
@@ -890,11 +943,11 @@ class DistributionPPASearchView(LaunchpadView):
# but we can acutally do better and join the terms supplied
# instead.
self.name_filter = " ".join(self.name_filter)
- self.show_inactive = self.request.get('show_inactive')
+ self.show_inactive = self.request.get("show_inactive")
@property
def label(self):
- return 'Personal Package Archives for %s' % self.context.title
+ return "Personal Package Archives for %s" % self.context.title
@property
def search_results(self):
@@ -905,11 +958,11 @@ class DistributionPPASearchView(LaunchpadView):
# Preserve self.show_inactive state because it's used in the
# template and build a boolean field to be passed for
# searchPPAs.
- show_inactive = (self.show_inactive == 'on')
+ show_inactive = self.show_inactive == "on"
ppas = self.context.searchPPAs(
- text=self.name_filter, show_inactive=show_inactive,
- user=self.user)
+ text=self.name_filter, show_inactive=show_inactive, user=self.user
+ )
self.batchnav = BatchNavigator(ppas, self.request)
return self.batchnav.currentBatch()
@@ -923,14 +976,16 @@ class DistributionPPASearchView(LaunchpadView):
"""Return the last 5 sources publication in the context PPAs."""
archive_set = getUtility(IArchiveSet)
return archive_set.getLatestPPASourcePublicationsForDistribution(
- distribution=self.context)
+ distribution=self.context
+ )
@property
def most_active_ppas(self):
"""Return the last 5 most active PPAs."""
archive_set = getUtility(IArchiveSet)
return archive_set.getMostActivePPAsForDistribution(
- distribution=self.context)
+ distribution=self.context
+ )
class DistributionSetActionNavigationMenu(RegistryCollectionActionMenuBase):
@@ -938,15 +993,18 @@ class DistributionSetActionNavigationMenu(RegistryCollectionActionMenuBase):
usedfor = IDistributionSet
links = [
- 'register_team', 'register_project', 'register_distribution',
- 'create_account']
+ "register_team",
+ "register_project",
+ "register_distribution",
+ "create_account",
+ ]
@implementer(IRegistryCollectionNavigationMenu)
class DistributionSetView(LaunchpadView):
"""View for /distros top level collection."""
- page_title = 'Distributions registered in Launchpad'
+ page_title = "Distributions registered in Launchpad"
@cachedproperty
def count(self):
@@ -959,20 +1017,24 @@ class RequireVirtualizedBuildersMixin:
def createRequireVirtualized(self):
return form.Fields(
Bool(
- __name__='require_virtualized',
+ __name__="require_virtualized",
title="Require virtualized builders",
description=(
"Only build the distribution's packages on virtual "
- "builders."),
- required=True))
+ "builders."
+ ),
+ required=True,
+ )
+ )
def updateRequireVirtualized(self, require_virtualized, archive):
if archive.require_virtualized != require_virtualized:
archive.require_virtualized = require_virtualized
-class DistributionAddView(LaunchpadFormView, RequireVirtualizedBuildersMixin,
- EnableProcessorsMixin):
+class DistributionAddView(
+ LaunchpadFormView, RequireVirtualizedBuildersMixin, EnableProcessorsMixin
+):
schema = IDistribution
label = "Register a new distribution"
@@ -987,7 +1049,7 @@ class DistributionAddView(LaunchpadFormView, RequireVirtualizedBuildersMixin,
"blueprints_usage",
"translations_usage",
"answers_usage",
- ]
+ ]
custom_widget_require_virtualized = CheckBoxWidget
custom_widget_processors = LabeledMultiCheckBoxWidget
@@ -999,9 +1061,9 @@ class DistributionAddView(LaunchpadFormView, RequireVirtualizedBuildersMixin,
@property
def initial_values(self):
return {
- 'processors': getUtility(IProcessorSet).getAll(),
- 'require_virtualized': False,
- }
+ "processors": getUtility(IProcessorSet).getAll(),
+ "require_virtualized": False,
+ }
@property
def cancel_url(self):
@@ -1015,62 +1077,69 @@ class DistributionAddView(LaunchpadFormView, RequireVirtualizedBuildersMixin,
self.form_fields += self.createEnabledProcessors(
getUtility(IProcessorSet).getAll(),
"The architectures on which the distribution's main archive can "
- "build.")
+ "build.",
+ )
- @action("Save", name='save')
+ @action("Save", name="save")
def save_action(self, action, data):
distribution = getUtility(IDistributionSet).new(
- name=data['name'],
- display_name=data['display_name'],
- title=data['display_name'],
- summary=data['summary'],
- description=data['description'],
- domainname=data['domainname'],
- members=data['members'],
+ name=data["name"],
+ display_name=data["display_name"],
+ title=data["display_name"],
+ summary=data["summary"],
+ description=data["description"],
+ domainname=data["domainname"],
+ members=data["members"],
owner=self.user,
registrant=self.user,
- )
+ )
archive = distribution.main_archive
- self.updateRequireVirtualized(data['require_virtualized'], archive)
+ self.updateRequireVirtualized(data["require_virtualized"], archive)
archive.setProcessors(
- data['processors'], check_permissions=True, user=self.user)
+ data["processors"], check_permissions=True, user=self.user
+ )
notify(ObjectCreatedEvent(distribution))
self.next_url = canonical_url(distribution)
-class DistributionEditView(RegistryEditFormView,
- RequireVirtualizedBuildersMixin,
- EnableProcessorsMixin):
+class DistributionEditView(
+ RegistryEditFormView,
+ RequireVirtualizedBuildersMixin,
+ EnableProcessorsMixin,
+):
schema = IDistribution
field_names = [
- 'display_name',
- 'summary',
- 'description',
- 'bug_reporting_guidelines',
- 'bug_reported_acknowledgement',
- 'package_derivatives_email',
- 'icon',
- 'logo',
- 'mugshot',
- 'official_malone',
- 'enable_bug_expiration',
- 'blueprints_usage',
- 'translations_usage',
- 'answers_usage',
- 'translation_focus',
- 'default_traversal_policy',
- 'redirect_default_traversal',
- 'oci_registry_credentials',
- ]
+ "display_name",
+ "summary",
+ "description",
+ "bug_reporting_guidelines",
+ "bug_reported_acknowledgement",
+ "package_derivatives_email",
+ "icon",
+ "logo",
+ "mugshot",
+ "official_malone",
+ "enable_bug_expiration",
+ "blueprints_usage",
+ "translations_usage",
+ "answers_usage",
+ "translation_focus",
+ "default_traversal_policy",
+ "redirect_default_traversal",
+ "oci_registry_credentials",
+ ]
custom_widget_icon = CustomWidgetFactory(
- ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+ ImageChangeWidget, ImageChangeWidget.EDIT_STYLE
+ )
custom_widget_logo = CustomWidgetFactory(
- ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+ ImageChangeWidget, ImageChangeWidget.EDIT_STYLE
+ )
custom_widget_mugshot = CustomWidgetFactory(
- ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+ ImageChangeWidget, ImageChangeWidget.EDIT_STYLE
+ )
custom_widget_require_virtualized = CheckBoxWidget
custom_widget_processors = LabeledMultiCheckBoxWidget
custom_widget_oci_registry_credentials = OCICredentialsWidget
@@ -1078,7 +1147,7 @@ class DistributionEditView(RegistryEditFormView,
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Change %s details' % self.context.displayname
+ return "Change %s details" % self.context.displayname
def setUpFields(self):
"""See `LaunchpadFormView`."""
@@ -1087,52 +1156,56 @@ class DistributionEditView(RegistryEditFormView,
self.form_fields += self.createEnabledProcessors(
getUtility(IProcessorSet).getAll(),
"The architectures on which the distribution's main archive can "
- "build.")
+ "build.",
+ )
@property
def initial_values(self):
+ main_archive = self.context.main_archive
return {
- 'require_virtualized':
- self.context.main_archive.require_virtualized,
- 'processors': self.context.main_archive.processors
- }
+ "require_virtualized": main_archive.require_virtualized,
+ "processors": main_archive.processors,
+ }
def validate(self, data):
"""Constrain bug expiration to Launchpad Bugs tracker."""
# enable_bug_expiration is disabled by JavaScript when official_malone
# is set False. The contraint is enforced here in case the JavaScript
# fails to load or activate.
- official_malone = data.get('official_malone', False)
+ official_malone = data.get("official_malone", False)
if not official_malone:
- data['enable_bug_expiration'] = False
- if 'processors' in data:
- widget = self.widgets['processors']
+ data["enable_bug_expiration"] = False
+ if "processors" in data:
+ widget = self.widgets["processors"]
for processor in self.context.main_archive.processors:
- if processor not in data['processors']:
+ if processor not in data["processors"]:
if processor.name in widget.disabled_items:
# This processor is restricted and currently
# enabled. Leave it untouched.
- data['processors'].append(processor)
+ data["processors"].append(processor)
def change_archive_fields(self, data):
# Update context.main_archive.
- new_require_virtualized = data.get('require_virtualized')
+ new_require_virtualized = data.get("require_virtualized")
if new_require_virtualized is not None:
self.updateRequireVirtualized(
- new_require_virtualized, self.context.main_archive)
- del(data['require_virtualized'])
- new_processors = data.get('processors')
+ new_require_virtualized, self.context.main_archive
+ )
+ del data["require_virtualized"]
+ new_processors = data.get("processors")
if new_processors is not None:
- if (set(self.context.main_archive.processors) !=
- set(new_processors)):
+ if set(self.context.main_archive.processors) != set(
+ new_processors
+ ):
self.context.main_archive.setProcessors(
- new_processors, check_permissions=True, user=self.user)
- del(data['processors'])
+ new_processors, check_permissions=True, user=self.user
+ )
+ del data["processors"]
- @action("Change", name='change')
+ @action("Change", name="change")
def change_action(self, action, data):
self.change_archive_fields(data)
- new_credentials = data.pop('oci_registry_credentials', None)
+ new_credentials = data.pop("oci_registry_credentials", None)
old_credentials = self.context.oci_registry_credentials
if self.context.oci_registry_credentials != new_credentials:
# Remove the old credentials as we're assigning new ones
@@ -1147,38 +1220,40 @@ class DistributionAdminView(LaunchpadEditFormView):
schema = IDistribution
field_names = [
- 'official_packages',
- 'supports_ppas',
- 'supports_mirrors',
- 'default_traversal_policy',
- 'redirect_default_traversal',
- 'information_type',
- ]
+ "official_packages",
+ "supports_ppas",
+ "supports_mirrors",
+ "default_traversal_policy",
+ "redirect_default_traversal",
+ "information_type",
+ ]
custom_widget_information_type = CustomWidgetFactory(
LaunchpadRadioWidgetWithDescription,
- vocabulary=InformationTypeVocabulary(types=PILLAR_INFORMATION_TYPES))
+ vocabulary=InformationTypeVocabulary(types=PILLAR_INFORMATION_TYPES),
+ )
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Administer %s' % self.context.displayname
+ return "Administer %s" % self.context.displayname
def validate(self, data):
super().validate(data)
- information_type = data.get('information_type')
+ information_type = data.get("information_type")
if information_type:
errors = [
- str(e) for e in self.context.checkInformationType(
- information_type)]
+ str(e)
+ for e in self.context.checkInformationType(information_type)
+ ]
if len(errors) > 0:
- self.setFieldError('information_type', ' '.join(errors))
+ self.setFieldError("information_type", " ".join(errors))
@property
def cancel_url(self):
return canonical_url(self.context)
- @action("Change", name='change')
+ @action("Change", name="change")
def change_action(self, action, data):
self.updateContextFromData(data)
self.next_url = canonical_url(self.context)
@@ -1186,31 +1261,35 @@ class DistributionAdminView(LaunchpadEditFormView):
class DistributionSeriesBaseView(LaunchpadView):
"""A base view to list distroseries."""
+
@cachedproperty
def styled_series(self):
"""A list of dicts; keys: series, css_class, is_development_focus"""
all_series = []
for series in self._displayed_series:
- all_series.append({
- 'series': series,
- 'css_class': self.getCssClass(series),
- })
+ all_series.append(
+ {
+ "series": series,
+ "css_class": self.getCssClass(series),
+ }
+ )
return all_series
def getCssClass(self, series):
"""The highlight, lowlight, or normal CSS class."""
if series.status == SeriesStatus.DEVELOPMENT:
- return 'highlight'
+ return "highlight"
elif series.status == SeriesStatus.OBSOLETE:
- return 'lowlight'
+ return "lowlight"
else:
# This is normal presentation.
- return ''
+ return ""
class DistributionSeriesView(DistributionSeriesBaseView):
"""A view to list the distribution series."""
- label = 'Timeline'
+
+ label = "Timeline"
show_add_series_link = True
show_milestones_link = True
@@ -1221,7 +1300,8 @@ class DistributionSeriesView(DistributionSeriesBaseView):
class DistributionDerivativesView(DistributionSeriesBaseView):
"""A view to list the distribution derivatives."""
- label = 'Derivatives'
+
+ label = "Derivatives"
show_add_series_link = False
show_milestones_link = False
@@ -1232,8 +1312,9 @@ class DistributionDerivativesView(DistributionSeriesBaseView):
class DistributionChangeMirrorAdminView(RegistryEditFormView):
"""A view to change the mirror administrator."""
+
schema = IDistribution
- field_names = ['mirror_admin']
+ field_names = ["mirror_admin"]
@property
def label(self):
@@ -1243,20 +1324,23 @@ class DistributionChangeMirrorAdminView(RegistryEditFormView):
class DistributionChangeOCIProjectAdminView(RegistryEditFormView):
"""A view to change the OCI project administrator."""
+
schema = IDistribution
- field_names = ['oci_project_admin']
+ field_names = ["oci_project_admin"]
@property
def label(self):
"""See `LaunchpadFormView`."""
return "Change the %s OCI project administrator" % (
- self.context.displayname)
+ self.context.displayname
+ )
class DistributionChangeSecurityAdminView(RegistryEditFormView):
"""A view to change the security administrator."""
+
schema = IDistribution
- field_names = ['security_admin']
+ field_names = ["security_admin"]
@property
def label(self):
@@ -1268,8 +1352,9 @@ class DistributionChangeSecurityAdminView(RegistryEditFormView):
class DistributionChangeMembersView(RegistryEditFormView):
"""A view to change the members team."""
+
schema = IDistribution
- field_names = ['members']
+ field_names = ["members"]
@property
def label(self):
@@ -1289,27 +1374,30 @@ class DistributionCountryArchiveMirrorsView(LaunchpadView):
request = self.request
if not self.context.supports_mirrors:
request.response.setStatus(404)
- return ''
+ return ""
ip_address = ipaddress_from_request(request)
country = request_country(request)
mirrors = self.context.getBestMirrorsForCountry(
- country, MirrorContent.ARCHIVE)
+ country, MirrorContent.ARCHIVE
+ )
body = "\n".join(mirror.base_url for mirror in mirrors)
- request.response.setHeader('content-type', 'text/plain;charset=utf-8')
+ request.response.setHeader("content-type", "text/plain;charset=utf-8")
if country is None:
- country_name = 'Unknown'
+ country_name = "Unknown"
else:
country_name = country.name
- request.response.setHeader('X-Generated-For-Country', country_name)
- request.response.setHeader('X-Generated-For-IP', ip_address)
+ request.response.setHeader("X-Generated-For-Country", country_name)
+ request.response.setHeader("X-Generated-For-IP", ip_address)
# XXX: Guilherme Salgado 2008-01-09 bug=173729: These are here only
# for debugging.
request.response.setHeader(
- 'X-REQUEST-HTTP_X_FORWARDED_FOR',
- request.get('HTTP_X_FORWARDED_FOR'))
+ "X-REQUEST-HTTP_X_FORWARDED_FOR",
+ request.get("HTTP_X_FORWARDED_FOR"),
+ )
request.response.setHeader(
- 'X-REQUEST-REMOTE_ADDR', request.get('REMOTE_ADDR'))
- return body.encode('utf-8')
+ "X-REQUEST-REMOTE_ADDR", request.get("REMOTE_ADDR")
+ )
+ return body.encode("utf-8")
@implementer(IDistributionMirrorMenuMarker)
@@ -1317,7 +1405,7 @@ class DistributionMirrorsView(LaunchpadView):
show_freshness = True
show_mirror_type = False
description = None
- page_title = 'Mirrors'
+ page_title = "Mirrors"
@cachedproperty
def mirror_count(self):
@@ -1363,13 +1451,13 @@ class DistributionMirrorsView(LaunchpadView):
else:
# need to be made aware of new values in
# interfaces/distributionmirror.py MirrorSpeed
- return 'Indeterminate'
+ return "Indeterminate"
if throughput < 1000:
- return str(throughput) + ' Kbps'
+ return str(throughput) + " Kbps"
elif throughput < 1000000:
- return str(throughput // 1000) + ' Mbps'
+ return str(throughput // 1000) + " Mbps"
else:
- return str(throughput // 1000000) + ' Gbps'
+ return str(throughput // 1000000) + " Gbps"
@cachedproperty
def total_throughput(self):
@@ -1385,18 +1473,24 @@ class DistributionMirrorsView(LaunchpadView):
for mirror in self.mirrors:
mirrors_by_country[mirror.country.name].append(mirror)
- return [dict(country=country,
- mirrors=mirrors,
- number=len(mirrors),
- throughput=self._sum_throughput(mirrors))
- for country, mirrors in sorted(mirrors_by_country.items())]
+ return [
+ dict(
+ country=country,
+ mirrors=mirrors,
+ number=len(mirrors),
+ throughput=self._sum_throughput(mirrors),
+ )
+ for country, mirrors in sorted(mirrors_by_country.items())
+ ]
class DistributionArchiveMirrorsView(DistributionMirrorsView):
- heading = 'Official Archive Mirrors'
- description = ('These mirrors provide repositories and archives of all '
- 'software for the distribution.')
+ heading = "Official Archive Mirrors"
+ description = (
+ "These mirrors provide repositories and archives of all "
+ "software for the distribution."
+ )
@cachedproperty
def mirrors(self):
@@ -1405,9 +1499,11 @@ class DistributionArchiveMirrorsView(DistributionMirrorsView):
class DistributionSeriesMirrorsView(DistributionMirrorsView):
- heading = 'Official CD Mirrors'
- description = ('These mirrors offer ISO images which you can download '
- 'and burn to CD to make installation disks.')
+ heading = "Official CD Mirrors"
+ description = (
+ "These mirrors offer ISO images which you can download "
+ "and burn to CD to make installation disks."
+ )
show_freshness = False
@cachedproperty
@@ -1423,15 +1519,16 @@ class DistributionMirrorsRSSBaseView(LaunchpadView):
def render(self):
self.request.response.setHeader(
- 'content-type', 'text/xml;charset=utf-8')
+ "content-type", "text/xml;charset=utf-8"
+ )
body = LaunchpadView.render(self)
- return body.encode('utf-8')
+ return body.encode("utf-8")
class DistributionArchiveMirrorsRSSView(DistributionMirrorsRSSBaseView):
"""The RSS feed for archive mirrors."""
- heading = 'Archive Mirrors'
+ heading = "Archive Mirrors"
@cachedproperty
def mirrors(self):
@@ -1441,7 +1538,7 @@ class DistributionArchiveMirrorsRSSView(DistributionMirrorsRSSBaseView):
class DistributionSeriesMirrorsRSSView(DistributionMirrorsRSSBaseView):
"""The RSS feed for series mirrors."""
- heading = 'CD Mirrors'
+ heading = "CD Mirrors"
@cachedproperty
def mirrors(self):
@@ -1449,7 +1546,6 @@ class DistributionSeriesMirrorsRSSView(DistributionMirrorsRSSBaseView):
class DistributionMirrorsAdminView(DistributionMirrorsView):
-
def initialize(self):
"""Raise an Unauthorized exception if the user is not a member of this
distribution's mirror_admin team.
@@ -1460,12 +1556,12 @@ class DistributionMirrorsAdminView(DistributionMirrorsView):
# that permission on a Distribution would be able to see them. That's
# why we have to do the permission check here.
if not (self.user and self.user.inTeam(self.context.mirror_admin)):
- raise Unauthorized('Forbidden')
+ raise Unauthorized("Forbidden")
class DistributionUnofficialMirrorsView(DistributionMirrorsAdminView):
- heading = 'Unofficial Mirrors'
+ heading = "Unofficial Mirrors"
@cachedproperty
def mirrors(self):
@@ -1474,7 +1570,7 @@ class DistributionUnofficialMirrorsView(DistributionMirrorsAdminView):
class DistributionPendingReviewMirrorsView(DistributionMirrorsAdminView):
- heading = 'Pending-review mirrors'
+ heading = "Pending-review mirrors"
show_mirror_type = True
show_freshness = False
@@ -1485,7 +1581,7 @@ class DistributionPendingReviewMirrorsView(DistributionMirrorsAdminView):
class DistributionDisabledMirrorsView(DistributionMirrorsAdminView):
- heading = 'Disabled Mirrors'
+ heading = "Disabled Mirrors"
@cachedproperty
def mirrors(self):
@@ -1494,7 +1590,8 @@ class DistributionDisabledMirrorsView(DistributionMirrorsAdminView):
class DistributionReassignmentView(ObjectReassignmentView):
"""View class for changing distribution maintainer."""
- ownerOrMaintainerName = 'maintainer'
+
+ ownerOrMaintainerName = "maintainer"
class DistributionPublisherConfigView(LaunchpadFormView):
@@ -1502,13 +1599,14 @@ class DistributionPublisherConfigView(LaunchpadFormView):
It redirects to the main distroseries page after a successful edit.
"""
+
schema = IPublisherConfig
- field_names = ['root_dir', 'base_url', 'copy_base_url']
+ field_names = ["root_dir", "base_url", "copy_base_url"]
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Publisher configuration for %s' % self.context.title
+ return "Publisher configuration for %s" % self.context.title
@property
def page_title(self):
@@ -1523,8 +1621,9 @@ class DistributionPublisherConfigView(LaunchpadFormView):
@property
def initial_values(self):
"""If the config already exists, set up the fields with data."""
- config = getUtility(
- IPublisherConfigSet).getByDistribution(self.context)
+ config = getUtility(IPublisherConfigSet).getByDistribution(
+ self.context
+ )
values = {}
if config is not None:
for name in self.field_names:
@@ -1536,16 +1635,19 @@ class DistributionPublisherConfigView(LaunchpadFormView):
def save_action(self, action, data):
"""Update the context and redirect to its overview page."""
config = getUtility(IPublisherConfigSet).getByDistribution(
- self.context)
+ self.context
+ )
if config is None:
config = getUtility(IPublisherConfigSet).new(
distribution=self.context,
- root_dir=data['root_dir'],
- base_url=data['base_url'],
- copy_base_url=data['copy_base_url'])
+ root_dir=data["root_dir"],
+ base_url=data["base_url"],
+ copy_base_url=data["copy_base_url"],
+ )
else:
form.applyChanges(config, self.form_fields, data, self.adapters)
self.request.response.addInfoNotification(
- 'Your changes have been applied.')
+ "Your changes have been applied."
+ )
self.next_url = canonical_url(self.context)
diff --git a/lib/lp/registry/browser/distributionmirror.py b/lib/lp/registry/browser/distributionmirror.py
index 6b77341..75aa827 100644
--- a/lib/lp/registry/browser/distributionmirror.py
+++ b/lib/lp/registry/browser/distributionmirror.py
@@ -2,16 +2,16 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'DistributionMirrorEditView',
- 'DistributionMirrorOverviewMenu',
- 'DistributionMirrorAddView',
- 'DistributionMirrorView',
- 'DistributionMirrorReviewView',
- 'DistributionMirrorReassignmentView',
- 'DistributionMirrorDeleteView',
- 'DistributionMirrorProberLogView',
- 'DistributionMirrorBreadcrumb',
- ]
+ "DistributionMirrorEditView",
+ "DistributionMirrorOverviewMenu",
+ "DistributionMirrorAddView",
+ "DistributionMirrorView",
+ "DistributionMirrorReviewView",
+ "DistributionMirrorReassignmentView",
+ "DistributionMirrorDeleteView",
+ "DistributionMirrorProberLogView",
+ "DistributionMirrorBreadcrumb",
+]
from datetime import datetime
@@ -22,10 +22,10 @@ from zope.lifecycleevent import ObjectCreatedEvent
from lp import _
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.archivepublisher.debversion import Version
from lp.registry.browser.objectreassignment import ObjectReassignmentView
from lp.registry.errors import InvalidMirrorReviewState
@@ -33,61 +33,61 @@ from lp.registry.interfaces.distribution import IDistributionMirrorMenuMarker
from lp.registry.interfaces.distributionmirror import (
IDistributionMirror,
MirrorStatus,
- )
+)
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
- enabled_with_permission,
Link,
NavigationMenu,
- )
+ canonical_url,
+ enabled_with_permission,
+)
from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import TitleBreadcrumb
from lp.services.webapp.publisher import LaunchpadView
from lp.soyuz.browser.sourceslist import (
SourcesListEntries,
SourcesListEntriesView,
- )
+)
class DistributionMirrorOverviewMenu(NavigationMenu):
usedfor = IDistributionMirror
- facet = 'overview'
- links = ['proberlogs', 'edit', 'review', 'reassign', 'delete', 'resubmit']
+ facet = "overview"
+ links = ["proberlogs", "edit", "review", "reassign", "delete", "resubmit"]
- @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 proberlogs(self):
- text = 'Prober logs'
+ text = "Prober logs"
enabled = self.context.last_probe_record is not None
- return Link('+prober-logs', text, icon='info', enabled=enabled)
+ return Link("+prober-logs", text, icon="info", enabled=enabled)
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def delete(self):
enabled = self.context.last_probe_record is None
- text = 'Delete this mirror'
- return Link('+delete', text, icon='remove', enabled=enabled)
+ text = "Delete this mirror"
+ return Link("+delete", text, icon="remove", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def reassign(self):
- text = 'Change owner'
- return Link('+reassign', text, icon='edit')
+ text = "Change owner"
+ return Link("+reassign", text, icon="edit")
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def review(self):
- text = 'Review mirror'
- return Link('+review', text, icon='edit')
+ text = "Review mirror"
+ return Link("+review", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def resubmit(self):
- text = 'Resubmit for review'
+ text = "Resubmit for review"
enabled = self.context.status == MirrorStatus.BROKEN
- return Link('+resubmit', text, icon='edit', enabled=enabled)
+ return Link("+resubmit", text, icon="edit", enabled=enabled)
class _FlavoursByDistroSeries:
@@ -105,12 +105,13 @@ class DistributionMirrorBreadcrumb(TitleBreadcrumb):
class DistributionMirrorView(LaunchpadView):
-
@property
def page_title(self):
"""The HTML page title."""
- values = dict(distribution=self.context.distribution.displayname,
- name=self.context.title)
+ values = dict(
+ distribution=self.context.distribution.displayname,
+ name=self.context.title,
+ )
return '%(distribution)s mirror "%(name)s"' % values
def initialize(self):
@@ -121,11 +122,12 @@ class DistributionMirrorView(LaunchpadView):
series = arch_series.distro_arch_series.distroseries
if series not in valid_series:
valid_series.append(series)
- entries = SourcesListEntries(self.context.distribution,
- self.context.base_url,
- valid_series)
+ entries = SourcesListEntries(
+ self.context.distribution, self.context.base_url, valid_series
+ )
self.sources_list_entries = SourcesListEntriesView(
- entries, self.request, initially_without_selection=True)
+ entries, self.request, initially_without_selection=True
+ )
@cachedproperty
def probe_records(self):
@@ -136,15 +138,21 @@ class DistributionMirrorView(LaunchpadView):
def summarized_arch_series(self):
mirrors = self.context.getSummarizedMirroredArchSeries()
return sorted(
- mirrors, reverse=True,
+ mirrors,
+ reverse=True,
key=lambda mirror: Version(
- mirror.distro_arch_series.distroseries.version))
+ mirror.distro_arch_series.distroseries.version
+ ),
+ )
@property
def summarized_source_series(self):
mirrors = self.context.getSummarizedMirroredSourceSeries()
- return sorted(mirrors, reverse=True,
- key=lambda mirror: Version(mirror.distroseries.version))
+ return sorted(
+ mirrors,
+ reverse=True,
+ key=lambda mirror: Version(mirror.distroseries.version),
+ )
def getCDImageMirroredFlavoursBySeries(self):
"""Return a list of _FlavoursByDistroSeries objects ordered
@@ -159,8 +167,11 @@ class DistributionMirrorView(LaunchpadView):
all_series[series] = flavours_by_series
flavours_by_series.flavours.append(flavour)
flavours_by_series = all_series.values()
- return sorted(flavours_by_series, reverse=True,
- key=lambda item: Version(item.distroseries.version))
+ return sorted(
+ flavours_by_series,
+ reverse=True,
+ key=lambda item: Version(item.distroseries.version),
+ )
class DistributionMirrorDeleteView(LaunchpadFormView):
@@ -171,7 +182,7 @@ class DistributionMirrorDeleteView(LaunchpadFormView):
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Delete mirror %s' % self.context.title
+ return "Delete mirror %s" % self.context.title
@property
def page_title(self):
@@ -185,14 +196,17 @@ class DistributionMirrorDeleteView(LaunchpadFormView):
# and so we do this check here.
if self.context.last_probe_record is not None:
self.request.response.addInfoNotification(
- "This mirror has been probed and thus can't be deleted.")
+ "This mirror has been probed and thus can't be deleted."
+ )
self.next_url = canonical_url(self.context)
return
- self.next_url = canonical_url(self.context.distribution,
- view_name='+pendingreviewmirrors')
+ self.next_url = canonical_url(
+ self.context.distribution, view_name="+pendingreviewmirrors"
+ )
self.request.response.addInfoNotification(
- "Mirror %s has been deleted." % self.context.title)
+ "Mirror %s has been deleted." % self.context.title
+ )
self.context.destroySelf()
@property
@@ -205,10 +219,18 @@ class DistributionMirrorDeleteView(LaunchpadFormView):
class DistributionMirrorAddView(LaunchpadFormView):
schema = IDistributionMirror
field_names = [
- "display_name", "description", "whiteboard", "https_base_url",
- "http_base_url", "ftp_base_url", "rsync_base_url", "speed", "country",
- "content", "official_candidate",
- ]
+ "display_name",
+ "description",
+ "whiteboard",
+ "https_base_url",
+ "http_base_url",
+ "ftp_base_url",
+ "rsync_base_url",
+ "speed",
+ "country",
+ "content",
+ "official_candidate",
+ ]
invariant_context = None
@property
@@ -229,15 +251,19 @@ class DistributionMirrorAddView(LaunchpadFormView):
@action(_("Register Mirror"), name="create")
def create_action(self, action, data):
mirror = self.context.newMirror(
- owner=self.user, speed=data['speed'], country=data['country'],
- content=data['content'], display_name=data['display_name'],
- description=data['description'],
- whiteboard=data['whiteboard'],
- https_base_url=data['https_base_url'],
- http_base_url=data['http_base_url'],
- ftp_base_url=data['ftp_base_url'],
- rsync_base_url=data['rsync_base_url'],
- official_candidate=data['official_candidate'])
+ owner=self.user,
+ speed=data["speed"],
+ country=data["country"],
+ content=data["content"],
+ display_name=data["display_name"],
+ description=data["description"],
+ whiteboard=data["whiteboard"],
+ https_base_url=data["https_base_url"],
+ http_base_url=data["http_base_url"],
+ ftp_base_url=data["ftp_base_url"],
+ rsync_base_url=data["rsync_base_url"],
+ official_candidate=data["official_candidate"],
+ )
self.next_url = canonical_url(mirror)
notify(ObjectCreatedEvent(mirror))
@@ -246,12 +272,12 @@ class DistributionMirrorAddView(LaunchpadFormView):
class DistributionMirrorReviewView(LaunchpadEditFormView):
schema = IDistributionMirror
- field_names = ['status', 'whiteboard']
+ field_names = ["status", "whiteboard"]
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Review mirror %s' % self.context.title
+ return "Review mirror %s" % self.context.title
@property
def page_title(self):
@@ -266,9 +292,9 @@ class DistributionMirrorReviewView(LaunchpadEditFormView):
@action(_("Save"), name="save")
def action_save(self, action, data):
context = self.context
- if data['status'] != context.status:
+ if data["status"] != context.status:
context.reviewer = self.user
- context.date_reviewed = datetime.now(pytz.timezone('UTC'))
+ context.date_reviewed = datetime.now(pytz.timezone("UTC"))
self.updateContextFromData(data)
self.next_url = canonical_url(context)
@@ -277,15 +303,24 @@ class DistributionMirrorEditView(LaunchpadEditFormView):
schema = IDistributionMirror
field_names = [
- "name", "display_name", "description", "whiteboard",
- "https_base_url", "http_base_url", "ftp_base_url", "rsync_base_url",
- "speed", "country", "content", "official_candidate",
- ]
+ "name",
+ "display_name",
+ "description",
+ "whiteboard",
+ "https_base_url",
+ "http_base_url",
+ "ftp_base_url",
+ "rsync_base_url",
+ "speed",
+ "country",
+ "content",
+ "official_candidate",
+ ]
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Edit mirror %s' % self.context.title
+ return "Edit mirror %s" % self.context.title
@property
def page_title(self):
@@ -311,7 +346,7 @@ class DistributionMirrorResubmitView(LaunchpadEditFormView):
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Resubmit mirror %s' % self.context.title
+ return "Resubmit mirror %s" % self.context.title
page_title = label
@@ -322,12 +357,12 @@ class DistributionMirrorResubmitView(LaunchpadEditFormView):
except InvalidMirrorReviewState:
self.request.response.addInfoNotification(
"The mirror is not in the correct state"
- " (broken) and cannot be resubmitted.")
+ " (broken) and cannot be resubmitted."
+ )
self.next_url = canonical_url(self.context)
class DistributionMirrorReassignmentView(ObjectReassignmentView):
-
@property
def contextName(self):
return self.context.title
@@ -339,4 +374,4 @@ class DistributionMirrorProberLogView(DistributionMirrorView):
@property
def page_title(self):
"""The HTML page title."""
- return '%s mirror prober logs' % self.context.title
+ return "%s mirror prober logs" % self.context.title
diff --git a/lib/lp/registry/browser/distributionsourcepackage.py b/lib/lp/registry/browser/distributionsourcepackage.py
index 1d09774..a55097b 100644
--- a/lib/lp/registry/browser/distributionsourcepackage.py
+++ b/lib/lp/registry/browser/distributionsourcepackage.py
@@ -2,41 +2,35 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'DistributionSourcePackageAnswersMenu',
- 'DistributionSourcePackageBreadcrumb',
- 'DistributionSourcePackageChangelogView',
- 'DistributionSourcePackageEditView',
- 'DistributionSourcePackageFacets',
- 'DistributionSourcePackageHelpView',
- 'DistributionSourcePackageNavigation',
- 'DistributionSourcePackageOverviewMenu',
- 'DistributionSourcePackagePublishingHistoryView',
- 'DistributionSourcePackageURL',
- 'DistributionSourcePackageView',
- 'PublishingHistoryViewMixin',
- ]
+ "DistributionSourcePackageAnswersMenu",
+ "DistributionSourcePackageBreadcrumb",
+ "DistributionSourcePackageChangelogView",
+ "DistributionSourcePackageEditView",
+ "DistributionSourcePackageFacets",
+ "DistributionSourcePackageHelpView",
+ "DistributionSourcePackageNavigation",
+ "DistributionSourcePackageOverviewMenu",
+ "DistributionSourcePackagePublishingHistoryView",
+ "DistributionSourcePackageURL",
+ "DistributionSourcePackageView",
+ "PublishingHistoryViewMixin",
+]
-from functools import cmp_to_key
import itertools
import operator
+from functools import cmp_to_key
import apt_pkg
from lazr.delegates import delegate_to
from zope.component import getUtility
-from zope.interface import (
- implementer,
- Interface,
- )
+from zope.interface import Interface, implementer
from lp.answers.browser.questiontarget import (
QuestionTargetAnswersMenu,
QuestionTargetTraversalMixin,
- )
+)
from lp.answers.enums import QuestionStatus
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadEditFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadEditFormView, action
from lp.app.browser.stringformatter import extract_email_addresses
from lp.app.browser.tales import CustomizableFormatter
from lp.app.enums import ServiceUsage
@@ -44,10 +38,10 @@ from lp.app.interfaces.headings import IHeadingBreadcrumb
from lp.app.interfaces.launchpad import IServiceUsage
from lp.bugs.browser.bugtask import BugTargetTraversalMixin
from lp.bugs.browser.structuralsubscription import (
- expose_structural_subscription_data_to_js,
StructuralSubscriptionMenuMixin,
StructuralSubscriptionTargetTraversalMixin,
- )
+ expose_structural_subscription_data_to_js,
+)
from lp.bugs.interfaces.bugtask import BugTaskStatus
from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
from lp.code.browser.vcslisting import TargetDefaultVCSNavigationMixin
@@ -56,41 +50,41 @@ from lp.registry.browser.pillar import PillarBugsMenu
from lp.registry.enums import DistributionDefaultTraversalPolicy
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
- )
+)
from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.series import SeriesStatus
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.helpers import shortlist
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
Navigation,
- redirection,
StandardLaunchpadFacets,
- )
+ canonical_url,
+ redirection,
+)
from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.interfaces import (
ICanonicalUrlData,
IMultiFacetedBreadcrumb,
- )
+)
from lp.services.webapp.menu import (
ApplicationMenu,
- enabled_with_permission,
Link,
NavigationMenu,
- )
+ enabled_with_permission,
+)
from lp.services.webapp.publisher import LaunchpadView
from lp.services.webapp.sorting import sorted_dotted_numbers
from lp.soyuz.browser.sourcepackagerelease import linkify_changelog
from lp.soyuz.interfaces.archive import IArchiveSet
from lp.soyuz.interfaces.distributionsourcepackagerelease import (
IDistributionSourcePackageRelease,
- )
+)
from lp.soyuz.interfaces.packagediff import IPackageDiffSet
from lp.translations.browser.customlanguagecode import (
HasCustomLanguageCodesTraversalMixin,
- )
+)
@implementer(ICanonicalUrlData)
@@ -114,8 +108,10 @@ class DistributionSourcePackageURL:
@property
def path(self):
policy = self.context.distribution.default_traversal_policy
- if (policy == DistributionDefaultTraversalPolicy.SOURCE_PACKAGE and
- not self.context.distribution.redirect_default_traversal):
+ if (
+ policy == DistributionDefaultTraversalPolicy.SOURCE_PACKAGE
+ and not self.context.distribution.redirect_default_traversal
+ ):
return self.context.name
else:
return "+source/%s" % self.context.name
@@ -124,12 +120,12 @@ class DistributionSourcePackageURL:
class DistributionSourcePackageFormatterAPI(CustomizableFormatter):
"""Adapt IDistributionSourcePackage objects to a formatted string."""
- _link_permission = 'zope.Public'
- _link_summary_template = '%(displayname)s'
+ _link_permission = "zope.Public"
+ _link_summary_template = "%(displayname)s"
def _link_summary_values(self):
displayname = self._context.displayname
- return {'displayname': displayname}
+ return {"displayname": displayname}
@implementer(IHeadingBreadcrumb, IMultiFacetedBreadcrumb)
@@ -138,32 +134,31 @@ class DistributionSourcePackageBreadcrumb(Breadcrumb):
@property
def text(self):
- return '%s package' % self.context.sourcepackagename.name
+ return "%s package" % self.context.sourcepackagename.name
class DistributionSourcePackageFacets(StandardLaunchpadFacets):
usedfor = IDistributionSourcePackage
enable_only = [
- 'overview',
- 'branches',
- 'bugs',
- 'translations',
- 'answers',
- ]
+ "overview",
+ "branches",
+ "bugs",
+ "translations",
+ "answers",
+ ]
class DistributionSourcePackageLinksMixin:
-
def publishinghistory(self):
- return Link('+publishinghistory', 'Show publishing history')
+ return Link("+publishinghistory", "Show publishing history")
- @enabled_with_permission('launchpad.BugSupervisor')
+ @enabled_with_permission("launchpad.BugSupervisor")
def edit(self):
"""Edit the details of this source package."""
# This is titled "Edit bug reporting guidelines" because that
# is the only editable property of a source package right now.
- return Link('+edit', 'Configure bug tracker', icon='edit')
+ return Link("+edit", "Configure bug tracker", icon="edit")
def new_bugs(self):
base_path = "+bugs"
@@ -177,24 +172,26 @@ class DistributionSourcePackageLinksMixin:
class DistributionSourcePackageOverviewMenu(
- ApplicationMenu, DistributionSourcePackageLinksMixin):
+ ApplicationMenu, DistributionSourcePackageLinksMixin
+):
usedfor = IDistributionSourcePackage
- facet = 'overview'
- links = ['new_bugs', 'open_questions']
+ facet = "overview"
+ links = ["new_bugs", "open_questions"]
class DistributionSourcePackageBugsMenu(
PillarBugsMenu,
StructuralSubscriptionMenuMixin,
- DistributionSourcePackageLinksMixin):
+ DistributionSourcePackageLinksMixin,
+):
usedfor = IDistributionSourcePackage
- facet = 'bugs'
+ facet = "bugs"
@cachedproperty
def links(self):
- links = ['filebug']
+ links = ["filebug"]
add_subscribe_link(links)
return links
@@ -202,30 +199,34 @@ class DistributionSourcePackageBugsMenu(
class DistributionSourcePackageAnswersMenu(QuestionTargetAnswersMenu):
usedfor = IDistributionSourcePackage
- facet = 'answers'
+ facet = "answers"
- links = QuestionTargetAnswersMenu.links + ['gethelp']
+ links = QuestionTargetAnswersMenu.links + ["gethelp"]
def gethelp(self):
- return Link('+gethelp', 'Help and support options', icon='info')
+ return Link("+gethelp", "Help and support options", icon="info")
-class DistributionSourcePackageNavigation(Navigation,
- BugTargetTraversalMixin, HasCustomLanguageCodesTraversalMixin,
- QuestionTargetTraversalMixin, TargetDefaultVCSNavigationMixin,
- StructuralSubscriptionTargetTraversalMixin):
+class DistributionSourcePackageNavigation(
+ Navigation,
+ BugTargetTraversalMixin,
+ HasCustomLanguageCodesTraversalMixin,
+ QuestionTargetTraversalMixin,
+ TargetDefaultVCSNavigationMixin,
+ StructuralSubscriptionTargetTraversalMixin,
+):
usedfor = IDistributionSourcePackage
- @redirection('+editbugcontact')
+ @redirection("+editbugcontact")
def redirect_editbugcontact(self):
- return '+subscribe'
+ return "+subscribe"
def traverse(self, name):
return self.context.getVersion(name)
-@delegate_to(IDistributionSourcePackageRelease, context='context')
+@delegate_to(IDistributionSourcePackageRelease, context="context")
class DecoratedDistributionSourcePackageRelease:
"""A decorated DistributionSourcePackageRelease.
@@ -234,8 +235,13 @@ class DecoratedDistributionSourcePackageRelease:
"""
def __init__(
- self, distributionsourcepackagerelease, publishing_history,
- package_diffs, person_data, user):
+ self,
+ distributionsourcepackagerelease,
+ publishing_history,
+ package_diffs,
+ person_data,
+ user,
+ ):
self.context = distributionsourcepackagerelease
self._publishing_history = publishing_history
self._package_diffs = package_diffs
@@ -244,19 +250,20 @@ class DecoratedDistributionSourcePackageRelease:
@property
def publishing_history(self):
- """ See `IDistributionSourcePackageRelease`."""
+ """See `IDistributionSourcePackageRelease`."""
return self._publishing_history
@property
def package_diffs(self):
- """ See `ISourcePackageRelease`."""
+ """See `ISourcePackageRelease`."""
return self._package_diffs
@property
def change_summary(self):
- """ See `ISourcePackageRelease`."""
+ """See `ISourcePackageRelease`."""
return linkify_changelog(
- self._user, self.context.change_summary, self._person_data)
+ self._user, self.context.change_summary, self._person_data
+ )
class IDistributionSourcePackageActionMenu(Interface):
@@ -266,26 +273,28 @@ class IDistributionSourcePackageActionMenu(Interface):
class DistributionSourcePackageActionMenu(
NavigationMenu,
StructuralSubscriptionMenuMixin,
- DistributionSourcePackageLinksMixin):
+ DistributionSourcePackageLinksMixin,
+):
"""Action menu for distro source packages."""
+
usedfor = IDistributionSourcePackageActionMenu
- facet = 'overview'
- title = 'Actions'
+ facet = "overview"
+ title = "Actions"
@cachedproperty
def links(self):
- links = ['publishing_history', 'change_log']
+ links = ["publishing_history", "change_log"]
add_subscribe_link(links)
- links.append('edit')
+ links.append("edit")
return links
def publishing_history(self):
- text = 'View full publishing history'
- return Link('+publishinghistory', text, icon="info")
+ text = "View full publishing history"
+ return Link("+publishinghistory", text, icon="info")
def change_log(self):
- text = 'View full change log'
- return Link('+changelog', text, icon="info")
+ text = "View full change log"
+ return Link("+changelog", text, icon="info")
class DistributionSourcePackageBaseView(LaunchpadView):
@@ -297,52 +306,71 @@ class DistributionSourcePackageBaseView(LaunchpadView):
def not_empty(text):
return (
- text is not None and isinstance(text, str)
- and len(text.strip()) > 0)
+ text is not None
+ and isinstance(text, str)
+ and len(text.strip()) > 0
+ )
def decorate(dspr_pubs):
sprs = [dspr.sourcepackagerelease for (dspr, spphs) in dspr_pubs]
# Preload email/person data only if user is logged on. In
# the opposite case the emails in the changelog will be
# obfuscated anyway and thus cause no database lookups.
- the_changelog = '\n'.join(
- [spr.changelog_entry for spr in sprs
- if not_empty(spr.changelog_entry)])
+ the_changelog = "\n".join(
+ [
+ spr.changelog_entry
+ for spr in sprs
+ if not_empty(spr.changelog_entry)
+ ]
+ )
if self.user:
self._person_data = {
- email.email: person for (email, person) in
- getUtility(IPersonSet).getByEmails(
- extract_email_addresses(the_changelog),
- include_hidden=False)}
+ email.email: person
+ for (email, person) in getUtility(IPersonSet).getByEmails(
+ extract_email_addresses(the_changelog),
+ include_hidden=False,
+ )
+ }
else:
self._person_data = None
# Collate diffs for relevant SourcePackageReleases
pkg_diffs = getUtility(IPackageDiffSet).getDiffsToReleases(
- sprs, preload_for_display=True)
+ sprs, preload_for_display=True
+ )
spr_diffs = {}
for spr, diffs in itertools.groupby(
- pkg_diffs, operator.attrgetter('to_source')):
+ pkg_diffs, operator.attrgetter("to_source")
+ ):
spr_diffs[spr] = list(diffs)
return [
DecoratedDistributionSourcePackageRelease(
- dspr, spphs, spr_diffs.get(dspr.sourcepackagerelease, []),
- self._person_data, self.user)
- for (dspr, spphs) in dspr_pubs]
+ dspr,
+ spphs,
+ spr_diffs.get(dspr.sourcepackagerelease, []),
+ self._person_data,
+ self.user,
+ )
+ for (dspr, spphs) in dspr_pubs
+ ]
+
return DecoratedResultSet(
self.context.getReleasesAndPublishingHistory(),
- bulk_decorator=decorate)
+ bulk_decorator=decorate,
+ )
@implementer(IDistributionSourcePackageActionMenu)
-class DistributionSourcePackageView(DistributionSourcePackageBaseView,
- LaunchpadView):
+class DistributionSourcePackageView(
+ DistributionSourcePackageBaseView, LaunchpadView
+):
"""View class for DistributionSourcePackage."""
def initialize(self):
super().initialize()
expose_structural_subscription_data_to_js(
- self.context, self.request, self.user)
+ self.context, self.request, self.user
+ )
@property
def label(self):
@@ -390,8 +418,10 @@ class DistributionSourcePackageView(DistributionSourcePackageBaseView,
# three archives.
archive_set = getUtility(IArchiveSet)
publications = archive_set.getPublicationsInArchives(
- self.context.sourcepackagename, top_three_archives,
- distribution=self.context.distribution)
+ self.context.sourcepackagename,
+ top_three_archives,
+ distribution=self.context.distribution,
+ )
# Collect the publishings for each archive
archive_publishings = {}
@@ -408,26 +438,28 @@ class DistributionSourcePackageView(DistributionSourcePackageBaseView,
# 'Jaunty (1.0.1b)' to the versions list.
for pub in archive_publishings[archive]:
versions.append(
- "%s (%s)" % (
+ "%s (%s)"
+ % (
pub.distroseries.displayname,
pub.source_package_version,
- )
)
- archive_versions.append({
- 'archive': archive,
- 'versions': ", ".join(versions),
- })
+ )
+ archive_versions.append(
+ {
+ "archive": archive,
+ "versions": ", ".join(versions),
+ }
+ )
return archive_versions
@property
def further_ppa_versions_url(self):
- """Return the url used to find further PPA versions of this package.
- """
+ """Return the url used to find further PPA versions of this package."""
return "%s/+ppas?name_filter=%s" % (
canonical_url(self.context.distribution),
self.context.name,
- )
+ )
@cachedproperty
def active_distroseries_packages(self):
@@ -453,7 +485,8 @@ class DistributionSourcePackageView(DistributionSourcePackageBaseView,
for package in self.active_distroseries_packages:
series.add(package.distroseries)
result = sorted_dotted_numbers(
- series, key=operator.attrgetter('version'))
+ series, key=operator.attrgetter("version")
+ )
result.reverse()
return result
@@ -465,7 +498,8 @@ class DistributionSourcePackageView(DistributionSourcePackageBaseView,
publications = getUtility(IArchiveSet).getPublicationsInArchives(
sourcepackage.sourcepackagename,
sourcepackage.distribution.all_distro_archives,
- distroseries=sourcepackage.distroseries)
+ distroseries=sourcepackage.distroseries,
+ )
pocket_dict = {}
for pub in shortlist(publications):
version = pub.source_package_version
@@ -477,7 +511,8 @@ class DistributionSourcePackageView(DistributionSourcePackageBaseView,
if len(self.active_series) == 0:
return None
return self.active_series[0].getSourcePackage(
- self.context.sourcepackagename)
+ self.context.sourcepackagename
+ )
@property
def version_table(self):
@@ -493,51 +528,57 @@ class DistributionSourcePackageView(DistributionSourcePackageBaseView,
# fill it in.
show_set_upstream_link = (
packaging is None
- and distroseries.status in (
+ and distroseries.status
+ in (
SeriesStatus.CURRENT,
SeriesStatus.DEVELOPMENT,
- )
)
+ )
title_row = {
- 'blank_row': False,
- 'title_row': True,
- 'data_row': False,
- 'distroseries': distroseries,
- 'series_package': package,
- 'packaging': packaging,
- 'show_set_upstream_link': show_set_upstream_link,
- }
+ "blank_row": False,
+ "title_row": True,
+ "data_row": False,
+ "distroseries": distroseries,
+ "series_package": package,
+ "packaging": packaging,
+ "show_set_upstream_link": show_set_upstream_link,
+ }
rows.append(title_row)
# After the title row, we list each package version that's
# currently published, and which pockets it's published in.
pocket_dict = self.published_by_version(package)
for version in sorted(
- pocket_dict,
- key=cmp_to_key(apt_pkg.version_compare), reverse=True):
+ pocket_dict,
+ key=cmp_to_key(apt_pkg.version_compare),
+ reverse=True,
+ ):
most_recent_publication = pocket_dict[version][0]
date_published = most_recent_publication.datepublished
pockets = ", ".join(
- [pub.pocket.name for pub in pocket_dict[version]])
+ [pub.pocket.name for pub in pocket_dict[version]]
+ )
row = {
- 'blank_row': False,
- 'title_row': False,
- 'data_row': True,
- 'version': version,
- 'publication': most_recent_publication,
- 'pockets': pockets,
- 'component': most_recent_publication.component_name,
- 'date_published': date_published,
- }
+ "blank_row": False,
+ "title_row": False,
+ "data_row": True,
+ "version": version,
+ "publication": most_recent_publication,
+ "pockets": pockets,
+ "component": most_recent_publication.component_name,
+ "date_published": date_published,
+ }
rows.append(row)
# We need a blank row after each section, so the series
# header row doesn't appear too close to the previous
# section.
- rows.append({
- 'blank_row': True,
- 'title_row': False,
- 'data_row': False,
- })
+ rows.append(
+ {
+ "blank_row": True,
+ "title_row": False,
+ "data_row": False,
+ }
+ )
return rows
@@ -548,34 +589,37 @@ class DistributionSourcePackageView(DistributionSourcePackageBaseView,
@cachedproperty
def bugs_answers_usage(self):
- """Return a dict of uses_bugs, uses_answers, uses_both, uses_either.
- """
+ """Return a dict of uses_bugs, uses_answers, uses_both, uses_either."""
service_usage = IServiceUsage(self.context)
- uses_bugs = (
- service_usage.bug_tracking_usage == ServiceUsage.LAUNCHPAD)
+ uses_bugs = service_usage.bug_tracking_usage == ServiceUsage.LAUNCHPAD
uses_answers = service_usage.answers_usage == ServiceUsage.LAUNCHPAD
uses_both = uses_bugs and uses_answers
uses_either = uses_bugs or uses_answers
return dict(
- uses_bugs=uses_bugs, uses_answers=uses_answers,
- uses_both=uses_both, uses_either=uses_either)
+ uses_bugs=uses_bugs,
+ uses_answers=uses_answers,
+ uses_both=uses_both,
+ uses_either=uses_either,
+ )
@cachedproperty
def new_bugtasks_count(self):
search_params = BugTaskSearchParams(
- self.user, status=BugTaskStatus.NEW, omit_dupes=True)
+ self.user, status=BugTaskStatus.NEW, omit_dupes=True
+ )
return self.context.searchTasks(search_params).count()
class DistributionSourcePackageChangelogView(
- DistributionSourcePackageBaseView, LaunchpadView):
+ DistributionSourcePackageBaseView, LaunchpadView
+):
"""View for presenting change logs for a `DistributionSourcePackage`."""
- page_title = 'Change log'
+ page_title = "Change log"
@property
def label(self):
- return 'Change log for %s' % self.context.title
+ return "Change log for %s" % self.context.title
@cachedproperty
def batchnav(self):
@@ -591,8 +635,11 @@ class PublishingHistoryViewMixin:
ids.update((spph.removed_byID, spph.creatorID, spph.sponsorID))
ids.discard(None)
if ids:
- list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
- ids, need_validity=True))
+ list(
+ getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ ids, need_validity=True
+ )
+ )
@property
def batchnav(self):
@@ -601,19 +648,22 @@ class PublishingHistoryViewMixin:
return BatchNavigator(
DecoratedResultSet(
self.context.publishing_history,
- pre_iter_hook=self._preload_people),
- self.request)
+ pre_iter_hook=self._preload_people,
+ ),
+ self.request,
+ )
class DistributionSourcePackagePublishingHistoryView(
- LaunchpadView, PublishingHistoryViewMixin):
+ LaunchpadView, PublishingHistoryViewMixin
+):
"""View for presenting `DistributionSourcePackage` publishing history."""
- page_title = 'Publishing history'
+ page_title = "Publishing history"
@property
def label(self):
- return 'Publishing history of %s' % self.context.title
+ return "Publishing history of %s" % self.context.title
class DistributionSourcePackageEditView(LaunchpadEditFormView):
@@ -621,22 +671,22 @@ class DistributionSourcePackageEditView(LaunchpadEditFormView):
schema = IDistributionSourcePackage
field_names = [
- 'bug_reporting_guidelines',
- 'bug_reported_acknowledgement',
- 'enable_bugfiling_duplicate_search',
- ]
+ "bug_reporting_guidelines",
+ "bug_reported_acknowledgement",
+ "enable_bugfiling_duplicate_search",
+ ]
@property
def label(self):
"""The form label."""
- return 'Edit %s' % self.context.title
+ return "Edit %s" % self.context.title
@property
def page_title(self):
"""The page title."""
return self.label
- @action("Change", name='change')
+ @action("Change", name="change")
def change_action(self, action, data):
self.updateContextFromData(data)
@@ -650,4 +700,4 @@ class DistributionSourcePackageEditView(LaunchpadEditFormView):
class DistributionSourcePackageHelpView(LaunchpadView):
"""A View to show Answers help."""
- page_title = 'Help and support options'
+ page_title = "Help and support options"
diff --git a/lib/lp/registry/browser/distroseries.py b/lib/lp/registry/browser/distroseries.py
index ee34d1f..f1a25fe 100644
--- a/lib/lp/registry/browser/distroseries.py
+++ b/lib/lp/registry/browser/distroseries.py
@@ -4,20 +4,20 @@
"""View classes related to `IDistroSeries`."""
__all__ = [
- 'DistroSeriesAddView',
- 'DistroSeriesAdminView',
- 'DistroSeriesBreadcrumb',
- 'DistroSeriesEditView',
- 'DistroSeriesInitializeView',
- 'DistroSeriesLocalDifferencesView',
- 'DistroSeriesMissingPackagesView',
- 'DistroSeriesNavigation',
- 'DistroSeriesPackageSearchView',
- 'DistroSeriesPackagesView',
- 'DistroSeriesUniquePackagesView',
- 'DistroSeriesURL',
- 'DistroSeriesView',
- ]
+ "DistroSeriesAddView",
+ "DistroSeriesAdminView",
+ "DistroSeriesBreadcrumb",
+ "DistroSeriesEditView",
+ "DistroSeriesInitializeView",
+ "DistroSeriesLocalDifferencesView",
+ "DistroSeriesMissingPackagesView",
+ "DistroSeriesNavigation",
+ "DistroSeriesPackageSearchView",
+ "DistroSeriesPackagesView",
+ "DistroSeriesUniquePackagesView",
+ "DistroSeriesURL",
+ "DistroSeriesView",
+]
import apt_pkg
from lazr.restful.interface import copy_field
@@ -26,57 +26,44 @@ from zope.component import getUtility
from zope.event import notify
from zope.formlib import form
from zope.formlib.widget import CustomWidgetFactory
-from zope.interface import (
- implementer,
- Interface,
- )
+from zope.interface import Interface, implementer
from zope.lifecycleevent import ObjectCreatedEvent
-from zope.schema import (
- Choice,
- List,
- TextLine,
- )
-from zope.schema.vocabulary import (
- SimpleTerm,
- SimpleVocabulary,
- )
+from zope.schema import Choice, List, TextLine
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from lp import _
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.errors import NotFoundError
from lp.app.widgets.itemswidgets import (
LabeledMultiCheckBoxWidget,
LaunchpadDropdownWidget,
LaunchpadRadioWidget,
- )
+)
from lp.app.widgets.popup import PersonPickerWidget
from lp.archivepublisher.interfaces.publisherconfig import IPublisherConfigSet
from lp.blueprints.browser.specificationtarget import (
HasSpecificationsMenuMixin,
- )
+)
from lp.bugs.browser.bugtask import BugTargetTraversalMixin
from lp.bugs.browser.structuralsubscription import (
- expose_structural_subscription_data_to_js,
StructuralSubscriptionMenuMixin,
StructuralSubscriptionTargetTraversalMixin,
- )
-from lp.registry.browser import (
- add_subscribe_link,
- MilestoneOverlayMixin,
- )
+ expose_structural_subscription_data_to_js,
+)
+from lp.registry.browser import MilestoneOverlayMixin, add_subscribe_link
from lp.registry.enums import (
DistributionDefaultTraversalPolicy,
DistroSeriesDifferenceStatus,
DistroSeriesDifferenceType,
- )
+)
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.distroseriesdifference import (
IDistroSeriesDifferenceSource,
- )
+)
from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.series import SeriesStatus
@@ -92,16 +79,16 @@ from lp.services.webapp.escaping import structured
from lp.services.webapp.interfaces import ICanonicalUrlData
from lp.services.webapp.menu import (
ApplicationMenu,
- enabled_with_permission,
Link,
NavigationMenu,
- )
+ enabled_with_permission,
+)
from lp.services.webapp.publisher import (
- canonical_url,
LaunchpadView,
+ canonical_url,
stepthrough,
stepto,
- )
+)
from lp.services.webapp.url import urlappend
from lp.services.worlddata.helpers import browser_languages
from lp.services.worlddata.interfaces.country import ICountry
@@ -111,22 +98,21 @@ from lp.soyuz.browser.packagesearch import PackageSearchViewBase
from lp.soyuz.enums import PackageCopyPolicy
from lp.soyuz.interfaces.distributionjob import (
IDistroSeriesDifferenceJobSource,
- )
+)
from lp.soyuz.interfaces.packagecopyjob import IPlainPackageCopyJobSource
from lp.soyuz.interfaces.packageset import IPackagesetSet
from lp.soyuz.interfaces.queue import IPackageUploadSet
from lp.soyuz.model.queue import PackageUploadQueue
from lp.translations.browser.distroseries import (
check_distroseries_translations_viewable,
- )
-
+)
# DistroSeries statuses that benefit from mass package upgrade support.
UPGRADABLE_SERIES_STATUSES = [
SeriesStatus.FUTURE,
SeriesStatus.EXPERIMENTAL,
SeriesStatus.DEVELOPMENT,
- ]
+]
def get_dsd_source():
@@ -155,24 +141,29 @@ class DistroSeriesURL:
@property
def path(self):
policy = self.context.distribution.default_traversal_policy
- if (policy == DistributionDefaultTraversalPolicy.SERIES and
- not self.context.distribution.redirect_default_traversal):
+ if (
+ policy == DistributionDefaultTraversalPolicy.SERIES
+ and not self.context.distribution.redirect_default_traversal
+ ):
return self.context.name
else:
return "+series/%s" % self.context.name
-class DistroSeriesNavigation(GetitemNavigation, BugTargetTraversalMixin,
- StructuralSubscriptionTargetTraversalMixin):
+class DistroSeriesNavigation(
+ GetitemNavigation,
+ BugTargetTraversalMixin,
+ StructuralSubscriptionTargetTraversalMixin,
+):
usedfor = IDistroSeries
- @stepthrough('+lang')
+ @stepthrough("+lang")
def traverse_lang(self, langcode):
"""Retrieve the DistroSeriesLanguage or a dummy if one it is None."""
# We do not want users to see the 'en' pofile because
# we store the messages we want to translate as English.
- if langcode == 'en':
+ if langcode == "en":
raise NotFoundError(langcode)
langset = getUtility(ILanguageSet)
@@ -191,36 +182,36 @@ class DistroSeriesNavigation(GetitemNavigation, BugTargetTraversalMixin,
return distroserieslang
- @stepthrough('+source')
+ @stepthrough("+source")
def source(self, name):
return self.context.getSourcePackage(name)
# sabdfl 17/10/05 please keep this old location here for
# LaunchpadIntegration on Breezy, unless you can figure out how to
# redirect to the newer +source, defined above
- @stepthrough('+sources')
+ @stepthrough("+sources")
def sources(self, name):
return self.context.getSourcePackage(name)
- @stepthrough('+package')
+ @stepthrough("+package")
def package(self, name):
return self.context.getBinaryPackage(name)
- @stepto('+latest-full-language-pack')
+ @stepto("+latest-full-language-pack")
def latest_full_language_pack(self):
if self.context.last_full_language_pack_exported is None:
return None
else:
return self.context.last_full_language_pack_exported.file
- @stepto('+latest-delta-language-pack')
+ @stepto("+latest-delta-language-pack")
def redirect_latest_delta_language_pack(self):
if self.context.last_delta_language_pack_exported is None:
return None
else:
return self.context.last_delta_language_pack_exported.file
- @stepthrough('+upload')
+ @stepthrough("+upload")
def traverse_queue(self, id):
try:
queue_id = int(id)
@@ -241,117 +232,127 @@ class DistroSeriesBreadcrumb(Breadcrumb):
class DistroSeriesOverviewMenu(
- ApplicationMenu, StructuralSubscriptionMenuMixin):
+ ApplicationMenu, StructuralSubscriptionMenuMixin
+):
usedfor = IDistroSeries
- facet = 'overview'
+ facet = "overview"
@property
def links(self):
- links = ['edit',
- 'driver',
- 'answers',
- 'packaging',
- 'needs_packaging',
- 'builds',
- 'queue',
- 'add_port',
- 'create_milestone',
- 'initseries',
- ]
+ links = [
+ "edit",
+ "driver",
+ "answers",
+ "packaging",
+ "needs_packaging",
+ "builds",
+ "queue",
+ "add_port",
+ "create_milestone",
+ "initseries",
+ ]
add_subscribe_link(links)
- links.append('admin')
+ links.append("admin")
return links
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
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 driver(self):
- text = 'Appoint driver'
- summary = 'Someone with permission to set goals for this series'
- return Link('+driver', text, summary, icon='edit')
+ text = "Appoint driver"
+ summary = "Someone with permission to set goals for this series"
+ return Link("+driver", text, summary, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def create_milestone(self):
- text = 'Create milestone'
- summary = 'Register a new milestone for this series'
- return Link('+addmilestone', text, summary, icon='add')
+ text = "Create milestone"
+ summary = "Register a new milestone for this series"
+ return Link("+addmilestone", text, summary, icon="add")
def packaging(self):
- text = 'All upstream links'
- summary = 'A listing of source packages and their upstream projects'
- return Link('+packaging', text, summary=summary, icon='info')
+ text = "All upstream links"
+ summary = "A listing of source packages and their upstream projects"
+ return Link("+packaging", text, summary=summary, icon="info")
def needs_packaging(self):
- text = 'Needs upstream links'
- summary = 'A listing of source packages without upstream projects'
- return Link('+needs-packaging', text, summary=summary, icon='info')
+ text = "Needs upstream links"
+ summary = "A listing of source packages without upstream projects"
+ return Link("+needs-packaging", text, summary=summary, icon="info")
# A search link isn't needed because the distro series overview
# has a search form.
def answers(self):
- text = 'Ask a question'
- url = canonical_url(self.context.distribution) + '/+addquestion'
- return Link(url, text, icon='add')
+ text = "Ask a question"
+ url = canonical_url(self.context.distribution) + "/+addquestion"
+ return Link(url, text, icon="add")
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def add_port(self):
- text = 'Add architecture'
- return Link('+addport', text, icon='add')
+ text = "Add architecture"
+ return Link("+addport", text, icon="add")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def admin(self):
- text = 'Administer'
- return Link('+admin', text, icon='edit')
+ text = "Administer"
+ return Link("+admin", text, icon="edit")
def builds(self):
- text = 'Show builds'
- return Link('+builds', text, icon='info')
+ text = "Show builds"
+ return Link("+builds", text, icon="info")
def queue(self):
- text = 'Show uploads'
- return Link('+queue', text, icon='info')
+ text = "Show uploads"
+ return Link("+queue", text, icon="info")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def initseries(self):
enabled = (
- not self.context.isInitializing() and
- not self.context.isInitialized())
- text = 'Initialize series'
- return Link('+initseries', text, icon='edit', enabled=enabled)
+ not self.context.isInitializing()
+ and not self.context.isInitialized()
+ )
+ text = "Initialize series"
+ return Link("+initseries", text, icon="edit", enabled=enabled)
class DistroSeriesBugsMenu(ApplicationMenu, StructuralSubscriptionMenuMixin):
usedfor = IDistroSeries
- facet = 'bugs'
+ facet = "bugs"
@property
def links(self):
- links = ['cve',
- 'nominations',
- ]
+ links = [
+ "cve",
+ "nominations",
+ ]
add_subscribe_link(links)
return links
def cve(self):
- return Link('+cve', 'CVE reports', icon='cve')
+ return Link("+cve", "CVE reports", icon="cve")
def nominations(self):
- return Link('+nominations', 'Review nominations', icon='bug')
+ return Link("+nominations", "Review nominations", icon="bug")
-class DistroSeriesSpecificationsMenu(NavigationMenu,
- HasSpecificationsMenuMixin):
+class DistroSeriesSpecificationsMenu(
+ NavigationMenu, HasSpecificationsMenuMixin
+):
usedfor = IDistroSeries
- facet = 'specifications'
+ facet = "specifications"
links = [
- 'listall', 'listdeclined', 'assignments', 'setgoals',
- 'new', 'register_sprint']
+ "listall",
+ "listdeclined",
+ "assignments",
+ "setgoals",
+ "new",
+ "register_sprint",
+ ]
class DistroSeriesPackageSearchView(PackageSearchViewBase):
@@ -361,7 +362,7 @@ class DistroSeriesPackageSearchView(PackageSearchViewBase):
"""See `AbstractPackageSearchView`."""
return self.context.searchPackages(self.text)
- label = 'Search packages'
+ label = "Search packages"
class SeriesStatusMixin:
@@ -379,35 +380,43 @@ class SeriesStatusMixin:
SeriesStatus.CURRENT,
SeriesStatus.SUPPORTED,
SeriesStatus.OBSOLETE,
- )
+ )
if self.context.status not in stable_status:
- terms = [status for status in SeriesStatus.items
- if status not in stable_status]
+ terms = [
+ status
+ for status in SeriesStatus.items
+ if status not in stable_status
+ ]
terms.append(SeriesStatus.CURRENT)
else:
terms = stable_status
status_vocabulary = SimpleVocabulary(
- [SimpleTerm(item, item.name, item.title) for item in terms])
+ [SimpleTerm(item, item.name, item.title) for item in terms]
+ )
return form.Fields(
- Choice(__name__='status',
- title=_('Status'),
- default=self.context.status,
- vocabulary=status_vocabulary,
- description=_("Select the distroseries status."),
- required=True))
+ Choice(
+ __name__="status",
+ title=_("Status"),
+ default=self.context.status,
+ vocabulary=status_vocabulary,
+ description=_("Select the distroseries status."),
+ required=True,
+ )
+ )
def updateDateReleased(self, status):
"""Update the datereleased field if the status is set to CURRENT."""
- if (self.context.datereleased is None and
- status == SeriesStatus.CURRENT):
+ if (
+ self.context.datereleased is None
+ and status == SeriesStatus.CURRENT
+ ):
self.context.datereleased = UTC_NOW
class DerivedDistroSeriesMixin:
-
@cachedproperty
def has_unique_parent(self):
return len(self.context.getParentSeries()) == 1
@@ -425,13 +434,12 @@ class DerivedDistroSeriesMixin:
def getParentName(self, multiple_parent_default=None):
if self.has_unique_parent:
- return ("parent series '%s'" %
- self.unique_parent.displayname)
+ return "parent series '%s'" % self.unique_parent.displayname
else:
if multiple_parent_default is not None:
return multiple_parent_default
else:
- return 'a parent series'
+ return "a parent series"
def word_differences_count(count):
@@ -442,22 +450,26 @@ def word_differences_count(count):
return get_plural_text(count, "%d package", "%d packages") % count
-class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
- DerivedDistroSeriesMixin):
-
+class DistroSeriesView(
+ LaunchpadView, MilestoneOverlayMixin, DerivedDistroSeriesMixin
+):
def initialize(self):
super().initialize()
- self.displayname = '%s %s' % (
+ self.displayname = "%s %s" % (
self.context.distribution.displayname,
- self.context.version)
+ self.context.version,
+ )
expose_structural_subscription_data_to_js(
- self.context, self.request, self.user)
+ self.context, self.request, self.user
+ )
@property
def page_title(self):
"""Return the HTML page title."""
- return '%s %s in Launchpad' % (
- self.context.distribution.title, self.context.version)
+ return "%s %s in Launchpad" % (
+ self.context.distribution.title,
+ self.context.version,
+ )
def requestCountry(self):
return ICountry(self.request, None)
@@ -472,9 +484,10 @@ class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
permitted; we redirect to the distribution's file
"""
distro_url = canonical_url(
- self.context.distribution, view_name='+filebug')
- if self.request.form.get('no-redirect') is not None:
- distro_url += '?no-redirect'
+ self.context.distribution, view_name="+filebug"
+ )
+ if self.request.form.get("no-redirect") is not None:
+ distro_url += "?no-redirect"
return self.request.response.redirect(distro_url)
@cachedproperty
@@ -513,24 +526,27 @@ class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
attention? If not, count all that can be viewed.
"""
if needing_attention_only:
- status = (DistroSeriesDifferenceStatus.NEEDS_ATTENTION, )
+ status = (DistroSeriesDifferenceStatus.NEEDS_ATTENTION,)
else:
status = None
differences = get_dsd_source().getForDistroSeries(
- self.context, difference_type=difference_type, status=status)
+ self.context, difference_type=difference_type, status=status
+ )
return differences.count()
@cachedproperty
def num_version_differences_needing_attention(self):
return self.countDifferences(
- DistroSeriesDifferenceType.DIFFERENT_VERSIONS)
+ DistroSeriesDifferenceType.DIFFERENT_VERSIONS
+ )
@cachedproperty
def num_version_differences(self):
return self.countDifferences(
DistroSeriesDifferenceType.DIFFERENT_VERSIONS,
- needing_attention_only=False)
+ needing_attention_only=False,
+ )
def wordVersionDifferences(self):
return word_differences_count(self.num_version_differences)
@@ -538,7 +554,8 @@ class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
@cachedproperty
def num_differences_in_parent(self):
return self.countDifferences(
- DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES)
+ DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
+ )
def wordDifferencesInParent(self):
return word_differences_count(self.num_differences_in_parent)
@@ -546,7 +563,8 @@ class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
@cachedproperty
def num_differences_in_child(self):
return self.countDifferences(
- DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES)
+ DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
+ )
def wordDifferencesInChild(self):
return word_differences_count(self.num_differences_in_child)
@@ -566,24 +584,25 @@ class DistroSeriesView(LaunchpadView, MilestoneOverlayMixin,
@cachedproperty
def link_to_version_diffs_needing_attention(self):
"""Return URL for +localpackagediffs page."""
- return canonical_url(self.context, view_name='+localpackagediffs')
+ return canonical_url(self.context, view_name="+localpackagediffs")
@property
def link_to_all_version_diffs(self):
"""Return URL for +localdiffs page for all statuses."""
return (
"%s?field.package_type=all"
- % self.link_to_version_diffs_needing_attention)
+ % self.link_to_version_diffs_needing_attention
+ )
@property
def link_to_differences_in_parent(self):
"""Return URL for +missingpackages page."""
- return canonical_url(self.context, view_name='+missingpackages')
+ return canonical_url(self.context, view_name="+missingpackages")
@property
def link_to_differences_in_child(self):
"""Return URL for +uniquepackages page."""
- return canonical_url(self.context, view_name='+uniquepackages')
+ return canonical_url(self.context, view_name="+uniquepackages")
class DistroSeriesEditView(LaunchpadEditFormView, SeriesStatusMixin):
@@ -591,14 +610,15 @@ class DistroSeriesEditView(LaunchpadEditFormView, SeriesStatusMixin):
It redirects to the main distroseries page after a successful edit.
"""
+
schema = IDistroSeries
- field_names = ['display_name', 'title', 'summary', 'description']
+ field_names = ["display_name", "title", "summary", "description"]
custom_widget_status = LaunchpadDropdownWidget
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Edit %s details' % self.context.title
+ return "Edit %s details" % self.context.title
@property
def page_title(self):
@@ -618,22 +638,23 @@ class DistroSeriesEditView(LaunchpadEditFormView, SeriesStatusMixin):
"""
LaunchpadEditFormView.setUpFields(self)
self.series_are_harmless = (
- not self.context.distribution.official_packages)
- self.has_admin = check_permission('launchpad.Admin', self.context)
+ not self.context.distribution.official_packages
+ )
+ self.has_admin = check_permission("launchpad.Admin", self.context)
if self.has_admin or self.series_are_harmless:
# The user is an admin or damage to the series can't break
# archives.
- self.form_fields = (
- self.form_fields + self.createStatusField())
+ self.form_fields = self.form_fields + self.createStatusField()
@action("Change")
def change_action(self, action, data):
"""Update the context and redirects to its overviw page."""
if self.has_admin or self.series_are_harmless:
- self.updateDateReleased(data.get('status'))
+ self.updateDateReleased(data.get("status"))
self.updateContextFromData(data)
self.request.response.addInfoNotification(
- 'Your changes have been applied.')
+ "Your changes have been applied."
+ )
self.next_url = canonical_url(self.context)
@@ -642,15 +663,20 @@ class DistroSeriesAdminView(LaunchpadEditFormView, SeriesStatusMixin):
It redirects to the main distroseries page after a successful edit.
"""
+
schema = IDistroSeries
field_names = [
- 'name', 'version', 'changeslist', 'inherit_overrides_from_parents']
+ "name",
+ "version",
+ "changeslist",
+ "inherit_overrides_from_parents",
+ ]
custom_widget_status = LaunchpadDropdownWidget
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Administer %s' % self.context.title
+ return "Administer %s" % self.context.title
@property
def page_title(self):
@@ -669,8 +695,7 @@ class DistroSeriesAdminView(LaunchpadEditFormView, SeriesStatusMixin):
'status' field. See `createStatusField` method.
"""
LaunchpadEditFormView.setUpFields(self)
- self.form_fields = (
- self.form_fields + self.createStatusField())
+ self.form_fields = self.form_fields + self.createStatusField()
@action("Change")
def change_action(self, action, data):
@@ -679,50 +704,56 @@ class DistroSeriesAdminView(LaunchpadEditFormView, SeriesStatusMixin):
Also, set 'datereleased' when a unstable distroseries is made
CURRENT.
"""
- self.updateDateReleased(data.get('status'))
+ self.updateDateReleased(data.get("status"))
self.updateContextFromData(data)
self.request.response.addInfoNotification(
- 'Your changes have been applied.')
+ "Your changes have been applied."
+ )
self.next_url = canonical_url(self.context)
class IDistroSeriesAddForm(Interface):
name = copy_field(
- IDistroSeries["name"], description=_(
- "The name of this series as used for URLs."))
+ IDistroSeries["name"],
+ description=_("The name of this series as used for URLs."),
+ )
version = copy_field(
- IDistroSeries["version"], description=_(
- "The version of the new series."))
+ IDistroSeries["version"],
+ description=_("The version of the new series."),
+ )
display_name = copy_field(
- IDistroSeries["display_name"], description=_(
- "The name of the new series as it would "
- "appear in a paragraph."))
+ IDistroSeries["display_name"],
+ description=_(
+ "The name of the new series as it would " "appear in a paragraph."
+ ),
+ )
summary = copy_field(IDistroSeries["summary"])
class DistroSeriesAddView(LaunchpadFormView):
"""A view to create an `IDistroSeries`."""
+
schema = IDistroSeriesAddForm
field_names = [
- 'name',
- 'version',
- 'display_name',
- 'summary',
- ]
+ "name",
+ "version",
+ "display_name",
+ "summary",
+ ]
help_links = {
"name": "/+help-registry/distribution-add-series.html#codename",
- }
+ }
- label = 'Add a series'
+ label = "Add a series"
page_title = label
- @action(_('Add Series'), name='create')
+ @action(_("Add Series"), name="create")
def createAndAdd(self, action, data):
"""Create and add a new Distribution Series"""
# 'series' is a cached property so this won't issue 2 queries.
@@ -732,14 +763,15 @@ class DistroSeriesAddView(LaunchpadFormView):
previous_series = None
# previous_series will be None if there isn't one.
distroseries = self.context.newSeries(
- name=data['name'],
- display_name=data['display_name'],
- title=data['display_name'],
- summary=data['summary'],
+ name=data["name"],
+ display_name=data["display_name"],
+ title=data["display_name"],
+ summary=data["summary"],
description="",
- version=data['version'],
+ version=data["version"],
previous_series=previous_series,
- registrant=self.user)
+ registrant=self.user,
+ )
notify(ObjectCreatedEvent(distroseries))
self.next_url = canonical_url(distroseries)
return distroseries
@@ -753,11 +785,10 @@ def seriesToVocab(series):
# Simple helper function to format series data into a dict:
# {'value':series_id, 'api_uri': api_uri, 'title': series_title}.
return {
- 'value': series.id,
- 'title': '%s: %s'
- % (series.distribution.displayname, series.title),
- 'api_uri': canonical_url(
- series, path_only_if_possible=True)}
+ "value": series.id,
+ "title": "%s: %s" % (series.distribution.displayname, series.title),
+ "api_uri": canonical_url(series, path_only_if_possible=True),
+ }
class EmptySchema(Interface):
@@ -768,7 +799,7 @@ class DistroSeriesInitializeView(LaunchpadFormView):
"""A view to initialize an `IDistroSeries`."""
schema = EmptySchema
- label = 'Initialize series'
+ label = "Initialize series"
page_title = label
def initialize(self):
@@ -776,27 +807,31 @@ class DistroSeriesInitializeView(LaunchpadFormView):
cache = IJSONRequestCache(self.request).objects
distribution = self.context.distribution
is_first_derivation = not distribution.has_published_sources
- cache['is_first_derivation'] = is_first_derivation
- if (not is_first_derivation and
- self.context.previous_series is not None):
- cache['previous_series'] = seriesToVocab(
- self.context.previous_series)
+ cache["is_first_derivation"] = is_first_derivation
+ if (
+ not is_first_derivation
+ and self.context.previous_series is not None
+ ):
+ cache["previous_series"] = seriesToVocab(
+ self.context.previous_series
+ )
previous_parents = self.context.previous_series.getParentSeries()
- cache['previous_parents'] = [
- seriesToVocab(series) for series in previous_parents]
+ cache["previous_parents"] = [
+ seriesToVocab(series) for series in previous_parents
+ ]
- @action("Initialize Series", name='initialize')
+ @action("Initialize Series", name="initialize")
def submit(self, action, data):
"""Stub for the Javascript in the page to use."""
@cachedproperty
def show_derivation_form(self):
return (
- not self.show_previous_series_empty_message and
- not self.show_already_initializing_message and
- not self.show_already_initialized_message and
- not self.show_no_publisher_message
- )
+ not self.show_previous_series_empty_message
+ and not self.show_already_initializing_message
+ and not self.show_already_initialized_message
+ and not self.show_no_publisher_message
+ )
@cachedproperty
def show_previous_series_empty_message(self):
@@ -804,8 +839,9 @@ class DistroSeriesInitializeView(LaunchpadFormView):
# The distribution already has initialized series and this
# distroseries has no previous_series.
return (
- self.context.distribution.has_published_sources and
- self.context.previous_series is None)
+ self.context.distribution.has_published_sources
+ and self.context.previous_series is None
+ )
@cachedproperty
def show_already_initialized_message(self):
@@ -832,39 +868,39 @@ class DistroSeriesInitializeView(LaunchpadFormView):
class DistroSeriesPackagesView(LaunchpadView):
"""A View to show series package to upstream package relationships."""
- label = 'All series packages linked to upstream project series'
- page_title = 'All upstream links'
+ label = "All series packages linked to upstream project series"
+ page_title = "All upstream links"
@cachedproperty
def cached_packagings(self):
"""The batched upstream packaging links."""
packagings = self.context.getPrioritizedPackagings()
navigator = BatchNavigator(packagings, self.request, size=20)
- navigator.setHeadings('packaging', 'packagings')
+ navigator.setHeadings("packaging", "packagings")
return navigator
# A helper to create package filtering radio button vocabulary.
-NON_IGNORED = 'non-ignored'
-HIGHER_VERSION_THAN_PARENT = 'higher-than-parent'
-RESOLVED = 'resolved'
-ALL = 'all'
+NON_IGNORED = "non-ignored"
+HIGHER_VERSION_THAN_PARENT = "higher-than-parent"
+RESOLVED = "resolved"
+ALL = "all"
DEFAULT_PACKAGE_TYPE = NON_IGNORED
def make_package_type_vocabulary(parent_name, higher_version_option=False):
voc = [
- SimpleTerm(NON_IGNORED, NON_IGNORED, 'Non ignored packages'),
+ SimpleTerm(NON_IGNORED, NON_IGNORED, "Non ignored packages"),
SimpleTerm(RESOLVED, RESOLVED, "Resolved package differences"),
- SimpleTerm(ALL, ALL, 'All packages'),
- ]
+ SimpleTerm(ALL, ALL, "All packages"),
+ ]
if higher_version_option:
higher_term = SimpleTerm(
HIGHER_VERSION_THAN_PARENT,
HIGHER_VERSION_THAN_PARENT,
- "Ignored packages with a higher version than in %s"
- % parent_name)
+ "Ignored packages with a higher version than in %s" % parent_name,
+ )
voc.insert(1, higher_term)
return SimpleVocabulary(tuple(voc))
@@ -872,45 +908,50 @@ def make_package_type_vocabulary(parent_name, higher_version_option=False):
class DistroSeriesNeedsPackagesView(LaunchpadView):
"""A View to show series package to upstream package relationships."""
- label = 'Packages that need upstream packaging links'
- page_title = 'Needs upstream links'
+ label = "Packages that need upstream packaging links"
+ page_title = "Needs upstream links"
@cachedproperty
def cached_unlinked_packages(self):
"""The batched `ISourcePackage`s that needs packaging links."""
packages = self.context.getPrioritizedUnlinkedSourcePackages()
navigator = BatchNavigator(packages, self.request, size=20)
- navigator.setHeadings('package', 'packages')
+ navigator.setHeadings("package", "packages")
return navigator
class IDifferencesFormSchema(Interface):
- name_filter = TextLine(
- title=_("Package name contains"), required=False)
+ name_filter = TextLine(title=_("Package name contains"), required=False)
selected_differences = List(
- title=_('Selected differences'),
+ title=_("Selected differences"),
value_type=Choice(vocabulary="DistroSeriesDifferences"),
description=_("Select the differences for syncing."),
- required=True)
+ required=True,
+ )
sponsored_person = Choice(
- title="Person being sponsored", vocabulary='ValidPerson',
- required=False)
+ title="Person being sponsored",
+ vocabulary="ValidPerson",
+ required=False,
+ )
-class DistroSeriesDifferenceBaseView(LaunchpadFormView,
- PackageCopyingMixin,
- DerivedDistroSeriesMixin):
+class DistroSeriesDifferenceBaseView(
+ LaunchpadFormView, PackageCopyingMixin, DerivedDistroSeriesMixin
+):
"""Base class for all pages presenting differences between
a derived series and its parent."""
+
schema = IDifferencesFormSchema
- field_names = ['selected_differences', 'sponsored_person']
+ field_names = ["selected_differences", "sponsored_person"]
custom_widget_selected_differences = LabeledMultiCheckBoxWidget
custom_widget_package_type = LaunchpadRadioWidget
custom_widget_sponsored_person = CustomWidgetFactory(
PersonPickerWidget,
- header="Select person being sponsored", show_assign_me_button=False)
+ header="Select person being sponsored",
+ show_assign_me_button=False,
+ )
# Differences type to display. Can be overrided by sublasses.
differences_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
@@ -931,12 +972,16 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
# action which we can modify.
actions = self.actions
sync_action = next(
- action for action in actions if action.name == "sync")
+ action for action in actions if action.name == "sync"
+ )
sync_action.label = label
# Mask the actions descriptor with an instance variable.
self.actions = actions.__class__(
- *((sync_action if action.name == "sync" else action)
- for action in actions))
+ *(
+ (sync_action if action.name == "sync" else action)
+ for action in actions
+ )
+ )
@property
def label(self):
@@ -946,14 +991,17 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
if self.has_unique_parent:
parent_name = "'%s'" % self.unique_parent.displayname
else:
- parent_name = 'parent'
- return form.Fields(Choice(
- __name__='package_type',
- vocabulary=make_package_type_vocabulary(
- parent_name,
- self.search_higher_parent_option),
- default=DEFAULT_PACKAGE_TYPE,
- required=True))
+ parent_name = "parent"
+ return form.Fields(
+ Choice(
+ __name__="package_type",
+ vocabulary=make_package_type_vocabulary(
+ parent_name, self.search_higher_parent_option
+ ),
+ default=DEFAULT_PACKAGE_TYPE,
+ required=True,
+ )
+ )
def setUpFields(self):
"""Add the selected differences field.
@@ -962,9 +1010,7 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
for its own vocabulary, we set it up after all the others.
"""
super().setUpFields()
- self.form_fields = (
- self.setupPackageFilterRadio() +
- self.form_fields)
+ self.form_fields = self.setupPackageFilterRadio() + self.form_fields
def _sync_sources(self, action, data):
"""Synchronise packages from the parent series to this one."""
@@ -973,10 +1019,8 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
# isn't, we need to implement a way of flagging sources 'to be
# synced' and write a job runner to do it in the background.
- selected_differences = data['selected_differences']
- sources = [
- diff.parent_source_pub
- for diff in selected_differences]
+ selected_differences = data["selected_differences"]
+ sources = [diff.parent_source_pub for diff in selected_differences]
# PackageCopyingMixin.do_copy() does the work of copying and
# setting up on-page notifications.
@@ -992,10 +1036,17 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
sponsored_person = data.get("sponsored_person")
if self.do_copy(
- 'selected_differences', sources, self.context.main_archive,
- self.context, destination_pocket, include_binaries=False,
- dest_url=series_url, dest_display_name=series_title,
- person=self.user, sponsored_person=sponsored_person):
+ "selected_differences",
+ sources,
+ self.context.main_archive,
+ self.context,
+ destination_pocket,
+ include_binaries=False,
+ dest_url=series_url,
+ dest_display_name=series_title,
+ person=self.user,
+ sponsored_person=sponsored_person,
+ ):
# The copy worked so we redirect back to show the results. Include
# the query string so that the user ends up on the same batch page
# with the same filtering parameters as before.
@@ -1017,9 +1068,10 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
"""Validate selected differences."""
form.getWidgetsData(self.widgets, self.prefix, data)
- if len(data.get('selected_differences', [])) == 0:
+ if len(data.get("selected_differences", [])) == 0:
self.setFieldError(
- 'selected_differences', 'No differences selected.')
+ "selected_differences", "No differences selected."
+ )
def canPerformSync(self, *args):
"""Return whether a sync can be performed.
@@ -1028,11 +1080,11 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
well as directly in the template.
"""
archive = self.context.main_archive
- has_perm = (self.user is not None and (
- archive.hasAnyPermission(self.user) or
- check_permission('launchpad.Append', archive)))
- return (has_perm and
- self.cached_differences.batch.total() > 0)
+ has_perm = self.user is not None and (
+ archive.hasAnyPermission(self.user)
+ or check_permission("launchpad.Append", archive)
+ )
+ return has_perm and self.cached_differences.batch.total() > 0
@cachedproperty
def pending_syncs(self):
@@ -1052,7 +1104,8 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
"""
job_source = getUtility(IDistroSeriesDifferenceJobSource)
return job_source.getPendingJobsForDifferences(
- self.context, self.cached_differences.batch)
+ self.context, self.cached_differences.batch
+ )
def hasPendingDSDUpdate(self, dsd):
"""Have there been changes that `dsd` is still being updated for?"""
@@ -1082,7 +1135,8 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
# parent being newer.
return False
comparison = apt_pkg.version_compare(
- dsd.parent_source_version, dsd.source_version)
+ dsd.parent_source_version, dsd.source_version
+ )
return comparison < 0
def canRequestSync(self, dsd):
@@ -1092,8 +1146,10 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
# is newer than the parent's version, or if there is already a sync
# pending.
return (
- dsd.status != DistroSeriesDifferenceStatus.RESOLVED and
- not self.isNewerThanParent(dsd) and not self.pendingSync(dsd))
+ dsd.status != DistroSeriesDifferenceStatus.RESOLVED
+ and not self.isNewerThanParent(dsd)
+ and not self.pendingSync(dsd)
+ )
def describeJobs(self, dsd):
"""Describe any jobs that may be pending for `dsd`.
@@ -1119,23 +1175,30 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
if pending_sync is not None:
# If the pending sync is waiting in the distroseries queues,
# provide a handy link to there.
- queue_item = getUtility(IPackageUploadSet).getByPackageCopyJobIDs(
- (pending_sync.id,)).any()
+ queue_item = (
+ getUtility(IPackageUploadSet)
+ .getByPackageCopyJobIDs((pending_sync.id,))
+ .any()
+ )
if queue_item is None:
description.append("synchronizing")
else:
url = urlappend(
- canonical_url(self.context), "+queue?queue_state=%s" %
- queue_item.status.value)
- description.append('waiting in <a href="%s">%s</a>' %
- (url, queue_item.status.name))
+ canonical_url(self.context),
+ "+queue?queue_state=%s" % queue_item.status.value,
+ )
+ description.append(
+ 'waiting in <a href="%s">%s</a>'
+ % (url, queue_item.status.name)
+ )
return " and ".join(description) + "…"
@property
def specified_name_filter(self):
"""If specified, return the name filter from the GET form data."""
requested_name_filter = self.request.query_string_params.get(
- 'field.name_filter')
+ "field.name_filter"
+ )
if requested_name_filter and requested_name_filter[0]:
return requested_name_filter[0]
@@ -1145,15 +1208,20 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
@property
def specified_packagesets_filter(self):
"""If specified, return Packagesets given in the GET form data."""
- packageset_ids = (
- self.request.query_string_params.get("field.packageset", []))
+ packageset_ids = self.request.query_string_params.get(
+ "field.packageset", []
+ )
packageset_ids = {
- int(packageset_id) for packageset_id in packageset_ids
- if packageset_id.isdigit()}
+ int(packageset_id)
+ for packageset_id in packageset_ids
+ if packageset_id.isdigit()
+ }
packagesets = getUtility(IPackagesetSet).getBySeries(self.context)
packagesets = {
- packageset for packageset in packagesets
- if packageset.id in packageset_ids}
+ packageset
+ for packageset in packagesets
+ if packageset.id in packageset_ids
+ }
return None if len(packagesets) == 0 else packagesets
@property
@@ -1161,11 +1229,10 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
"""If specified, return Persons given in the GET form data."""
get_person_by_name = getUtility(IPersonSet).getByName
changed_by_names = set(
- self.request.query_string_params.get("field.changed_by", ()))
- changed_by = (
- get_person_by_name(name) for name in changed_by_names)
- changed_by = {
- person for person in changed_by if person is not None}
+ self.request.query_string_params.get("field.changed_by", ())
+ )
+ changed_by = (get_person_by_name(name) for name in changed_by_names)
+ changed_by = {person for person in changed_by if person is not None}
return None if len(changed_by) == 0 else changed_by
@property
@@ -1174,7 +1241,8 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
data.
"""
package_type = self.request.query_string_params.get(
- 'field.package_type')
+ "field.package_type"
+ )
if package_type and package_type[0]:
return package_type[0]
else:
@@ -1186,7 +1254,8 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
package_type_dsd_status = {
NON_IGNORED: DistroSeriesDifferenceStatus.NEEDS_ATTENTION,
HIGHER_VERSION_THAN_PARENT: (
- DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT),
+ DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT
+ ),
RESOLVED: DistroSeriesDifferenceStatus.RESOLVED,
ALL: None,
}
@@ -1194,35 +1263,42 @@ class DistroSeriesDifferenceBaseView(LaunchpadFormView,
# If the package_type option is not supported, add an error to
# the field and return an empty list.
if self.specified_package_type not in package_type_dsd_status:
- self.setFieldError('package_type', 'Invalid option')
+ self.setFieldError("package_type", "Invalid option")
differences = []
else:
status = package_type_dsd_status[self.specified_package_type]
child_version_higher = (
- self.specified_package_type == HIGHER_VERSION_THAN_PARENT)
+ self.specified_package_type == HIGHER_VERSION_THAN_PARENT
+ )
differences = get_dsd_source().getForDistroSeries(
- self.context, difference_type=self.differences_type,
- name_filter=self.specified_name_filter, status=status,
+ self.context,
+ difference_type=self.differences_type,
+ name_filter=self.specified_name_filter,
+ status=status,
child_version_higher=child_version_higher,
packagesets=self.specified_packagesets_filter,
- changed_by=self.specified_changed_by_filter)
+ changed_by=self.specified_changed_by_filter,
+ )
return BatchNavigator(differences, self.request)
def parent_changelog_url(self, distroseriesdifference):
- """The URL to the /parent/series/+source/package/+changelog """
+ """The URL to the /parent/series/+source/package/+changelog"""
distro = distroseriesdifference.parent_series.distribution
dsp = distro.getSourcePackage(
- distroseriesdifference.source_package_name)
- return urlappend(canonical_url(dsp), '+changelog')
+ distroseriesdifference.source_package_name
+ )
+ return urlappend(canonical_url(dsp), "+changelog")
-class DistroSeriesLocalDifferencesView(DistroSeriesDifferenceBaseView,
- LaunchpadFormView):
+class DistroSeriesLocalDifferencesView(
+ DistroSeriesDifferenceBaseView, LaunchpadFormView
+):
"""Present differences of type DIFFERENT_VERSIONS between
a derived series and its parent.
"""
- page_title = 'Local package differences'
+
+ page_title = "Local package differences"
differences_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
show_packagesets = True
search_higher_parent_option = True
@@ -1232,12 +1308,14 @@ class DistroSeriesLocalDifferencesView(DistroSeriesDifferenceBaseView,
if self.has_unique_parent:
parent_name = "'%s'" % self.unique_parent.displayname
else:
- parent_name = 'Parent'
+ parent_name = "Parent"
self.initialize_sync_label(
- "Sync Selected %s Versions into %s" % (
+ "Sync Selected %s Versions into %s"
+ % (
parent_name,
self.context.displayname,
- ))
+ )
+ )
super().initialize()
@property
@@ -1249,21 +1327,24 @@ class DistroSeriesLocalDifferencesView(DistroSeriesDifferenceBaseView,
"versions (and the diff if necessary) before syncing the parent "
'version (<a href="/+help-soyuz/derived-series-syncing.html" '
'target="help">Read more about syncing from a parent series'
- '</a>).',
+ "</a>).",
self.context.displayname,
- self.getParentName())
+ self.getParentName(),
+ )
@property
def label(self):
- return (
- "Source package differences between '%s' and"
- " %s" % (
- self.context.displayname,
- self.getParentName(multiple_parent_default='parent series'),
- ))
-
- @action(_("Sync Sources"), name="sync", validator='validate_sync',
- condition='canPerformSync')
+ return "Source package differences between '%s' and" " %s" % (
+ self.context.displayname,
+ self.getParentName(multiple_parent_default="parent series"),
+ )
+
+ @action(
+ _("Sync Sources"),
+ name="sync",
+ validator="validate_sync",
+ condition="canPerformSync",
+ )
def sync_sources(self, action, data):
self._sync_sources(action, data)
@@ -1279,7 +1360,7 @@ class DistroSeriesLocalDifferencesView(DistroSeriesDifferenceBaseView,
"""
return get_dsd_source().getSimpleUpgrades(self.context)
- @action(_("Upgrade Packages"), name="upgrade", condition='canUpgrade')
+ @action(_("Upgrade Packages"), name="upgrade", condition="canUpgrade")
def upgrade(self, action, data):
"""Request synchronization of straightforward package upgrades."""
self.requestUpgrades()
@@ -1296,14 +1377,19 @@ class DistroSeriesLocalDifferencesView(DistroSeriesDifferenceBaseView,
target_distroseries,
PackagePublishingPocket.RELEASE,
)
- for dsd in self.getUpgrades()]
+ for dsd in self.getUpgrades()
+ ]
getUtility(IPlainPackageCopyJobSource).createMultiple(
- copies, self.user, copy_policy=PackageCopyPolicy.MASS_SYNC)
+ copies, self.user, copy_policy=PackageCopyPolicy.MASS_SYNC
+ )
self.request.response.addInfoNotification(
- ("Upgrades of {context.displayname} packages have been "
- "requested. Please give Launchpad some time to complete "
- "these.").format(context=self.context))
+ (
+ "Upgrades of {context.displayname} packages have been "
+ "requested. Please give Launchpad some time to complete "
+ "these."
+ ).format(context=self.context)
+ )
def canUpgrade(self, action=None):
"""Should the form offer a packages upgrade?"""
@@ -1320,12 +1406,14 @@ class DistroSeriesLocalDifferencesView(DistroSeriesDifferenceBaseView,
return check_permission("launchpad.Edit", queue)
-class DistroSeriesMissingPackagesView(DistroSeriesDifferenceBaseView,
- LaunchpadFormView):
+class DistroSeriesMissingPackagesView(
+ DistroSeriesDifferenceBaseView, LaunchpadFormView
+):
"""Present differences of type MISSING_FROM_DERIVED_SERIES between
a derived series and its parent.
"""
- page_title = 'Missing packages'
+
+ page_title = "Missing packages"
differences_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
show_derived_version = False
show_package_diffs = False
@@ -1334,9 +1422,8 @@ class DistroSeriesMissingPackagesView(DistroSeriesDifferenceBaseView,
def initialize(self):
# Update the label for sync action.
self.initialize_sync_label(
- "Include Selected packages into %s" % (
- self.context.displayname,
- ))
+ "Include Selected packages into %s" % (self.context.displayname,)
+ )
super().initialize()
@property
@@ -1347,28 +1434,34 @@ class DistroSeriesMissingPackagesView(DistroSeriesDifferenceBaseView,
"They are listed here so you can consider including them in %s.",
self.getParentName(),
self.context.displayname,
- self.context.displayname)
+ self.context.displayname,
+ )
@property
def label(self):
- return (
- "Packages in %s but not in '%s'" % (
- self.getParentName(),
- self.context.displayname,
- ))
+ return "Packages in %s but not in '%s'" % (
+ self.getParentName(),
+ self.context.displayname,
+ )
- @action(_("Sync Sources"), name="sync", validator='validate_sync',
- condition='canPerformSync')
+ @action(
+ _("Sync Sources"),
+ name="sync",
+ validator="validate_sync",
+ condition="canPerformSync",
+ )
def sync_sources(self, action, data):
self._sync_sources(action, data)
-class DistroSeriesUniquePackagesView(DistroSeriesDifferenceBaseView,
- LaunchpadFormView):
+class DistroSeriesUniquePackagesView(
+ DistroSeriesDifferenceBaseView, LaunchpadFormView
+):
"""Present differences of type UNIQUE_TO_DERIVED_SERIES between
a derived series and its parent.
"""
- page_title = 'Unique packages'
+
+ page_title = "Unique packages"
differences_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
show_parent = True
show_parent_version = False # The DSDs are unique to the derived series.
@@ -1384,15 +1477,15 @@ class DistroSeriesUniquePackagesView(DistroSeriesDifferenceBaseView,
"Packages that are listed here are those that have been added to "
"%s but are not yet part of %s.",
self.context.displayname,
- self.getParentName())
+ self.getParentName(),
+ )
@property
def label(self):
- return (
- "Packages in '%s' but not in %s" % (
- self.context.displayname,
- self.getParentName(),
- ))
+ return "Packages in '%s' but not in %s" % (
+ self.context.displayname,
+ self.getParentName(),
+ )
def canPerformSync(self, *args):
return False
diff --git a/lib/lp/registry/browser/distroseriesdifference.py b/lib/lp/registry/browser/distroseriesdifference.py
index ddcd915..a64469b 100644
--- a/lib/lp/registry/browser/distroseriesdifference.py
+++ b/lib/lp/registry/browser/distroseriesdifference.py
@@ -4,62 +4,49 @@
"""Browser views for DistroSeriesDifferences."""
__all__ = [
- 'CommentXHTMLRepresentation',
- 'DistroSeriesDifferenceView',
- ]
+ "CommentXHTMLRepresentation",
+ "DistroSeriesDifferenceView",
+]
from lazr.delegates import delegate_to
from lazr.restful.interfaces import IWebServiceClientRequest
from zope.browserpage import ViewPageTemplateFile
-from zope.component import (
- adapter,
- getUtility,
- )
+from zope.component import adapter, getUtility
from zope.formlib.itemswidgets import RadioWidget
-from zope.interface import (
- implementer,
- Interface,
- )
+from zope.interface import Interface, implementer
from zope.schema import Choice
-from zope.schema.vocabulary import (
- SimpleTerm,
- SimpleVocabulary,
- )
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from lp.app.browser.launchpadform import LaunchpadFormView
from lp.registry.enums import (
DistroSeriesDifferenceStatus,
DistroSeriesDifferenceType,
- )
+)
from lp.registry.interfaces.distroseriesdifference import (
IDistroSeriesDifference,
- )
+)
from lp.registry.interfaces.distroseriesdifferencecomment import (
IDistroSeriesDifferenceComment,
IDistroSeriesDifferenceCommentSource,
- )
+)
from lp.registry.model.distroseriesdifferencecomment import (
DistroSeriesDifferenceComment,
- )
+)
from lp.services.comments.browser.messagecomment import MessageComment
from lp.services.comments.interfaces.conversation import (
IComment,
IConversation,
- )
+)
from lp.services.messages.interfaces.message import IMessage
from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
- LaunchpadView,
- Navigation,
- stepthrough,
- )
+from lp.services.webapp import LaunchpadView, Navigation, stepthrough
from lp.services.webapp.authorization import check_permission
class DistroSeriesDifferenceNavigation(Navigation):
usedfor = IDistroSeriesDifference
- @stepthrough('comments')
+ @stepthrough("comments")
def traverse_comment(self, id_str):
try:
id = int(id_str)
@@ -67,8 +54,8 @@ class DistroSeriesDifferenceNavigation(Navigation):
return None
return getUtility(
- IDistroSeriesDifferenceCommentSource).getForDifference(
- self.context, id)
+ IDistroSeriesDifferenceCommentSource
+ ).getForDifference(self.context, id)
@property
def parent_packagesets_names(self):
@@ -87,24 +74,31 @@ class DistroSeriesDifferenceNavigation(Navigation):
def _formatPackageSets(self, packagesets):
"""Format a list of packagesets to display in the UI."""
if packagesets is not None:
- return ', '.join([packageset.name for packageset in packagesets])
+ return ", ".join([packageset.name for packageset in packagesets])
else:
return None
class IDistroSeriesDifferenceForm(Interface):
"""An interface used in the browser only for displaying form elements."""
- blacklist_options = Choice(vocabulary=SimpleVocabulary((
- SimpleTerm('NONE', 'NONE', 'No'),
- SimpleTerm(
- DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
- DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS.name,
- 'All versions'),
- SimpleTerm(
- DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
- DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT.name,
- 'These versions'),
- )))
+
+ blacklist_options = Choice(
+ vocabulary=SimpleVocabulary(
+ (
+ SimpleTerm("NONE", "NONE", "No"),
+ SimpleTerm(
+ DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
+ DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS.name,
+ "All versions",
+ ),
+ SimpleTerm(
+ DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
+ DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT.name,
+ "These versions",
+ ),
+ )
+ )
+ )
@implementer(IConversation)
@@ -118,11 +112,11 @@ class DistroSeriesDifferenceView(LaunchpadFormView):
blacklisted_statuses = (
DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
DistroSeriesDifferenceStatus.BLACKLISTED_ALWAYS,
- )
+ )
if self.context.status in blacklisted_statuses:
return dict(blacklist_options=self.context.status)
- return dict(blacklist_options='NONE')
+ return dict(blacklist_options="NONE")
@property
def binary_summaries(self):
@@ -136,7 +130,7 @@ class DistroSeriesDifferenceView(LaunchpadFormView):
if source_pub is not None:
summary = source_pub.meta_sourcepackage.summary
if summary:
- return summary.split('\n')
+ return summary.split("\n")
return None
@@ -144,15 +138,17 @@ class DistroSeriesDifferenceView(LaunchpadFormView):
def comments(self):
"""See `IConversation`."""
comments = self.context.getComments().order_by(
- DistroSeriesDifferenceComment.id)
+ DistroSeriesDifferenceComment.id
+ )
return [
- DistroSeriesDifferenceDisplayComment(comment) for
- comment in comments]
+ DistroSeriesDifferenceDisplayComment(comment)
+ for comment in comments
+ ]
@cachedproperty
def can_request_diffs(self):
"""Does the user have permission to request diff calculation?"""
- return check_permission('launchpad.Edit', self.context)
+ return check_permission("launchpad.Edit", self.context)
@cachedproperty
def show_add_comment(self):
@@ -167,7 +163,8 @@ class DistroSeriesDifferenceView(LaunchpadFormView):
is an archive admin.
"""
return self.request.is_ajax and check_permission(
- 'launchpad.Admin', self.context)
+ "launchpad.Admin", self.context
+ )
@cachedproperty
def blacklist_options_css_class(self):
@@ -176,9 +173,9 @@ class DistroSeriesDifferenceView(LaunchpadFormView):
'blacklist-options-disabled' if not enabled.
"""
if self.enable_blacklist_options:
- return 'blacklist-options'
+ return "blacklist-options"
else:
- return 'blacklist-options-disabled'
+ return "blacklist-options-disabled"
@property
def display_diffs(self):
@@ -212,13 +209,16 @@ class DistroSeriesDifferenceView(LaunchpadFormView):
request link.
"""
derived_diff_computable = (
- not self.context.package_diff and self.display_child_diff)
+ not self.context.package_diff and self.display_child_diff
+ )
parent_diff_computable = (
- not self.context.parent_package_diff and self.display_parent_diff)
- return (self.display_diffs and
- self.can_request_diffs and
- (derived_diff_computable or
- parent_diff_computable))
+ not self.context.parent_package_diff and self.display_parent_diff
+ )
+ return (
+ self.display_diffs
+ and self.can_request_diffs
+ and (derived_diff_computable or parent_diff_computable)
+ )
@property
def display_package_diffs_info(self):
@@ -236,9 +236,10 @@ class DistroSeriesDifferenceView(LaunchpadFormView):
"""
return (
- self.context.package_diff is not None or
- self.context.parent_package_diff is not None or
- self.show_package_diffs_request_link)
+ self.context.package_diff is not None
+ or self.context.parent_package_diff is not None
+ or self.show_package_diffs_request_link
+ )
class IDistroSeriesDifferenceDisplayComment(IComment, IMessage):
@@ -246,7 +247,7 @@ class IDistroSeriesDifferenceDisplayComment(IComment, IMessage):
@implementer(IDistroSeriesDifferenceDisplayComment)
-@delegate_to(IMessage, context='_message')
+@delegate_to(IMessage, context="_message")
class DistroSeriesDifferenceDisplayComment(MessageComment):
"""Used simply to provide `IComment` for rendering."""
@@ -272,8 +273,10 @@ def get_message(comment):
@implementer(Interface)
class CommentXHTMLRepresentation(LaunchpadView):
"""Render individual comments when requested via the API."""
+
template = ViewPageTemplateFile(
- '../templates/distroseriesdifferencecomment-fragment.pt')
+ "../templates/distroseriesdifferencecomment-fragment.pt"
+ )
@property
def comment(self):
diff --git a/lib/lp/registry/browser/distroseriesdifferencecomment.py b/lib/lp/registry/browser/distroseriesdifferencecomment.py
index 5bb0c86..f72b0a6 100644
--- a/lib/lp/registry/browser/distroseriesdifferencecomment.py
+++ b/lib/lp/registry/browser/distroseriesdifferencecomment.py
@@ -20,4 +20,4 @@ class DistroSeriesDifferenceCommentView(LaunchpadView):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
error_persona = getUtility(ILaunchpadCelebrities).janitor
- self.is_error = (self.context.comment_author == error_persona)
+ self.is_error = self.context.comment_author == error_persona
diff --git a/lib/lp/registry/browser/driver.py b/lib/lp/registry/browser/driver.py
index 5225d10..dd78c89 100644
--- a/lib/lp/registry/browser/driver.py
+++ b/lib/lp/registry/browser/driver.py
@@ -8,10 +8,7 @@ __all__ = ["AppointDriverView"]
from zope.interface import providedBy
from zope.security.proxy import removeSecurityProxy
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadEditFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadEditFormView, action
from lp.registry.interfaces.productseries import IProductSeries
from lp.registry.interfaces.role import IHasAppointedDriver
from lp.services.webapp.publisher import canonical_url
@@ -20,15 +17,16 @@ from lp.services.webapp.publisher import canonical_url
class AppointDriverView(LaunchpadEditFormView):
"""Browser view for appointing a driver to an object."""
- field_names = ['driver']
+ field_names = ["driver"]
@property
def schema(self):
"""Return the schema that is the most specific extension of
IHasAppointedDriver
"""
- assert IHasAppointedDriver.providedBy(self.context), (
- "context should provide IHasAppointedDriver.")
+ assert IHasAppointedDriver.providedBy(
+ self.context
+ ), "context should provide IHasAppointedDriver."
for interface in providedBy(self.context):
if interface.isOrExtends(IHasAppointedDriver):
# XXX matsubara 2007-02-13 bug=84940:
@@ -46,25 +44,29 @@ class AppointDriverView(LaunchpadEditFormView):
if IProductSeries.providedBy(self.context):
return "release manager"
else:
- return 'driver'
+ return "driver"
@property
def page_title(self):
- return 'Appoint the %s for %s' % (
- self.driver_title, self.context.title)
+ return "Appoint the %s for %s" % (
+ self.driver_title,
+ self.context.title,
+ )
- @action('Change', name='change')
+ @action("Change", name="change")
def change_action(self, action, data):
"""Change the driver."""
- driver = data['driver']
+ driver = data["driver"]
self.updateContextFromData(data)
if driver:
self.request.response.addNotification(
- "Successfully changed the %s to %s" % (
- self.driver_title, driver.displayname))
+ "Successfully changed the %s to %s"
+ % (self.driver_title, driver.displayname)
+ )
else:
self.request.response.addNotification(
- "Successfully removed the %s" % self.driver_title)
+ "Successfully removed the %s" % self.driver_title
+ )
@property
def next_url(self):
diff --git a/lib/lp/registry/browser/featuredproject.py b/lib/lp/registry/browser/featuredproject.py
index 214eede..4152919 100644
--- a/lib/lp/registry/browser/featuredproject.py
+++ b/lib/lp/registry/browser/featuredproject.py
@@ -4,21 +4,15 @@
"""Featured Project views."""
__all__ = [
- 'FeaturedProjectsView',
- ]
+ "FeaturedProjectsView",
+]
from zope.component import getUtility
from zope.interface import Interface
-from zope.schema import (
- Choice,
- Set,
- )
+from zope.schema import Choice, Set
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget
from lp.registry.interfaces.pillar import IPillarNameSet
from lp.services.webapp import canonical_url
@@ -30,42 +24,47 @@ class FeaturedProjectForm(Interface):
add = Choice(
title=_("Add project"),
description=_(
- "Choose a project to feature on the Launchpad home page."),
- required=False, vocabulary='DistributionOrProductOrProjectGroup')
+ "Choose a project to feature on the Launchpad home page."
+ ),
+ required=False,
+ vocabulary="DistributionOrProductOrProjectGroup",
+ )
remove = Set(
- title='Remove projects',
+ title="Remove projects",
description=_(
- 'Select projects that you would like to remove from the list.'),
+ "Select projects that you would like to remove from the list."
+ ),
required=False,
- value_type=Choice(vocabulary="FeaturedProject"))
+ value_type=Choice(vocabulary="FeaturedProject"),
+ )
class FeaturedProjectsView(LaunchpadFormView):
"""A view for adding and removing featured projects."""
- label = 'Manage featured projects in Launchpad'
+ label = "Manage featured projects in Launchpad"
page_title = label
schema = FeaturedProjectForm
custom_widget_remove = LabeledMultiCheckBoxWidget
- @action(_('Update featured project list'), name='update')
+ @action(_("Update featured project list"), name="update")
def update_action(self, action, data):
"""Add and remove featured projects."""
- add = data.get('add')
+ add = data.get("add")
if add is not None:
getUtility(IPillarNameSet).add_featured_project(add)
- remove = data.get('remove')
+ remove = data.get("remove")
if remove is not None:
for project in remove:
getUtility(IPillarNameSet).remove_featured_project(project)
self.next_url = canonical_url(self.context)
- @action(_("Cancel"), name="cancel", validator='validate_cancel')
+ @action(_("Cancel"), name="cancel", validator="validate_cancel")
def action_cancel(self, action, data):
self.next_url = canonical_url(self.context)
diff --git a/lib/lp/registry/browser/karma.py b/lib/lp/registry/browser/karma.py
index c0be894..19daccc 100644
--- a/lib/lp/registry/browser/karma.py
+++ b/lib/lp/registry/browser/karma.py
@@ -2,35 +2,25 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'KarmaActionEditView',
- 'KarmaActionSetNavigation',
- 'KarmaContextTopContributorsView',
- ]
+ "KarmaActionEditView",
+ "KarmaActionSetNavigation",
+ "KarmaContextTopContributorsView",
+]
from operator import attrgetter
from zope.component import getUtility
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadEditFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadEditFormView, action
from lp.registry.interfaces.distribution import IDistribution
-from lp.registry.interfaces.karma import (
- IKarmaAction,
- IKarmaActionSet,
- )
+from lp.registry.interfaces.karma import IKarmaAction, IKarmaActionSet
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.projectgroup import IProjectGroup
from lp.services.propertycache import cachedproperty
-from lp.services.webapp import (
- canonical_url,
- Navigation,
- )
+from lp.services.webapp import Navigation, canonical_url
from lp.services.webapp.publisher import LaunchpadView
-
TOP_CONTRIBUTORS_LIMIT = 20
@@ -45,7 +35,7 @@ class KarmaActionSetNavigation(Navigation):
class KarmaActionView(LaunchpadView):
"""View class for the index of karma actions."""
- page_title = 'Actions that give people karma'
+ page_title = "Actions that give people karma"
class KarmaActionEditView(LaunchpadEditFormView):
@@ -56,7 +46,7 @@ class KarmaActionEditView(LaunchpadEditFormView):
@property
def label(self):
"""See `LaunchpadFormView`."""
- return 'Edit %s karma action' % self.context.title
+ return "Edit %s karma action" % self.context.title
@property
def page_title(self):
@@ -75,7 +65,6 @@ class KarmaActionEditView(LaunchpadEditFormView):
class KarmaContextContributor:
-
def __init__(self, person, karmavalue):
self.person = person
self.karmavalue = karmavalue
@@ -91,22 +80,24 @@ class KarmaContextTopContributorsView(LaunchpadView):
def initialize(self):
context = self.context
if IProduct.providedBy(context):
- self.context_name = 'Project'
+ self.context_name = "Project"
elif IDistribution.providedBy(context):
- self.context_name = 'Distribution'
+ self.context_name = "Distribution"
elif IProjectGroup.providedBy(context):
- self.context_name = 'Project Group'
+ self.context_name = "Project Group"
else:
raise AssertionError(
"Context is not a Product, Project group or Distribution: %r"
- % context)
+ % context
+ )
def _getTopContributorsWithLimit(self, limit=None):
results = self.context.getTopContributors(limit=limit)
- contributors = [KarmaContextContributor(person, karmavalue)
- for person, karmavalue in results]
- return sorted(
- contributors, key=attrgetter('karmavalue'), reverse=True)
+ contributors = [
+ KarmaContextContributor(person, karmavalue)
+ for person, karmavalue in results
+ ]
+ return sorted(contributors, key=attrgetter("karmavalue"), reverse=True)
def getTopContributors(self):
return self._getTopContributorsWithLimit(limit=TOP_CONTRIBUTORS_LIMIT)
@@ -118,14 +109,14 @@ class KarmaContextTopContributorsView(LaunchpadView):
def top_contributors_by_category(self):
contributors_by_category = {}
limit = TOP_CONTRIBUTORS_LIMIT
- results = self.context.getTopContributorsGroupedByCategory(
- limit=limit)
+ results = self.context.getTopContributorsGroupedByCategory(limit=limit)
for category, people_and_karma in results.items():
contributors = []
for person, karmavalue in people_and_karma:
- contributors.append(KarmaContextContributor(
- person, karmavalue))
- contributors.sort(key=attrgetter('karmavalue'), reverse=True)
+ contributors.append(
+ KarmaContextContributor(person, karmavalue)
+ )
+ contributors.sort(key=attrgetter("karmavalue"), reverse=True)
contributors_by_category[category.title] = contributors
return contributors_by_category
diff --git a/lib/lp/registry/browser/mailinglists.py b/lib/lp/registry/browser/mailinglists.py
index be1e872..769e7be 100644
--- a/lib/lp/registry/browser/mailinglists.py
+++ b/lib/lp/registry/browser/mailinglists.py
@@ -4,9 +4,9 @@
"""Browser views for handling mailing lists."""
__all__ = [
- 'HeldMessageView',
- 'enabled_with_active_mailing_list',
- ]
+ "HeldMessageView",
+ "enabled_with_active_mailing_list",
+]
from textwrap import TextWrapper
@@ -18,7 +18,7 @@ from lp.app.browser.tales import PersonFormatterAPI
from lp.registry.interfaces.mailinglist import (
IHeldMessageDetails,
IMailingListSet,
- )
+)
from lp.registry.interfaces.person import ITeam
from lp.services.webapp import LaunchpadView
from lp.services.webapp.escaping import html_escape
@@ -40,7 +40,7 @@ class HeldMessageView(LaunchpadView):
self.message_id = self.details.message_id
self.subject = self.details.subject
self.date = self.details.date
- self.widget_name = 'field.' + quote(self.message_id)
+ self.widget_name = "field." + quote(self.message_id)
self.author = PersonFormatterAPI(self.details.author).link(None)
def initialize(self):
@@ -74,16 +74,16 @@ class HeldMessageView(LaunchpadView):
else:
current_paragraph.append(line)
self._append_paragraph(paragraphs, current_paragraph)
- self.body_details = ''.join(paragraphs)
+ self.body_details = "".join(paragraphs)
def _append_paragraph(self, paragraphs, current_paragraph):
if len(current_paragraph) == 0:
# There is nothing to append. The message has multiple
# blank lines.
return
- paragraphs.append('\n<p>\n')
- paragraphs.append('\n'.join(current_paragraph))
- paragraphs.append('\n</p>\n')
+ paragraphs.append("\n<p>\n")
+ paragraphs.append("\n".join(current_paragraph))
+ paragraphs.append("\n</p>\n")
def _remove_leading_blank_lines(self):
"""Strip off any leading blank lines.
@@ -113,13 +113,13 @@ class HeldMessageView(LaunchpadView):
"""
# If there are no non-blank lines, then we're done.
if len(text_lines) == 0:
- self.body_summary = ''
- return ''
+ self.body_summary = ""
+ return ""
# If the first line is of a completely arbitrarily chosen reasonable
# length, then we'll just use that as the summary.
elif len(text_lines[0]) < 60:
self.body_summary = text_lines[0]
- return '\n'.join(text_lines[1:])
+ return "\n".join(text_lines[1:])
# It could be the case that the text is actually flowed using RFC
# 3676 format="flowed" parameters. In that case, just split the line
# at the first whitespace after, again, our arbitrarily chosen limit.
@@ -128,8 +128,8 @@ class HeldMessageView(LaunchpadView):
wrapper = TextWrapper(width=60)
filled_lines = wrapper.fill(first_line).splitlines()
self.body_summary = filled_lines[0]
- text_lines.insert(0, ''.join(filled_lines[1:]))
- return '\n'.join(text_lines)
+ text_lines.insert(0, "".join(filled_lines[1:]))
+ return "\n".join(text_lines)
class enabled_with_active_mailing_list:
@@ -139,8 +139,7 @@ class enabled_with_active_mailing_list:
self._function = function
def __get__(self, obj, type=None):
- """Called by the decorator machinery to return a decorated function.
- """
+ """Called by the decorator machinery to return a decorated function."""
def enable_if_active(*args, **kws):
link = self._function(obj, *args, **kws)
@@ -150,4 +149,5 @@ class enabled_with_active_mailing_list:
if mailing_list is None or not mailing_list.is_usable:
link.enabled = False
return link
+
return enable_if_active
diff --git a/lib/lp/registry/browser/menu.py b/lib/lp/registry/browser/menu.py
index ad03092..8ec0b00 100644
--- a/lib/lp/registry/browser/menu.py
+++ b/lib/lp/registry/browser/menu.py
@@ -4,73 +4,73 @@
"""Shared menus."""
__all__ = [
- 'IRegistryCollectionNavigationMenu',
- 'RegistryCollectionActionMenuBase',
- 'RegistryCollectionNavigationMenu',
- 'TopLevelMenuMixin',
- ]
+ "IRegistryCollectionNavigationMenu",
+ "RegistryCollectionActionMenuBase",
+ "RegistryCollectionNavigationMenu",
+ "TopLevelMenuMixin",
+]
from zope.interface import Interface
from lp.services.webapp.menu import (
- enabled_with_permission,
Link,
NavigationMenu,
- )
+ enabled_with_permission,
+)
class TopLevelMenuMixin:
"""Menu shared by top level collection objects."""
def projects(self):
- return Link('/projects/', 'View projects', icon='info')
+ return Link("/projects/", "View projects", icon="info")
def distributions(self):
- return Link('/distros/', 'View distributions', icon='info')
+ return Link("/distros/", "View distributions", icon="info")
def people(self):
- return Link('/people/', 'View people', icon='info')
+ return Link("/people/", "View people", icon="info")
def meetings(self):
- return Link('/sprints/', 'View meetings', icon='info')
+ return Link("/sprints/", "View meetings", icon="info")
def project_groups(self):
- return Link('/projectgroups', 'View project groups', icon='info')
+ return Link("/projectgroups", "View project groups", icon="info")
def register_project(self):
- text = 'Register a project'
- return Link('/projects/+new', text, icon='add')
+ text = "Register a project"
+ return Link("/projects/+new", text, icon="add")
def register_team(self):
- text = 'Register a team'
- return Link('/people/+newteam', text, icon='add')
+ text = "Register a team"
+ return Link("/people/+newteam", text, icon="add")
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def register_distribution(self):
- text = 'Register a distribution'
- return Link('/distros/+add', text, icon='add')
+ text = "Register a distribution"
+ return Link("/distros/+add", text, icon="add")
def create_account(self):
- text = 'Create an account'
+ text = "Create an account"
# Only enable this link for anonymous users.
enabled = self.user is None
- return Link('/people/+login', text, icon='add', enabled=enabled)
+ return Link("/people/+login", text, icon="add", enabled=enabled)
- @enabled_with_permission('launchpad.View')
+ @enabled_with_permission("launchpad.View")
def request_merge(self):
- text = 'Request a merge'
- return Link('/people/+requestmerge', text, icon='edit')
+ text = "Request a merge"
+ return Link("/people/+requestmerge", text, icon="edit")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def admin_merge_people(self):
- text = 'Merge people'
- return Link('/people/+adminpeoplemerge', text, icon='edit')
+ text = "Merge people"
+ return Link("/people/+adminpeoplemerge", text, icon="edit")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def admin_merge_teams(self):
- text = 'Merge teams'
- return Link('/people/+adminteammerge', text, icon='edit')
+ text = "Merge teams"
+ return Link("/people/+adminteammerge", text, icon="edit")
class IRegistryCollectionNavigationMenu(Interface):
@@ -81,15 +81,15 @@ class RegistryCollectionNavigationMenu(NavigationMenu, TopLevelMenuMixin):
"""Navigation menu for top level registry collections."""
usedfor = IRegistryCollectionNavigationMenu
- facet = 'overview'
+ facet = "overview"
links = [
- 'projects',
- 'project_groups',
- 'distributions',
- 'people',
- 'meetings',
- ]
+ "projects",
+ "project_groups",
+ "distributions",
+ "people",
+ "meetings",
+ ]
class RegistryCollectionActionMenuBase(NavigationMenu, TopLevelMenuMixin):
@@ -102,4 +102,5 @@ class RegistryCollectionActionMenuBase(NavigationMenu, TopLevelMenuMixin):
You should also set the `links` attribute to get just the menu items you
want for the collection's overview page.
"""
- facet = 'overview'
+
+ facet = "overview"
diff --git a/lib/lp/registry/browser/milestone.py b/lib/lp/registry/browser/milestone.py
index 865b7f9..e6926a3 100644
--- a/lib/lp/registry/browser/milestone.py
+++ b/lib/lp/registry/browser/milestone.py
@@ -4,56 +4,50 @@
"""Milestone views."""
__all__ = [
- 'ISearchMilestoneTagsForm',
- 'MilestoneAddView',
- 'MilestoneBreadcrumb',
- 'MilestoneContextMenu',
- 'MilestoneDeleteView',
- 'MilestoneEditView',
- 'MilestoneInlineNavigationMenu',
- 'MilestoneNavigation',
- 'MilestoneOverviewNavigationMenu',
- 'MilestoneSetNavigation',
- 'MilestoneTagView',
- 'MilestoneWithoutCountsView',
- 'MilestoneView',
- 'MilestoneViewMixin',
- 'ObjectMilestonesView',
- ]
+ "ISearchMilestoneTagsForm",
+ "MilestoneAddView",
+ "MilestoneBreadcrumb",
+ "MilestoneContextMenu",
+ "MilestoneDeleteView",
+ "MilestoneEditView",
+ "MilestoneInlineNavigationMenu",
+ "MilestoneNavigation",
+ "MilestoneOverviewNavigationMenu",
+ "MilestoneSetNavigation",
+ "MilestoneTagView",
+ "MilestoneWithoutCountsView",
+ "MilestoneView",
+ "MilestoneViewMixin",
+ "ObjectMilestonesView",
+]
from zope.component import getUtility
from zope.formlib import form
-from zope.interface import (
- implementer,
- Interface,
- )
-from zope.schema import (
- Choice,
- TextLine,
- )
+from zope.interface import Interface, implementer
+from zope.schema import Choice, TextLine
from lp import _
from lp.app.browser.informationtype import InformationTypePortletMixin
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
+ action,
safe_action,
- )
+)
from lp.app.widgets.date import DateWidget
from lp.bugs.browser.buglisting import BugTaskListingItem
from lp.bugs.browser.structuralsubscription import (
- expose_structural_subscription_data_to_js,
StructuralSubscriptionMenuMixin,
StructuralSubscriptionTargetTraversalMixin,
- )
+ expose_structural_subscription_data_to_js,
+)
from lp.bugs.interfaces.bugtask import IBugTaskSet
from lp.registry.browser import (
+ RegistryDeleteViewMixin,
add_subscribe_link,
get_status_counts,
- RegistryDeleteViewMixin,
- )
+)
from lp.registry.browser.product import ProductDownloadFileMixin
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.milestone import (
@@ -62,22 +56,22 @@ from lp.registry.interfaces.milestone import (
IMilestoneData,
IMilestoneSet,
IProjectGroupMilestone,
- )
+)
from lp.registry.interfaces.milestonetag import IProjectGroupMilestoneTag
from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.product import IProduct
from lp.registry.model.milestonetag import (
ProjectGroupMilestoneTag,
validate_tags,
- )
+)
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
- enabled_with_permission,
GetitemNavigation,
LaunchpadView,
Navigation,
- )
+ canonical_url,
+ enabled_with_permission,
+)
from lp.services.webapp.authorization import precache_permission_for_objects
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.menu import (
@@ -85,17 +79,20 @@ from lp.services.webapp.menu import (
ContextMenu,
Link,
NavigationMenu,
- )
+)
class MilestoneSetNavigation(GetitemNavigation):
"""The navigation to traverse to milestones."""
+
usedfor = IMilestoneSet
-class MilestoneNavigation(Navigation,
- StructuralSubscriptionTargetTraversalMixin):
+class MilestoneNavigation(
+ Navigation, StructuralSubscriptionTargetTraversalMixin
+):
"""The navigation to traverse to a milestone."""
+
usedfor = IMilestoneData
@@ -105,7 +102,7 @@ class MilestoneBreadcrumb(Breadcrumb):
@property
def text(self):
milestone = IMilestoneData(self.context)
- if hasattr(milestone, 'code_name') and milestone.code_name:
+ if hasattr(milestone, "code_name") and milestone.code_name:
return '%s "%s"' % (milestone.name, milestone.code_name)
else:
return milestone.name
@@ -114,72 +111,81 @@ class MilestoneBreadcrumb(Breadcrumb):
class MilestoneLinkMixin(StructuralSubscriptionMenuMixin):
"""The menu for this milestone."""
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
"""The link to edit this milestone."""
- text = 'Change details'
+ text = "Change details"
# ProjectMilestones are virtual milestones and do not have
# any properties which can be edited.
enabled = not IProjectGroupMilestone.providedBy(self.context)
summary = "Edit this milestone"
return Link(
- '+edit', text, icon='edit', summary=summary, enabled=enabled)
+ "+edit", text, icon="edit", summary=summary, enabled=enabled
+ )
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def create_release(self):
"""The link to create a release for this milestone."""
- text = 'Create release'
- summary = 'Create a release from this milestone'
+ text = "Create release"
+ summary = "Create a release from this milestone"
# Releases only exist for products.
# A milestone can only have a single product release.
- enabled = (IProduct.providedBy(self.context.target)
- and self.context.product_release is None)
- return Link(
- '+addrelease', text, summary, icon='add', enabled=enabled)
+ enabled = (
+ IProduct.providedBy(self.context.target)
+ and self.context.product_release is None
+ )
+ return Link("+addrelease", text, summary, icon="add", enabled=enabled)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def delete(self):
"""The link to delete this milestone."""
- text = 'Delete milestone'
+ text = "Delete milestone"
# ProjectMilestones are virtual.
enabled = not IProjectGroupMilestone.providedBy(self.context)
summary = "Delete milestone"
return Link(
- '+delete', text, icon='trash-icon',
- summary=summary, enabled=enabled)
+ "+delete",
+ text,
+ icon="trash-icon",
+ summary=summary,
+ enabled=enabled,
+ )
class MilestoneContextMenu(ContextMenu, MilestoneLinkMixin):
"""The menu for this milestone."""
+
usedfor = IMilestoneData
@cachedproperty
def links(self):
- links = ['edit']
+ links = ["edit"]
add_subscribe_link(links)
- links.append('create_release')
+ links.append("create_release")
return links
class MilestoneOverviewNavigationMenu(NavigationMenu, MilestoneLinkMixin):
"""Overview navigation menu for `IAbstractMilestone` objects."""
+
usedfor = IAbstractMilestone
- facet = 'overview'
+ facet = "overview"
@cachedproperty
def links(self):
- links = ['edit', 'delete']
+ links = ["edit", "delete"]
add_subscribe_link(links)
return links
class MilestoneOverviewMenu(ApplicationMenu, MilestoneLinkMixin):
"""Overview menus for `IMilestone` objects."""
+
# This menu must not contain 'subscribe' because the link state is too
# costly to calculate when this menu is used with a list of milestones.
usedfor = IMilestoneData
- facet = 'overview'
- links = ('edit', 'create_release')
+ facet = "overview"
+ links = ("edit", "create_release")
class IMilestoneInline(Interface):
@@ -188,9 +194,10 @@ class IMilestoneInline(Interface):
class MilestoneInlineNavigationMenu(NavigationMenu, MilestoneLinkMixin):
"""An inline navigation menus for milestone views."""
+
usedfor = IMilestoneInline
- facet = 'overview'
- links = ('edit', )
+ facet = "overview"
+ links = ("edit",)
class MilestoneViewMixin:
@@ -224,22 +231,29 @@ class MilestoneViewMixin:
# NB: this is in principle unneeded due to injection of permission in
# the model layer now.
precache_permission_for_objects(
- self.request, 'launchpad.View', non_conjoined_replicas)
+ self.request, "launchpad.View", non_conjoined_replicas
+ )
precache_permission_for_objects(
- self.request, 'launchpad.View',
- [task.bug for task in non_conjoined_replicas])
+ self.request,
+ "launchpad.View",
+ [task.bug for task in non_conjoined_replicas],
+ )
# We want the assignees loaded as we show them in the milestone home
# page.
- list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
- [bug.assignee_id for bug in non_conjoined_replicas],
- need_validity=True))
+ list(
+ getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ [bug.assignee_id for bug in non_conjoined_replicas],
+ need_validity=True,
+ )
+ )
return non_conjoined_replicas
@cachedproperty
def _bug_badge_properties(self):
"""The badges for each bug associates with this milestone."""
return getUtility(IBugTaskSet).getBugTaskBadgeProperties(
- self._bugtasks)
+ self._bugtasks
+ )
@cachedproperty
def _bug_task_tags(self):
@@ -256,9 +270,13 @@ class MilestoneViewMixin:
tags = self._bug_task_tags.get(bugtask.id, ())
people = self._bug_task_people
return BugTaskListingItem(
- bugtask, badge_property['has_branch'],
- badge_property['has_specification'], badge_property['has_patch'],
- tags, people)
+ bugtask,
+ badge_property["has_branch"],
+ badge_property["has_specification"],
+ badge_property["has_patch"],
+ tags,
+ people,
+ )
@cachedproperty
def bugtasks(self):
@@ -270,35 +288,36 @@ class MilestoneViewMixin:
"""The formatted count of bugs for this milestone."""
count = len(self.bugtasks)
if count == 1:
- return '1 bug'
+ return "1 bug"
else:
- return '%d bugs' % count
+ return "%d bugs" % count
@property
def bugtask_status_counts(self):
"""A list StatusCounts summarising the targeted bugtasks."""
- return get_status_counts(self.bugtasks, 'status')
+ return get_status_counts(self.bugtasks, "status")
@property
def specification_count_text(self):
"""The formatted count of specifications for this milestone."""
count = len(self.specifications)
if count == 1:
- return '1 blueprint'
+ return "1 blueprint"
else:
- return '%d blueprints' % count
+ return "%d blueprints" % count
@property
def specification_status_counts(self):
"""A list StatusCounts summarising the targeted specification."""
- return get_status_counts(self.specifications, 'implementation_status')
+ return get_status_counts(self.specifications, "implementation_status")
@cachedproperty
def assignment_counts(self):
"""The counts of the items assigned to users."""
all_assignments = self.bugtasks + self.specifications
return get_status_counts(
- all_assignments, 'assignee', key='displayname')
+ all_assignments, "assignee", key="displayname"
+ )
@cachedproperty
def user_counts(self):
@@ -306,20 +325,22 @@ class MilestoneViewMixin:
all_assignments = []
if self.user:
for status_count in get_status_counts(
- self.specifications, 'assignee', key='displayname'):
+ self.specifications, "assignee", key="displayname"
+ ):
if status_count.status == self.user:
if status_count.count == 1:
- status_count.status = 'blueprint'
+ status_count.status = "blueprint"
else:
- status_count.status = 'blueprints'
+ status_count.status = "blueprints"
all_assignments.append(status_count)
for status_count in get_status_counts(
- self.bugtasks, 'assignee', key='displayname'):
+ self.bugtasks, "assignee", key="displayname"
+ ):
if status_count.status == self.user:
if status_count.count == 1:
- status_count.status = 'bug'
+ status_count.status = "bug"
else:
- status_count.status = 'bugs'
+ status_count.status = "bugs"
all_assignments.append(status_count)
return all_assignments
return all_assignments
@@ -339,8 +360,9 @@ class MilestoneViewMixin:
Return true, if the current milestone is a project milestone or
a project milestone tag, else return False."""
return (
- IProjectGroupMilestone.providedBy(self.context) or
- self.is_project_milestone_tag)
+ IProjectGroupMilestone.providedBy(self.context)
+ or self.is_project_milestone_tag
+ )
@property
def has_bugs_or_specs(self):
@@ -350,9 +372,13 @@ class MilestoneViewMixin:
@implementer(IMilestoneInline)
class MilestoneView(
- LaunchpadView, MilestoneViewMixin, ProductDownloadFileMixin,
- InformationTypePortletMixin):
+ LaunchpadView,
+ MilestoneViewMixin,
+ ProductDownloadFileMixin,
+ InformationTypePortletMixin,
+):
"""A View for listing milestones and releases."""
+
show_series_context = False
def __init__(self, context, request):
@@ -379,7 +405,8 @@ class MilestoneView(
self.form = self.request.form
self.processDeleteFiles()
expose_structural_subscription_data_to_js(
- self.context, self.request, self.user)
+ self.context, self.request, self.user
+ )
def getReleases(self):
"""See `ProductDownloadFileMixin`."""
@@ -403,7 +430,8 @@ class MilestoneView(
def total_downloads(self):
"""Total downloads of files associated with this milestone."""
return sum(
- file.libraryfile.hits for file in self.product_release_files)
+ file.libraryfile.hits for file in self.product_release_files
+ )
@property
def is_distroseries_milestone(self):
@@ -422,7 +450,6 @@ class MilestoneWithoutCountsView(MilestoneView):
class MilestoneTagBase:
-
def extendFields(self):
"""See `LaunchpadFormView`.
@@ -430,14 +457,18 @@ class MilestoneTagBase:
on the interface.
"""
tag_entry = TextLine(
- __name__='tags', title='Tags', required=False,
- constraint=lambda value: validate_tags(value.split()))
+ __name__="tags",
+ title="Tags",
+ required=False,
+ constraint=lambda value: validate_tags(value.split()),
+ )
self.form_fields += form.Fields(
- tag_entry, render_context=self.render_context)
+ tag_entry, render_context=self.render_context
+ )
# Make an instance attribute to avoid mutating the class attribute.
- self.field_names = getattr(self, '_field_names', self.field_names)[:]
+ self.field_names = getattr(self, "_field_names", self.field_names)[:]
# Insert the tags field before the summary.
- summary_index = self.field_names.index('summary')
+ summary_index = self.field_names.index("summary")
self.field_names.insert(summary_index, tag_entry.__name__)
@@ -445,20 +476,21 @@ class MilestoneAddView(MilestoneTagBase, LaunchpadFormView):
"""A view for creating a new Milestone."""
schema = IMilestone
- field_names = ['name', 'code_name', 'dateexpected', 'summary']
+ field_names = ["name", "code_name", "dateexpected", "summary"]
label = "Register a new milestone"
custom_widget_dateexpected = DateWidget
- @action(_('Register Milestone'), name='register')
+ @action(_("Register Milestone"), name="register")
def register_action(self, action, data):
"""Use the newMilestone method on the context to make a milestone."""
milestone = self.context.newMilestone(
- name=data.get('name'),
- code_name=data.get('code_name'),
- dateexpected=data.get('dateexpected'),
- summary=data.get('summary'))
- tags = data.get('tags')
+ name=data.get("name"),
+ code_name=data.get("code_name"),
+ dateexpected=data.get("dateexpected"),
+ summary=data.get("summary"),
+ )
+ tags = data.get("tags")
if tags:
milestone.setTags(tags.lower().split(), self.user)
self.next_url = canonical_url(self.context)
@@ -501,17 +533,17 @@ class MilestoneEditView(MilestoneTagBase, LaunchpadEditFormView):
its productseries. The distribution milestone may change its
distroseries.
"""
- names = ['name', 'code_name', 'active', 'dateexpected', 'summary']
+ names = ["name", "code_name", "active", "dateexpected", "summary"]
if self.context.product is None:
# This is a distribution milestone.
- names.append('distroseries')
+ names.append("distroseries")
else:
- names.append('productseries')
+ names.append("productseries")
return names
@property
def initial_values(self):
- return {'tags': ' '.join(self.context.getTags())}
+ return {"tags": " ".join(self.context.getTags())}
def setUpFields(self):
"""See `LaunchpadFormView`.
@@ -524,20 +556,22 @@ class MilestoneEditView(MilestoneTagBase, LaunchpadEditFormView):
if self.context.product is None:
# This is a distribution milestone.
choice = Choice(
- __name__='distroseries', vocabulary="FilteredDistroSeries")
+ __name__="distroseries", vocabulary="FilteredDistroSeries"
+ )
else:
choice = Choice(
- __name__='productseries', vocabulary="FilteredProductSeries")
+ __name__="productseries", vocabulary="FilteredProductSeries"
+ )
choice.title = _("Series")
choice.description = _("The series for which this is a milestone.")
field = form.Fields(choice, render_context=self.render_context)
# Remove the schema's field, then add back the replacement field.
self.form_fields = self.form_fields.omit(choice.__name__) + field
- @action(_('Update'), name='update')
+ @action(_("Update"), name="update")
def update_action(self, action, data):
"""Update the milestone."""
- tags = data.pop('tags') or ''
+ tags = data.pop("tags") or ""
self.updateContextFromData(data)
self.context.setTags(tags.lower().split(), self.user)
self.next_url = canonical_url(self.context)
@@ -545,6 +579,7 @@ class MilestoneEditView(MilestoneTagBase, LaunchpadEditFormView):
class MilestoneDeleteView(LaunchpadFormView, RegistryDeleteViewMixin):
"""A view for deleting an `IMilestone`."""
+
schema = IMilestone
field_names = []
@@ -555,7 +590,7 @@ class MilestoneDeleteView(LaunchpadFormView, RegistryDeleteViewMixin):
@property
def label(self):
"""The form label."""
- return 'Delete %s' % self.context.title
+ return "Delete %s" % self.context.title
@cachedproperty
def bugtasks(self):
@@ -577,7 +612,7 @@ class MilestoneDeleteView(LaunchpadFormView, RegistryDeleteViewMixin):
"""The list of `IProductReleaseFile`s related to the milestone."""
return self._getProductReleaseFiles(self.context)
- @action('Delete Milestone', name='delete')
+ @action("Delete Milestone", name="delete")
def delete_action(self, action, data):
"""Delete the milestone anddelete or unlink subordinate objects."""
# Any associated bugtasks and specifications are untargeted.
@@ -585,7 +620,8 @@ class MilestoneDeleteView(LaunchpadFormView, RegistryDeleteViewMixin):
name = self.context.name
self._deleteMilestone(self.context)
self.request.response.addInfoNotification(
- "Milestone %s deleted." % name)
+ "Milestone %s deleted." % name
+ )
self.next_url = canonical_url(series)
@@ -593,15 +629,20 @@ class ISearchMilestoneTagsForm(Interface):
"""Schema for the search milestone tags form."""
tags = TextLine(
- title=_('Search by tags'),
- description=_('Insert space separated tag names'),
- required=True, min_length=2, max_length=64,
- constraint=lambda value: validate_tags(value.split()))
+ title=_("Search by tags"),
+ description=_("Insert space separated tag names"),
+ required=True,
+ min_length=2,
+ max_length=64,
+ constraint=lambda value: validate_tags(value.split()),
+ )
class MilestoneTagView(
- LaunchpadFormView, MilestoneViewMixin, ProductDownloadFileMixin):
+ LaunchpadFormView, MilestoneViewMixin, ProductDownloadFileMixin
+):
"""A View for listing bugtasks and specification for milestone tags."""
+
schema = ISearchMilestoneTagsForm
def __init__(self, context, request):
@@ -617,12 +658,12 @@ class MilestoneTagView(
@property
def initial_values(self):
"""Set the initial value of the search tags field."""
- return {'tags': ' '.join(self.context.tags)}
+ return {"tags": " ".join(self.context.tags)}
@safe_action
- @action('Search Milestone Tags', name='search')
+ @action("Search Milestone Tags", name="search")
def search_by_tags(self, action, data):
- tags = data['tags'].split()
+ tags = data["tags"].split()
milestone_tag = ProjectGroupMilestoneTag(self.context.target, tags)
self.next_url = canonical_url(milestone_tag, request=self.request)
@@ -630,7 +671,7 @@ class MilestoneTagView(
class ObjectMilestonesView(LaunchpadView):
"""A view for listing the milestones for any `IHasMilestones` object"""
- label = 'Milestones'
+ label = "Milestones"
@cachedproperty
def milestones(self):
diff --git a/lib/lp/registry/browser/nameblacklist.py b/lib/lp/registry/browser/nameblacklist.py
index a653c12..2c2e175 100644
--- a/lib/lp/registry/browser/nameblacklist.py
+++ b/lib/lp/registry/browser/nameblacklist.py
@@ -2,45 +2,39 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'NameBlacklistAddView',
- 'NameBlacklistEditView',
- 'NameBlacklistNavigationMenu',
- 'NameBlacklistSetNavigationMenu',
- 'NameBlacklistSetView',
- ]
+ "NameBlacklistAddView",
+ "NameBlacklistEditView",
+ "NameBlacklistNavigationMenu",
+ "NameBlacklistSetNavigationMenu",
+ "NameBlacklistSetView",
+]
import re
-from zope.component import (
- adapter,
- getUtility,
- )
+from zope.component import adapter, getUtility
from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.widgets import TextWidget
from zope.interface import implementer
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.registry.browser import RegistryEditFormView
from lp.registry.interfaces.nameblacklist import (
INameBlacklist,
INameBlacklistSet,
- )
+)
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.interfaces import IBreadcrumb
from lp.services.webapp.menu import (
ApplicationMenu,
- enabled_with_permission,
Link,
NavigationMenu,
- )
+ enabled_with_permission,
+)
from lp.services.webapp.publisher import (
- canonical_url,
LaunchpadView,
Navigation,
- )
+ canonical_url,
+)
class NameBlacklistValidationMixin:
@@ -48,31 +42,32 @@ class NameBlacklistValidationMixin:
def validate(self, data):
"""Validate regular expression."""
- regexp = data['regexp']
+ regexp = data["regexp"]
try:
re.compile(regexp)
name_blacklist_set = getUtility(INameBlacklistSet)
- if (INameBlacklistSet.providedBy(self.context)
- or self.context.regexp != regexp):
+ if (
+ INameBlacklistSet.providedBy(self.context)
+ or self.context.regexp != regexp
+ ):
# Check if the regular expression already exists if a
# new expression is being created or if an existing
# regular expression has been modified.
if name_blacklist_set.getByRegExp(regexp) is not None:
self.setFieldError(
- 'regexp',
- 'This regular expression already exists.')
+ "regexp", "This regular expression already exists."
+ )
except re.error as e:
- self.setFieldError(
- 'regexp',
- 'Invalid regular expression: %s' % e)
+ self.setFieldError("regexp", "Invalid regular expression: %s" % e)
-class NameBlacklistEditView(NameBlacklistValidationMixin,
- RegistryEditFormView):
+class NameBlacklistEditView(
+ NameBlacklistValidationMixin, RegistryEditFormView
+):
"""View for editing a blacklist expression."""
schema = INameBlacklist
- field_names = ['regexp', 'admin', 'comment']
+ field_names = ["regexp", "admin", "comment"]
label = "Edit a blacklist expression"
page_title = label
@@ -87,7 +82,7 @@ class NameBlacklistAddView(NameBlacklistValidationMixin, LaunchpadFormView):
"""View for adding a blacklist expression."""
schema = INameBlacklist
- field_names = ['regexp', 'admin', 'comment']
+ field_names = ["regexp", "admin", "comment"]
label = "Add a new blacklist expression"
page_title = label
@@ -100,24 +95,24 @@ class NameBlacklistAddView(NameBlacklistValidationMixin, LaunchpadFormView):
next_url = cancel_url
- @action("Add to blacklist", name='add')
+ @action("Add to blacklist", name="add")
def add_action(self, action, data):
name_blacklist_set = getUtility(INameBlacklistSet)
name_blacklist_set.create(
- regexp=data['regexp'],
- comment=data['comment'],
- admin=data['admin'],
- )
+ regexp=data["regexp"],
+ comment=data["comment"],
+ admin=data["admin"],
+ )
self.request.response.addInfoNotification(
'Regular expression "%s" has been added to the name blacklist.'
- % data['regexp'])
+ % data["regexp"]
+ )
class NameBlacklistSetView(LaunchpadView):
"""View for /+nameblacklists top level collection."""
- label = (
- 'Blacklist for names of Launchpad pillars and persons')
+ label = "Blacklist for names of Launchpad pillars and persons"
page_title = label
@@ -131,28 +126,30 @@ class NameBlacklistSetNavigation(Navigation):
class NameBlacklistSetNavigationMenu(NavigationMenu):
"""Action menu for NameBlacklistSet."""
+
usedfor = INameBlacklistSet
- facet = 'overview'
+ facet = "overview"
links = [
- 'add_blacklist_expression',
- ]
+ "add_blacklist_expression",
+ ]
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def add_blacklist_expression(self):
- return Link('+add', 'Add blacklist expression', icon='add')
+ return Link("+add", "Add blacklist expression", icon="add")
class NameBlacklistNavigationMenu(ApplicationMenu):
"""Action menu for NameBlacklist."""
+
usedfor = INameBlacklist
- facet = 'overview'
+ facet = "overview"
links = [
- 'edit_blacklist_expression',
- ]
+ "edit_blacklist_expression",
+ ]
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit_blacklist_expression(self):
- return Link('+edit', 'Edit blacklist expression', icon='edit')
+ return Link("+edit", "Edit blacklist expression", icon="edit")
@adapter(INameBlacklistSet)
diff --git a/lib/lp/registry/browser/objectreassignment.py b/lib/lp/registry/browser/objectreassignment.py
index 014f9a2..e9d079d 100644
--- a/lib/lp/registry/browser/objectreassignment.py
+++ b/lib/lp/registry/browser/objectreassignment.py
@@ -12,27 +12,15 @@ __all__ = ["ObjectReassignmentView"]
from zope.component import getUtility
from zope.formlib.form import FormFields
-from zope.formlib.interfaces import (
- ConversionError,
- WidgetInputError,
- )
+from zope.formlib.interfaces import ConversionError, WidgetInputError
from zope.schema import Choice
-from zope.schema.vocabulary import (
- SimpleTerm,
- SimpleVocabulary,
- )
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.app.validators.name import valid_name
from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
-from lp.registry.interfaces.person import (
- IObjectReassignment,
- IPersonSet,
- )
+from lp.registry.interfaces.person import IObjectReassignment, IPersonSet
from lp.services.webapp import canonical_url
@@ -55,8 +43,8 @@ class ObjectReassignmentView(LaunchpadFormView):
contextName property in your subclass.
"""
- ownerOrMaintainerAttr = 'owner'
- ownerOrMaintainerName = 'owner'
+ ownerOrMaintainerAttr = "owner"
+ ownerOrMaintainerName = "owner"
# Called after changing the owner if it is overridden in a subclass.
callback = None
@@ -66,33 +54,42 @@ class ObjectReassignmentView(LaunchpadFormView):
@property
def label(self):
"""The form label."""
- return 'Change the %s of %s' % (
- self.ownerOrMaintainerName, self.contextName)
+ return "Change the %s of %s" % (
+ self.ownerOrMaintainerName,
+ self.contextName,
+ )
page_title = label
def setUpFields(self):
super().setUpFields()
self.form_fields = FormFields(
- self.form_fields, self.auto_create_team_field)
+ self.form_fields, self.auto_create_team_field
+ )
@property
def auto_create_team_field(self):
terms = [
- SimpleTerm('existing', token='existing',
- title='An existing person or team'),
- SimpleTerm('new', token='new',
- title="A new team I'm creating here"),
- ]
+ SimpleTerm(
+ "existing",
+ token="existing",
+ title="An existing person or team",
+ ),
+ SimpleTerm(
+ "new", token="new", title="A new team I'm creating here"
+ ),
+ ]
return Choice(
- __name__='existing',
- title=_('This is'),
+ __name__="existing",
+ title=_("This is"),
source=SimpleVocabulary(terms),
- default='existing',
+ default="existing",
description=_(
- "The new team's name must begin with a lower-case letter "
- "or number, and contain only letters, numbers, dots, hyphens, "
- "or plus signs."))
+ "The new team's name must begin with a lower-case letter "
+ "or number, and contain only letters, numbers, dots, hyphens, "
+ "or plus signs."
+ ),
+ )
@property
def ownerOrMaintainer(self):
@@ -110,12 +107,12 @@ class ObjectReassignmentView(LaunchpadFormView):
@property
def owner_widget(self):
- return self.widgets['owner']
+ return self.widgets["owner"]
@action("Change", name="change")
def changeOwner(self, action, data):
"""Change the owner of self.context to the one choosen by the user."""
- newOwner = data['owner']
+ newOwner = data["owner"]
oldOwner = getattr(self.context, self.ownerOrMaintainerAttr)
setattr(self.context, self.ownerOrMaintainerAttr, newOwner)
if callable(self.callback):
@@ -145,12 +142,13 @@ class ObjectReassignmentView(LaunchpadFormView):
owner_name = request.form.get(self.owner_widget.name)
if not owner_name:
self.setFieldError(
- 'owner',
+ "owner",
"You have to specify the name of the person/team that's "
- "going to be the new %s." % self.ownerOrMaintainerName)
+ "going to be the new %s." % self.ownerOrMaintainerName,
+ )
return None
- if request.form.get('field.existing') == 'existing':
+ if request.form.get("field.existing") == "existing":
try:
# By getting the owner using getInputValue() we make sure
# it's valid according to the vocabulary of self.schema's
@@ -158,35 +156,40 @@ class ObjectReassignmentView(LaunchpadFormView):
owner = self.owner_widget.getInputValue()
except WidgetInputError:
self.setFieldError(
- 'owner',
+ "owner",
"The person/team named '%s' is not a valid owner for %s."
- % (owner_name, self.contextName))
+ % (owner_name, self.contextName),
+ )
return None
except ConversionError:
self.setFieldError(
self.ownerOrMaintainerName,
"There's no person/team named '%s' in Launchpad."
- % owner_name)
+ % owner_name,
+ )
return None
else:
if personset.getByName(owner_name):
self.setFieldError(
- 'owner',
+ "owner",
"There's already a person/team with the name '%s' in "
"Launchpad. Please choose a different name or select "
"the option to make that person/team the new owner, "
- "if that's what you want." % owner_name)
+ "if that's what you want." % owner_name,
+ )
return None
if not valid_name(owner_name):
self.setFieldError(
- 'owner',
+ "owner",
"'%s' is not a valid name for a team. Please make sure "
"it contains only the allowed characters and no spaces."
- % owner_name)
+ % owner_name,
+ )
return None
owner = personset.newTeam(
- self.user, owner_name, owner_name.capitalize())
+ self.user, owner_name, owner_name.capitalize()
+ )
self.validateOwner(owner)
diff --git a/lib/lp/registry/browser/ociproject.py b/lib/lp/registry/browser/ociproject.py
index 00e8024..e5d00c7 100644
--- a/lib/lp/registry/browser/ociproject.py
+++ b/lib/lp/registry/browser/ociproject.py
@@ -4,28 +4,25 @@
"""Views, menus, and traversal related to `OCIProject`s."""
__all__ = [
- 'OCIProjectBreadcrumb',
- 'OCIProjectContextMenu',
- 'OCIProjectFacets',
- 'OCIProjectNavigation',
- 'OCIProjectNavigationMenu',
- 'OCIProjectURL',
- ]
+ "OCIProjectBreadcrumb",
+ "OCIProjectContextMenu",
+ "OCIProjectFacets",
+ "OCIProjectNavigation",
+ "OCIProjectNavigationMenu",
+ "OCIProjectURL",
+]
-from urllib.parse import (
- urlsplit,
- urlunsplit,
- )
+from urllib.parse import urlsplit, urlunsplit
from breezy import urlutils
from zope.component import getUtility
from zope.interface import implementer
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.browser.tales import CustomizableFormatter
from lp.app.errors import NotFoundError
from lp.app.interfaces.headings import IHeadingBreadcrumb
@@ -36,37 +33,37 @@ from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
from lp.registry.enums import DistributionDefaultTraversalPolicy
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.ociproject import (
+ OCI_PROJECT_ALLOW_CREATE,
IOCIProject,
IOCIProjectSet,
- OCI_PROJECT_ALLOW_CREATE,
OCIProjectCreateFeatureDisabled,
- )
+)
from lp.registry.interfaces.ociprojectname import (
IOCIProjectName,
IOCIProjectNameSet,
- )
+)
from lp.registry.interfaces.product import IProduct
from lp.services.config import config
from lp.services.features import getFeatureFlag
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
NavigationMenu,
StandardLaunchpadFacets,
+ canonical_url,
+ enabled_with_permission,
stepthrough,
- )
+)
from lp.services.webapp.authorization import check_permission
from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.interfaces import (
ICanonicalUrlData,
IMultiFacetedBreadcrumb,
- )
+)
@implementer(ICanonicalUrlData)
@@ -91,79 +88,89 @@ class OCIProjectURL:
def path(self):
if self.context.distribution is not None:
policy = self.context.distribution.default_traversal_policy
- if (policy == DistributionDefaultTraversalPolicy.OCI_PROJECT and
- not self.context.distribution.redirect_default_traversal):
+ if (
+ policy == DistributionDefaultTraversalPolicy.OCI_PROJECT
+ and not self.context.distribution.redirect_default_traversal
+ ):
return self.context.name
return "+oci/%s" % self.context.name
def getPillarFieldName(pillar):
if IDistribution.providedBy(pillar):
- return 'distribution'
+ return "distribution"
elif IProduct.providedBy(pillar):
- return 'project'
- raise NotImplementedError("This view only supports distribution or "
- "project as pillars for OCIProject.")
+ return "project"
+ raise NotImplementedError(
+ "This view only supports distribution or "
+ "project as pillars for OCIProject."
+ )
class OCIProjectAddView(LaunchpadFormView):
schema = IOCIProjectName
- field_names = ['name']
+ field_names = ["name"]
def initialize(self):
- if (not getFeatureFlag(OCI_PROJECT_ALLOW_CREATE) and not
- self.context.canAdministerOCIProjects(self.user)):
+ if not getFeatureFlag(
+ OCI_PROJECT_ALLOW_CREATE
+ ) and not self.context.canAdministerOCIProjects(self.user):
raise OCIProjectCreateFeatureDisabled
super().initialize()
@action("Create OCI Project", name="create")
def create_action(self, action, data):
"""Create a new OCI Project."""
- name = data.get('name')
- oci_project_name = getUtility(
- IOCIProjectNameSet).getOrCreateByName(name)
+ name = data.get("name")
+ oci_project_name = getUtility(IOCIProjectNameSet).getOrCreateByName(
+ name
+ )
oci_project = getUtility(IOCIProjectSet).new(
- registrant=self.user,
- pillar=self.context,
- name=oci_project_name)
+ registrant=self.user, pillar=self.context, name=oci_project_name
+ )
self.next_url = canonical_url(oci_project)
def validate(self, data):
super().validate(data)
- name = data.get('name', None)
- oci_project_name = getUtility(
- IOCIProjectNameSet).getOrCreateByName(name)
+ name = data.get("name", None)
+ oci_project_name = getUtility(IOCIProjectNameSet).getOrCreateByName(
+ name
+ )
oci_project = getUtility(IOCIProjectSet).getByPillarAndName(
- self.context, oci_project_name.name)
+ self.context, oci_project_name.name
+ )
if oci_project:
pillar_type = getPillarFieldName(self.context)
- msg = ('There is already an OCI project in %s %s with this name.'
- % (pillar_type, self.context.display_name))
- self.setFieldError('name', msg)
+ msg = (
+ "There is already an OCI project in %s %s with this name."
+ % (pillar_type, self.context.display_name)
+ )
+ self.setFieldError("name", msg)
class OCIProjectFormatterAPI(CustomizableFormatter):
"""Adapt `IOCIProject` objects to a formatted string."""
- _link_summary_template = '%(displayname)s'
+ _link_summary_template = "%(displayname)s"
def _link_summary_values(self):
displayname = self._context.display_name
- return {'displayname': displayname}
+ return {"displayname": displayname}
-class OCIProjectNavigation(TargetDefaultVCSNavigationMixin,
- BugTargetTraversalMixin, Navigation):
+class OCIProjectNavigation(
+ TargetDefaultVCSNavigationMixin, BugTargetTraversalMixin, Navigation
+):
usedfor = IOCIProject
- @stepthrough('+series')
+ @stepthrough("+series")
def traverse_series(self, name):
series = self.context.getSeriesByName(name)
if series is None:
- raise NotFoundError('%s is not a valid series name' % name)
+ raise NotFoundError("%s is not a valid series name" % name)
return series
@@ -173,31 +180,31 @@ class OCIProjectBreadcrumb(Breadcrumb):
@property
def text(self):
- return '%s OCI project' % self.context.name
+ return "%s OCI project" % self.context.name
class OCIProjectFacets(StandardLaunchpadFacets):
usedfor = IOCIProject
enable_only = [
- 'overview',
- 'branches',
- 'bugs',
- ]
+ "overview",
+ "branches",
+ "bugs",
+ ]
def makeLink(self, text, context, view_name, site):
- site = 'mainsite' if self.mainsite_only else site
+ site = "mainsite" if self.mainsite_only else site
target = canonical_url(context, view_name=view_name, rootsite=site)
return Link(target, text, site=site)
def branches(self):
- return self.makeLink('Code', self.context, '+code', 'code')
+ return self.makeLink("Code", self.context, "+code", "code")
def bugs(self):
"""Override bugs link to show the OCIProject's bug page, instead of
the pillar's bug page.
"""
- return self.makeLink('Bugs', self.context, '+bugs', 'bugs')
+ return self.makeLink("Bugs", self.context, "+bugs", "bugs")
class OCIProjectNavigationMenu(NavigationMenu):
@@ -205,23 +212,27 @@ class OCIProjectNavigationMenu(NavigationMenu):
usedfor = IOCIProject
- facet = 'overview'
+ facet = "overview"
- links = ('edit', 'create_recipe', 'view_recipes')
+ links = ("edit", "create_recipe", "view_recipes")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
- return Link('+edit', 'Edit OCI project', icon='edit')
+ return Link("+edit", "Edit OCI project", icon="edit")
- @enabled_with_permission('launchpad.AnyLegitimatePerson')
+ @enabled_with_permission("launchpad.AnyLegitimatePerson")
def create_recipe(self):
- return Link('+new-recipe', 'Create OCI recipe', icon='add')
+ return Link("+new-recipe", "Create OCI recipe", icon="add")
def view_recipes(self):
- enabled = not getUtility(IOCIRecipeSet).findByOCIProject(
- self.context, visible_by_user=self.user).is_empty()
+ enabled = (
+ not getUtility(IOCIRecipeSet)
+ .findByOCIProject(self.context, visible_by_user=self.user)
+ .is_empty()
+ )
return Link(
- '+recipes', 'View all recipes', icon='info', enabled=enabled)
+ "+recipes", "View all recipes", icon="info", enabled=enabled
+ )
class OCIProjectContextMenu(ContextMenu):
@@ -229,19 +240,23 @@ class OCIProjectContextMenu(ContextMenu):
usedfor = IOCIProject
- facet = 'overview'
+ facet = "overview"
- links = ('create_recipe', 'view_recipes')
+ links = ("create_recipe", "view_recipes")
- @enabled_with_permission('launchpad.AnyLegitimatePerson')
+ @enabled_with_permission("launchpad.AnyLegitimatePerson")
def create_recipe(self):
- return Link('+new-recipe', 'Create OCI recipe', icon='add')
+ return Link("+new-recipe", "Create OCI recipe", icon="add")
def view_recipes(self):
- enabled = not getUtility(IOCIRecipeSet).findByOCIProject(
- self.context, visible_by_user=self.user).is_empty()
+ enabled = (
+ not getUtility(IOCIRecipeSet)
+ .findByOCIProject(self.context, visible_by_user=self.user)
+ .is_empty()
+ )
return Link(
- '+recipes', 'View all recipes', icon='info', enabled=enabled)
+ "+recipes", "View all recipes", icon="info", enabled=enabled
+ )
class OCIProjectIndexView(LaunchpadView):
@@ -254,7 +269,9 @@ class OCIProjectIndexView(LaunchpadView):
base_url = urlsplit(
urlutils.join(
config.codehosting.git_ssh_root,
- canonical_url(self.context, force_local_path=True)[1:]))
+ canonical_url(self.context, force_local_path=True)[1:],
+ )
+ )
url = list(base_url)
url[1] = "{}@{}".format(self.user.name, base_url.hostname)
return urlunsplit(url)
@@ -270,12 +287,14 @@ class OCIProjectIndexView(LaunchpadView):
@cachedproperty
def official_recipe_count(self):
return self.context.getOfficialRecipes(
- visible_by_user=self.user).count()
+ visible_by_user=self.user
+ ).count()
@cachedproperty
def other_recipe_count(self):
return self.context.getUnofficialRecipes(
- visible_by_user=self.user).count()
+ visible_by_user=self.user
+ ).count()
class OCIProjectEditView(LaunchpadEditFormView):
@@ -283,8 +302,8 @@ class OCIProjectEditView(LaunchpadEditFormView):
schema = IOCIProject
field_names = [
- 'name',
- ]
+ "name",
+ ]
def setUpFields(self):
pillar_key = getPillarFieldName(self.context.pillar)
@@ -298,25 +317,27 @@ class OCIProjectEditView(LaunchpadEditFormView):
@property
def label(self):
- return 'Edit %s OCI project' % self.context.name
+ return "Edit %s OCI project" % self.context.name
- page_title = 'Edit'
+ page_title = "Edit"
def validate(self, data):
super().validate(data)
pillar_type_field = getPillarFieldName(self.context.pillar)
pillar = data.get(pillar_type_field)
- name = data.get('name')
+ name = data.get("name")
if pillar and name:
oci_project = getUtility(IOCIProjectSet).getByPillarAndName(
- pillar, name)
+ pillar, name
+ )
if oci_project is not None and oci_project != self.context:
self.setFieldError(
- 'name',
- 'There is already an OCI project in %s %s with this name.'
- % (pillar_type_field, pillar.display_name))
+ "name",
+ "There is already an OCI project in %s %s with this name."
+ % (pillar_type_field, pillar.display_name),
+ )
- @action('Update OCI project', name='update')
+ @action("Update OCI project", name="update")
def update_action(self, action, data):
self.updateContextFromData(data)
@@ -329,7 +350,8 @@ class OCIProjectEditView(LaunchpadEditFormView):
class OCIProjectSearchView(LaunchpadView):
"""Page to search for OCI projects of a given pillar."""
- page_title = ''
+
+ page_title = ""
@property
def label(self):
@@ -371,4 +393,5 @@ class OCIProjectSearchView(LaunchpadView):
@property
def search_results(self):
return getUtility(IOCIProjectSet).findByPillarAndName(
- self.context, self.text or '')
+ self.context, self.text or ""
+ )
diff --git a/lib/lp/registry/browser/peoplemerge.py b/lib/lp/registry/browser/peoplemerge.py
index f37f6da..63f7bd9 100644
--- a/lib/lp/registry/browser/peoplemerge.py
+++ b/lib/lp/registry/browser/peoplemerge.py
@@ -4,106 +4,123 @@
"""People Merge related wiew classes."""
__all__ = [
- 'AdminPeopleMergeView',
- 'AdminTeamMergeView',
- 'DeleteTeamView',
- 'FinishedPeopleMergeRequestView',
- 'RequestPeopleMergeMultipleEmailsView',
- 'RequestPeopleMergeView',
- ]
+ "AdminPeopleMergeView",
+ "AdminTeamMergeView",
+ "DeleteTeamView",
+ "FinishedPeopleMergeRequestView",
+ "RequestPeopleMergeMultipleEmailsView",
+ "RequestPeopleMergeView",
+]
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
from lp import _
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.code.interfaces.branchcollection import IAllBranches
from lp.code.interfaces.gitcollection import IAllGitRepositories
-from lp.registry.interfaces.mailinglist import (
- MailingListStatus,
- PURGE_STATES,
- )
+from lp.registry.interfaces.mailinglist import PURGE_STATES, MailingListStatus
from lp.registry.interfaces.person import (
IAdminPeopleMergeSchema,
IAdminTeamMergeSchema,
IPersonSet,
IRequestPeopleMerge,
- )
+)
from lp.services.identity.interfaces.emailaddress import (
EmailAddressStatus,
IEmailAddressSet,
- )
+)
from lp.services.propertycache import cachedproperty
from lp.services.verification.interfaces.authtoken import LoginTokenType
from lp.services.verification.interfaces.logintoken import ILoginTokenSet
-from lp.services.webapp import (
- canonical_url,
- LaunchpadView,
- )
+from lp.services.webapp import LaunchpadView, canonical_url
from lp.services.webapp.interfaces import ILaunchBag
from lp.soyuz.enums import ArchiveStatus
from lp.soyuz.interfaces.archive import IArchiveSet
class ValidatingMergeView(LaunchpadFormView):
-
def validate(self, data):
"""Check that user is not attempting to merge a person into itself."""
- dupe_person = data.get('dupe_person')
- target_person = data.get('target_person') or self.user
+ dupe_person = data.get("dupe_person")
+ target_person = data.get("target_person") or self.user
if dupe_person is None:
self.setFieldError(
- 'dupe_person', 'The duplicate is not a valid person or team.')
+ "dupe_person", "The duplicate is not a valid person or team."
+ )
else:
if dupe_person == target_person:
- self.addError(_("You can't merge ${name} into itself.",
- mapping=dict(name=dupe_person.name)))
+ self.addError(
+ _(
+ "You can't merge ${name} into itself.",
+ mapping=dict(name=dupe_person.name),
+ )
+ )
dupe_person_ppas = getUtility(IArchiveSet).getPPAOwnedByPerson(
- dupe_person, statuses=[ArchiveStatus.ACTIVE,
- ArchiveStatus.DELETING])
+ dupe_person,
+ statuses=[ArchiveStatus.ACTIVE, ArchiveStatus.DELETING],
+ )
if dupe_person_ppas is not None:
- self.addError(_(
- "${name} has a PPA that must be deleted before it "
- "can be merged. It may take ten minutes to remove the "
- "deleted PPA's files.",
- mapping=dict(name=dupe_person.name)))
+ self.addError(
+ _(
+ "${name} has a PPA that must be deleted before it "
+ "can be merged. It may take ten minutes to remove the "
+ "deleted PPA's files.",
+ mapping=dict(name=dupe_person.name),
+ )
+ )
all_branches = getUtility(IAllBranches)
if not all_branches.ownedBy(dupe_person).isPrivate().is_empty():
self.addError(
- _("${name} owns private branches that must be "
- "deleted or transferred to another owner first.",
- mapping=dict(name=dupe_person.name)))
+ _(
+ "${name} owns private branches that must be "
+ "deleted or transferred to another owner first.",
+ mapping=dict(name=dupe_person.name),
+ )
+ )
all_repositories = getUtility(IAllGitRepositories)
- if not all_repositories.ownedBy(
- dupe_person).isPrivate().is_empty():
+ if (
+ not all_repositories.ownedBy(dupe_person)
+ .isPrivate()
+ .is_empty()
+ ):
self.addError(
- _("${name} owns private Git repositories that must be "
- "deleted or transferred to another owner first.",
- mapping=dict(name=dupe_person.name)))
+ _(
+ "${name} owns private Git repositories that must be "
+ "deleted or transferred to another owner first.",
+ mapping=dict(name=dupe_person.name),
+ )
+ )
if dupe_person.isMergePending():
- self.addError(_("${name} is already queued for merging.",
- mapping=dict(name=dupe_person.name)))
+ self.addError(
+ _(
+ "${name} is already queued for merging.",
+ mapping=dict(name=dupe_person.name),
+ )
+ )
if target_person is not None and target_person.isMergePending():
- self.addError(_("${name} is already queued for merging.",
- mapping=dict(name=target_person.name)))
+ self.addError(
+ _(
+ "${name} is already queued for merging.",
+ mapping=dict(name=target_person.name),
+ )
+ )
class AdminMergeBaseView(ValidatingMergeView):
"""Base view for the pages where admins can merge people/teams."""
- page_title = 'Merge Launchpad accounts'
+ page_title = "Merge Launchpad accounts"
# Both subclasses share the same template so we need to define these
# variables (which are used in the template) here rather than on
# subclasses.
should_confirm_email_reassignment = False
should_confirm_member_deactivation = False
merge_message = _(
- 'A merge is queued and is expected to complete in a few minutes.')
+ "A merge is queued and is expected to complete in a few minutes."
+ )
dupe_person_emails = ()
dupe_person = None
@@ -132,8 +149,8 @@ class AdminMergeBaseView(ValidatingMergeView):
instance variable.
"""
emailset = getUtility(IEmailAddressSet)
- self.dupe_person = data['dupe_person']
- self.target_person = data.get('target_person', None)
+ self.dupe_person = data["dupe_person"]
+ self.target_person = data.get("target_person", None)
self.dupe_person_emails = emailset.getByPerson(self.dupe_person)
def doMerge(self, data):
@@ -151,8 +168,12 @@ class AdminMergeBaseView(ValidatingMergeView):
naked_email.personID = self.target_person.id
naked_email.status = EmailAddressStatus.NEW
getUtility(IPersonSet).mergeAsync(
- self.dupe_person, self.target_person, reviewer=self.user,
- delete=self.delete, requester=self.user)
+ self.dupe_person,
+ self.target_person,
+ reviewer=self.user,
+ delete=self.delete,
+ requester=self.user,
+ )
self.request.response.addInfoNotification(self.merge_message)
self.next_url = self.success_url
@@ -170,7 +191,7 @@ class AdminPeopleMergeView(AdminMergeBaseView):
label = "Merge Launchpad people"
schema = IAdminPeopleMergeSchema
- @action('Merge', name='merge')
+ @action("Merge", name="merge")
def merge_action(self, action, data):
"""Merge the two person entries specified in the form.
@@ -187,7 +208,7 @@ class AdminPeopleMergeView(AdminMergeBaseView):
return
self.doMerge(data)
- @action('Reassign Emails and Merge', name='reassign_emails_and_merge')
+ @action("Reassign Emails and Merge", name="reassign_emails_and_merge")
def reassign_emails_and_merge_action(self, action, data):
"""Reassign emails of the person to be merged and merge them."""
self.setUpPeople(data)
@@ -210,7 +231,8 @@ class AdminTeamMergeView(AdminMergeBaseView):
unused_states.append(MailingListStatus.PURGED)
return (
team.mailing_list is not None
- and team.mailing_list.status not in unused_states)
+ and team.mailing_list.status not in unused_states
+ )
@cachedproperty
def registry_experts(self):
@@ -225,15 +247,19 @@ class AdminTeamMergeView(AdminMergeBaseView):
return
super().validate(data)
- dupe_team = data['dupe_person']
+ dupe_team = data["dupe_person"]
# We cannot merge the teams if there is a mailing list on the
# duplicate person, unless that mailing list is purged.
if self.hasMailingList(dupe_team):
- self.addError(_(
- "${name} is associated with a Launchpad mailing list; we "
- "can't merge it.", mapping=dict(name=dupe_team.name)))
-
- @action('Merge', name='merge')
+ self.addError(
+ _(
+ "${name} is associated with a Launchpad mailing list; we "
+ "can't merge it.",
+ mapping=dict(name=dupe_team.name),
+ )
+ )
+
+ @action("Merge", name="merge")
def merge_action(self, action, data):
"""Merge the two team entries specified in the form.
@@ -250,8 +276,9 @@ class AdminTeamMergeView(AdminMergeBaseView):
return
super().doMerge(data)
- @action('Deactivate Members and Merge',
- name='deactivate_members_and_merge')
+ @action(
+ "Deactivate Members and Merge", name="deactivate_members_and_merge"
+ )
def deactivate_members_and_merge_action(self, action, data):
"""Deactivate all members of the team to be merged and merge them."""
self.setUpPeople(data)
@@ -261,22 +288,22 @@ class AdminTeamMergeView(AdminMergeBaseView):
class DeleteTeamView(AdminTeamMergeView):
"""A view that deletes a team by merging it with Registry experts."""
- page_title = 'Delete'
- field_names = ['dupe_person']
- merge_message = _('The team is queued to be deleted.')
+ page_title = "Delete"
+ field_names = ["dupe_person"]
+ merge_message = _("The team is queued to be deleted.")
@property
def label(self):
- return 'Delete %s' % self.context.displayname
+ return "Delete %s" % self.context.displayname
def __init__(self, context, request):
super().__init__(context, request)
- if ('field.dupe_person' in self.request.form):
+ if "field.dupe_person" in self.request.form:
# These fields have fixed values and are managed by this method.
# The user has crafted a request to gain ownership of the dupe
# team's assets.
- self.addError('Unable to process submitted data.')
- elif 'field.actions.delete' in self.request.form:
+ self.addError("Unable to process submitted data.")
+ elif "field.actions.delete" in self.request.form:
# In the case of deleting a team, the form values are always
# the context team, and the registry experts team. These values
# are injected during __init__ because the base classes assume the
@@ -290,9 +317,9 @@ class DeleteTeamView(AdminTeamMergeView):
@property
def default_values(self):
return {
- 'field.dupe_person': self.context.name,
- 'field.delete': True,
- }
+ "field.dupe_person": self.context.name,
+ "field.delete": True,
+ }
@property
def cancel_url(self):
@@ -309,7 +336,7 @@ class DeleteTeamView(AdminTeamMergeView):
def canDelete(self, data):
return not self.has_mailing_list
- @action('Delete', name='delete', condition=canDelete)
+ @action("Delete", name="delete", condition=canDelete)
def merge_action(self, action, data):
self.delete = True
super().deactivate_members_and_merge_action.success(data)
@@ -322,12 +349,12 @@ class FinishedPeopleMergeRequestView(LaunchpadView):
This view is used only when the dupe account has a single email address.
"""
- page_title = 'Merge request sent'
+ page_title = "Merge request sent"
def initialize(self):
user = getUtility(ILaunchBag).user
try:
- dupe_id = int(self.request.get('dupe'))
+ dupe_id = int(self.request.get("dupe"))
except (ValueError, TypeError):
self.request.response.redirect(canonical_url(user))
return
@@ -350,13 +377,13 @@ class FinishedPeopleMergeRequestView(LaunchpadView):
if self.dupe_email:
return LaunchpadView.render(self)
else:
- return ''
+ return ""
class RequestPeopleMergeMultipleEmailsView(LaunchpadView):
"""Merge request view when dupe account has multiple email addresses."""
- label = 'Merge Launchpad accounts'
+ label = "Merge Launchpad accounts"
page_title = label
def __init__(self, context, request):
@@ -366,11 +393,11 @@ class RequestPeopleMergeMultipleEmailsView(LaunchpadView):
self.notified_addresses = []
def processForm(self):
- dupe = self.request.form.get('dupe')
+ dupe = self.request.form.get("dupe")
if dupe is None:
# We just got redirected to this page and we don't have the dupe
# hidden field in request.form.
- dupe = self.request.get('dupe')
+ dupe = self.request.get("dupe")
if dupe is None:
return
@@ -389,8 +416,9 @@ class RequestPeopleMergeMultipleEmailsView(LaunchpadView):
# If the email addresses are hidden we must send a merge request
# to each of them. But first we've got to remove the security
# proxy so we can get to them.
- email_addresses = [removeSecurityProxy(email).email
- for email in self.dupeemails]
+ email_addresses = [
+ removeSecurityProxy(email).email for email in self.dupeemails
+ ]
else:
# Otherwise we send a merge request only to the ones the user
# selected.
@@ -411,13 +439,15 @@ class RequestPeopleMergeMultipleEmailsView(LaunchpadView):
self.request.response.addNotification(
"An address was removed from the duplicate "
"account while you were making this merge "
- "request. Select again.")
+ "request. Select again."
+ )
return
email_addresses.append(emailaddress)
for emailaddress in email_addresses:
token = logintokenset.new(
- self.user, login, emailaddress, LoginTokenType.ACCOUNTMERGE)
+ self.user, login, emailaddress, LoginTokenType.ACCOUNTMERGE
+ )
token.sendMergeRequestEmail()
self.notified_addresses.append(emailaddress)
self.form_processed = True
@@ -443,7 +473,7 @@ class RequestPeopleMergeView(ValidatingMergeView):
of those they want to claim.
"""
- label = 'Merge Launchpad accounts'
+ label = "Merge Launchpad accounts"
page_title = label
schema = IRequestPeopleMerge
@@ -451,9 +481,9 @@ class RequestPeopleMergeView(ValidatingMergeView):
def cancel_url(self):
return canonical_url(getUtility(IPersonSet))
- @action('Continue', name='continue')
+ @action("Continue", name="continue")
def continue_action(self, action, data):
- dupeaccount = data['dupe_person']
+ dupeaccount = data["dupe_person"]
if dupeaccount == self.user:
# Please, don't try to merge you into yourself.
return
@@ -464,7 +494,7 @@ class RequestPeopleMergeView(ValidatingMergeView):
# The dupe account have more than one email address. Must redirect
# the user to another page to ask which of those emails they
# want to claim.
- self.next_url = '+requestmerge-multiple?dupe=%d' % dupeaccount.id
+ self.next_url = "+requestmerge-multiple?dupe=%d" % dupeaccount.id
return
assert emails_count == 1
@@ -474,7 +504,10 @@ class RequestPeopleMergeView(ValidatingMergeView):
# Need to remove the security proxy because the dupe account may have
# hidden email addresses.
token = logintokenset.new(
- self.user, login, removeSecurityProxy(email).email,
- LoginTokenType.ACCOUNTMERGE)
+ self.user,
+ login,
+ removeSecurityProxy(email).email,
+ LoginTokenType.ACCOUNTMERGE,
+ )
token.sendMergeRequestEmail()
- self.next_url = './+mergerequest-sent?dupe=%d' % dupeaccount.id
+ self.next_url = "./+mergerequest-sent?dupe=%d" % dupeaccount.id
diff --git a/lib/lp/registry/browser/person.py b/lib/lp/registry/browser/person.py
index e1c31b3..5faea5e 100644
--- a/lib/lp/registry/browser/person.py
+++ b/lib/lp/registry/browser/person.py
@@ -4,121 +4,88 @@
"""Person-related view classes."""
__all__ = [
- 'BeginTeamClaimView',
- 'CommonMenuLinks',
- 'PersonEditOCIRegistryCredentialsView',
- 'EmailToPersonView',
- 'PeopleSearchView',
- 'PersonAccountAdministerView',
- 'PersonAdministerView',
- 'PersonBrandingView',
- 'PersonBreadcrumb',
- 'PersonCloseAccountView',
- 'PersonCodeOfConductEditView',
- 'PersonDeactivateAccountView',
- 'PersonEditEmailsView',
- 'PersonEditIRCNicknamesView',
- 'PersonEditJabberIDsView',
- 'PersonEditTimeZoneView',
- 'PersonEditSSHKeysView',
- 'PersonEditView',
- 'PersonFacets',
- 'PersonGPGView',
- 'PersonIndexMenu',
- 'PersonIndexView',
- 'PersonKarmaView',
- 'PersonLanguagesView',
- 'PersonLiveFSView',
- 'PersonNavigation',
- 'PersonOAuthTokensView',
- 'PersonOCIRegistryCredentialsView',
- 'PersonOverviewMenu',
- 'PersonOwnedTeamsView',
- 'PersonRdfContentsView',
- 'PersonRdfView',
- 'PersonRelatedSoftwareView',
- 'PersonRenameFormMixin',
- 'PersonSetActionNavigationMenu',
- 'PersonSetContextMenu',
- 'PersonSetNavigation',
- 'PersonView',
- 'PPANavigationMenuMixIn',
- 'RedirectToEditLanguagesView',
- 'RestrictedMembershipsPersonView',
- 'archive_to_person',
- ]
+ "BeginTeamClaimView",
+ "CommonMenuLinks",
+ "PersonEditOCIRegistryCredentialsView",
+ "EmailToPersonView",
+ "PeopleSearchView",
+ "PersonAccountAdministerView",
+ "PersonAdministerView",
+ "PersonBrandingView",
+ "PersonBreadcrumb",
+ "PersonCloseAccountView",
+ "PersonCodeOfConductEditView",
+ "PersonDeactivateAccountView",
+ "PersonEditEmailsView",
+ "PersonEditIRCNicknamesView",
+ "PersonEditJabberIDsView",
+ "PersonEditTimeZoneView",
+ "PersonEditSSHKeysView",
+ "PersonEditView",
+ "PersonFacets",
+ "PersonGPGView",
+ "PersonIndexMenu",
+ "PersonIndexView",
+ "PersonKarmaView",
+ "PersonLanguagesView",
+ "PersonLiveFSView",
+ "PersonNavigation",
+ "PersonOAuthTokensView",
+ "PersonOCIRegistryCredentialsView",
+ "PersonOverviewMenu",
+ "PersonOwnedTeamsView",
+ "PersonRdfContentsView",
+ "PersonRdfView",
+ "PersonRelatedSoftwareView",
+ "PersonRenameFormMixin",
+ "PersonSetActionNavigationMenu",
+ "PersonSetContextMenu",
+ "PersonSetNavigation",
+ "PersonView",
+ "PPANavigationMenuMixIn",
+ "RedirectToEditLanguagesView",
+ "RestrictedMembershipsPersonView",
+ "archive_to_person",
+]
-from datetime import datetime
import itertools
+from datetime import datetime
from itertools import chain
-from operator import (
- attrgetter,
- itemgetter,
- )
+from operator import attrgetter, itemgetter
from textwrap import dedent
-from urllib.parse import (
- quote,
- urlencode,
- )
+from urllib.parse import quote, urlencode
+import pytz
from lazr.config import as_timedelta
from lazr.delegates import delegate_to
from lazr.restful.interface import copy_field
from lazr.restful.interfaces import IWebServiceClientRequest
from lazr.restful.utils import smartquote
from lazr.uri import URI
-import pytz
from storm.zope.interfaces import IResultSet
from zope.browserpage import ViewPageTemplateFile
-from zope.component import (
- adapter,
- getUtility,
- queryMultiAdapter,
- )
+from zope.component import adapter, getUtility, queryMultiAdapter
from zope.formlib.form import FormFields
from zope.formlib.widget import CustomWidgetFactory
-from zope.formlib.widgets import (
- TextAreaWidget,
- TextWidget,
- )
-from zope.interface import (
- classImplements,
- implementer,
- Interface,
- invariant,
- )
+from zope.formlib.widgets import TextAreaWidget, TextWidget
+from zope.interface import Interface, classImplements, implementer, invariant
from zope.interface.exceptions import Invalid
from zope.publisher.interfaces import NotFound
-from zope.schema import (
- Bool,
- Choice,
- Password,
- Text,
- TextLine,
- )
-from zope.schema.vocabulary import (
- SimpleTerm,
- SimpleVocabulary,
- )
+from zope.schema import Bool, Choice, Password, Text, TextLine
+from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
from lp import _
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.app.browser.lazrjs import TextAreaEditorWidget
-from lp.app.browser.tales import (
- DateTimeFormatterAPI,
- PersonFormatterAPI,
- )
-from lp.app.errors import (
- NotFoundError,
- UnexpectedFormData,
- )
+from lp.app.browser.tales import DateTimeFormatterAPI, PersonFormatterAPI
+from lp.app.errors import NotFoundError, UnexpectedFormData
from lp.app.interfaces.headings import IHeadingBreadcrumb
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.validators import LaunchpadValidationError
@@ -128,7 +95,7 @@ from lp.app.widgets.image import ImageChangeWidget
from lp.app.widgets.itemswidgets import (
LaunchpadRadioWidget,
LaunchpadRadioWidgetWithDescription,
- )
+)
from lp.bugs.interfaces.bugsupervisor import IHasBugSupervisor
from lp.bugs.interfaces.bugtask import BugTaskStatus
from lp.bugs.interfaces.bugtasksearch import BugTaskSearchParams
@@ -145,63 +112,50 @@ from lp.oci.interfaces.ociregistrycredentials import (
IOCIRegistryCredentialsSet,
OCIRegistryCredentialsAlreadyExist,
user_can_edit_credentials_for_owner,
- )
+)
from lp.registry.browser import BaseRdfView
from lp.registry.browser.branding import BrandingChangeView
from lp.registry.browser.menu import (
IRegistryCollectionNavigationMenu,
RegistryCollectionActionMenuBase,
TopLevelMenuMixin,
- )
+)
from lp.registry.browser.teamjoin import TeamJoinMixin
from lp.registry.enums import PersonVisibility
from lp.registry.interfaces.codeofconduct import ISignedCodeOfConductSet
from lp.registry.interfaces.distribution import IDistribution
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
- )
+)
from lp.registry.interfaces.gpg import IGPGKeySet
from lp.registry.interfaces.irc import IIrcIDSet
-from lp.registry.interfaces.jabber import (
- IJabberID,
- IJabberIDSet,
- )
+from lp.registry.interfaces.jabber import IJabberID, IJabberIDSet
from lp.registry.interfaces.mailinglist import (
CannotUnsubscribe,
IMailingListSet,
- )
+)
from lp.registry.interfaces.mailinglistsubscription import (
MailingListAutoSubscribePolicy,
- )
+)
from lp.registry.interfaces.ociproject import IOCIProject
-from lp.registry.interfaces.person import (
- IPerson,
- IPersonClaim,
- IPersonSet,
- )
+from lp.registry.interfaces.person import IPerson, IPersonClaim, IPersonSet
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackageFactory,
- )
+)
from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
from lp.registry.interfaces.personproduct import IPersonProductFactory
from lp.registry.interfaces.persontransferjob import (
IPersonCloseAccountJobSource,
IPersonDeactivateJobSource,
- )
+)
from lp.registry.interfaces.pillar import IPillarNameSet
from lp.registry.interfaces.poll import IPollSubset
-from lp.registry.interfaces.product import (
- InvalidProductName,
- IProduct,
- )
-from lp.registry.interfaces.ssh import (
- ISSHKeySet,
- SSHKeyAdditionError,
- )
+from lp.registry.interfaces.product import InvalidProductName, IProduct
+from lp.registry.interfaces.ssh import ISSHKeySet, SSHKeyAdditionError
from lp.registry.interfaces.teammembership import (
ITeamMembershipSet,
TeamMembershipStatus,
- )
+)
from lp.registry.interfaces.wikiname import IWikiNameSet
from lp.registry.mail.notification import send_direct_contact_email
from lp.registry.model.person import get_recipients
@@ -210,67 +164,55 @@ from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.sqlbase import flush_database_updates
from lp.services.feeds.browser import FeedsMixin
from lp.services.geoip.interfaces import IRequestPreferredLanguages
-from lp.services.gpg.interfaces import (
- GPGKeyNotFoundError,
- IGPGHandler,
- )
-from lp.services.identity.interfaces.account import (
- AccountStatus,
- IAccount,
- )
+from lp.services.gpg.interfaces import GPGKeyNotFoundError, IGPGHandler
+from lp.services.identity.interfaces.account import AccountStatus, IAccount
from lp.services.identity.interfaces.emailaddress import (
EmailAddressStatus,
IEmailAddress,
IEmailAddressSet,
- )
+)
from lp.services.mail.interfaces import (
INotificationRecipientSet,
UnknownRecipientError,
- )
+)
from lp.services.messages.interfaces.message import (
IDirectEmailAuthorization,
QuotaReachedError,
- )
+)
from lp.services.oauth.interfaces import IOAuthConsumerSet
from lp.services.openid.adapters.openid import CurrentOpenIDEndPoint
from lp.services.openid.browser.openiddiscovery import (
XRDSContentNegotiationMixin,
- )
+)
from lp.services.openid.interfaces.openid import IOpenIDPersistentIdentity
-from lp.services.propertycache import (
- cachedproperty,
- get_property_cache,
- )
+from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.verification.interfaces.authtoken import LoginTokenType
from lp.services.verification.interfaces.logintoken import ILoginTokenSet
from lp.services.webapp import (
ApplicationMenu,
- canonical_url,
ContextMenu,
- enabled_with_permission,
Link,
Navigation,
NavigationMenu,
StandardLaunchpadFacets,
+ canonical_url,
+ enabled_with_permission,
stepthrough,
stepto,
structured,
- )
+)
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.services.webapp.batching import BatchNavigator
from lp.services.webapp.breadcrumb import DisplaynameBreadcrumb
from lp.services.webapp.interfaces import (
ILaunchBag,
IMultiFacetedBreadcrumb,
IOpenLaunchBag,
- )
-from lp.services.webapp.login import (
- logoutPerson,
- require_fresh_login,
- )
+)
+from lp.services.webapp.login import logoutPerson, require_fresh_login
from lp.services.webapp.menu import get_current_view
from lp.services.webapp.publisher import LaunchpadView
from lp.services.worlddata.interfaces.country import ICountry
@@ -279,7 +221,7 @@ from lp.snappy.browser.hassnaps import HasSnapsMenuMixin
from lp.snappy.interfaces.snap import ISnapSet
from lp.soyuz.browser.archivesubscription import (
traverse_archive_subscription_for_subscriber,
- )
+)
from lp.soyuz.interfaces.archivesubscriber import IArchiveSubscriberSet
from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuildSet
from lp.soyuz.interfaces.livefs import ILiveFSSet
@@ -308,8 +250,11 @@ class RestrictedMembershipsPersonView(LaunchpadView):
# getLatestApprovedMembershipsForPerson which returns a sqlobject
# result set.
membership_list = self.context.getLatestApprovedMembershipsForPerson()
- return [membership for membership in membership_list
- if check_permission('launchpad.View', membership.team)]
+ return [
+ membership
+ for membership in membership_list
+ if check_permission("launchpad.View", membership.team)
+ ]
@property
def teams_with_icons(self):
@@ -321,8 +266,11 @@ class RestrictedMembershipsPersonView(LaunchpadView):
# This method returns a list as opposed to the database object's
# teams_with_icons which returns a sqlobject
# result set.
- return [team for team in self.context.teams_with_icons
- if check_permission('launchpad.View', team)]
+ return [
+ team
+ for team in self.context.teams_with_icons
+ if check_permission("launchpad.View", team)
+ ]
@property
def administrated_teams(self):
@@ -331,8 +279,11 @@ class RestrictedMembershipsPersonView(LaunchpadView):
The user must be an administrator of the team, and the team must
be public.
"""
- return [team for team in self.context.getAdministratedTeams()
- if team.visibility == PersonVisibility.PUBLIC]
+ return [
+ team
+ for team in self.context.getAdministratedTeams()
+ if team.visibility == PersonVisibility.PUBLIC
+ ]
def userCanViewMembership(self):
"""Return true if the user can view a team's membership.
@@ -340,7 +291,7 @@ class RestrictedMembershipsPersonView(LaunchpadView):
Only launchpad admins and team members can view the private
membership. Anyone can view a public team's membership.
"""
- return check_permission('launchpad.View', self.context)
+ return check_permission("launchpad.View", self.context)
class BranchTraversalMixin:
@@ -360,16 +311,17 @@ class BranchTraversalMixin:
base.append(pillar_name)
return itertools.chain(iter(base), iter(self.request.stepstogo))
- @stepto('+branch')
+ @stepto("+branch")
def redirect_branch(self):
"""Redirect to canonical_url."""
branch = getUtility(IBranchNamespaceSet).traverse(self._getSegments())
if branch:
return self.redirectSubTree(
- canonical_url(branch, request=self.request))
+ canonical_url(branch, request=self.request)
+ )
raise NotFoundError
- @stepthrough('+git')
+ @stepthrough("+git")
def traverse_personal_gitrepo(self, name):
# XXX wgrant 2015-06-12: traverse() handles traversal for
# non-personal repos, and works for personal repos except that
@@ -377,7 +329,8 @@ class BranchTraversalMixin:
# view, but stepthroughs match before views and only for
# multi-segment paths, so this is a workable hack.
_, _, repository, _ = getUtility(IGitTraverser).traverse(
- iter(['+git', name]), owner=self.context)
+ iter(["+git", name]), owner=self.context
+ )
return repository
def traverse(self, pillar_name):
@@ -385,14 +338,15 @@ class BranchTraversalMixin:
# Look for a Git repository. We must be careful not to consume
# the traversal stack immediately, as if we fail to find a Git
# repository we will need to look for a Bazaar branch instead.
- segments = (
- [pillar_name] +
- list(reversed(self.request.getTraversalStack())))
+ segments = [pillar_name] + list(
+ reversed(self.request.getTraversalStack())
+ )
num_segments = len(segments)
iter_segments = iter(segments)
traverser = getUtility(IGitTraverser)
_, target, repository, trailing = traverser.traverse(
- iter_segments, owner=self.context)
+ iter_segments, owner=self.context
+ )
if repository is None:
raise NotFoundError
# Subtract one because the pillar has already been traversed.
@@ -417,7 +371,8 @@ class BranchTraversalMixin:
# This repository was accessed through one of its project's
# aliases, so we must redirect to its canonical URL.
return self.redirectSubTree(
- canonical_url(repository, request=self.request))
+ canonical_url(repository, request=self.request)
+ )
return repository
except (NotFoundError, InvalidNamespace, InvalidProductName):
@@ -431,16 +386,20 @@ class BranchTraversalMixin:
pillar = getUtility(IPillarNameSet).getByName(pillar_name)
if IProduct.providedBy(pillar):
person_product = getUtility(IPersonProductFactory).create(
- self.context, pillar)
+ self.context, pillar
+ )
# If accessed through an alias, redirect to the proper name.
if pillar.name != pillar_name:
return self.redirectSubTree(
canonical_url(person_product, request=self.request),
- status=301)
+ status=301,
+ )
getUtility(IOpenLaunchBag).add(pillar)
return person_product
- elif (IDistribution.providedBy(pillar) and
- len(self.request.stepstogo) >= 2):
+ elif (
+ IDistribution.providedBy(pillar)
+ and len(self.request.stepstogo) >= 2
+ ):
if self.request.stepstogo.peek() == "+source":
get_target = IDistribution(pillar).getSourcePackage
factory = getUtility(IPersonDistributionSourcePackageFactory)
@@ -460,14 +419,16 @@ class BranchTraversalMixin:
if pillar.name != pillar_name:
return self.redirectSubTree(
canonical_url(person_target, request=self.request),
- status=301)
+ status=301,
+ )
getUtility(IOpenLaunchBag).add(pillar)
return person_target
# Otherwise look for a branch.
try:
branch = getUtility(IBranchNamespaceSet).traverse(
- self._getSegments(pillar_name))
+ self._getSegments(pillar_name)
+ )
except (NotFoundError, InvalidNamespace):
return super().traverse(pillar_name)
@@ -482,14 +443,16 @@ class BranchTraversalMixin:
# This branch was accessed through one of its project's
# aliases, so we must redirect to its canonical URL.
return self.redirectSubTree(
- canonical_url(branch, request=self.request))
+ canonical_url(branch, request=self.request)
+ )
if branch.distribution is not None:
if branch.distribution.name != pillar_name:
# This branch was accessed through one of its distribution's
# aliases, so we must redirect to its canonical URL.
return self.redirectSubTree(
- canonical_url(branch, request=self.request))
+ canonical_url(branch, request=self.request)
+ )
return branch
@@ -498,20 +461,22 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
usedfor = IPerson
- @stepthrough('+expiringmembership')
+ @stepthrough("+expiringmembership")
def traverse_expiring_membership(self, name):
# Return the found membership regardless of its status as we know
# TeamMembershipSelfRenewalView will tell users why the memembership
# can't be renewed when necessary.
# Circular imports
from lp.registry.browser.team import TeamMembershipSelfRenewalView
+
membership = getUtility(ITeamMembershipSet).getByPersonAndTeam(
- self.context, getUtility(IPersonSet).getByName(name))
+ self.context, getUtility(IPersonSet).getByName(name)
+ )
if membership is None:
return None
return TeamMembershipSelfRenewalView(membership, self.request)
- @stepto('+archive')
+ @stepto("+archive")
def traverse_archive(self):
from lp.soyuz.browser.archive import traverse_named_ppa
@@ -521,8 +486,9 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
# apt-add-repository).
redirect_allowed = not (
IWebServiceClientRequest.providedBy(self.request)
- and self.request.annotations.get(
- self.request.VERSION_ANNOTATION) == '1.0')
+ and self.request.annotations.get(self.request.VERSION_ANNOTATION)
+ == "1.0"
+ )
# There are three cases, in order of preference:
# - 2014 onwards: /~wgrant/+archive/ubuntu/ppa:
@@ -551,12 +517,13 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
self.request.stepstogo.consume()
if redirect:
return self.redirectSubTree(
- canonical_url(ppa, request=self.request))
+ canonical_url(ppa, request=self.request)
+ )
else:
return ppa
return None
- @stepthrough('+email')
+ @stepthrough("+email")
def traverse_email(self, email):
"""Traverse to this person's emails on the webservice layer."""
email = getUtility(IEmailAddressSet).getByEmail(email)
@@ -564,7 +531,7 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
return None
return email
- @stepthrough('+wikiname')
+ @stepthrough("+wikiname")
def traverse_wikiname(self, id):
"""Traverse to this person's WikiNames on the webservice layer."""
wiki = getUtility(IWikiNameSet).get(id)
@@ -572,7 +539,7 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
return None
return wiki
- @stepthrough('+jabberid')
+ @stepthrough("+jabberid")
def traverse_jabberid(self, jabber_id):
"""Traverse to this person's JabberIDs on the webservice layer."""
jabber = getUtility(IJabberIDSet).getByJabberID(jabber_id)
@@ -580,7 +547,7 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
return None
return jabber
- @stepthrough('+ircnick')
+ @stepthrough("+ircnick")
def traverse_ircnick(self, id):
"""Traverse to this person's IrcIDs on the webservice layer."""
irc_nick = getUtility(IIrcIDSet).get(id)
@@ -588,7 +555,7 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
return None
return irc_nick
- @stepthrough('+oci-registry-credential')
+ @stepthrough("+oci-registry-credential")
def traverse_oci_registry_credential(self, id):
"""Traverse to this person's OCI registry credentials."""
oci_credentials = getUtility(IOCIRegistryCredentialsSet).get(id)
@@ -596,7 +563,7 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
return None
return oci_credentials
- @stepto('+archivesubscriptions')
+ @stepto("+archivesubscriptions")
def traverse_archive_subscription(self):
"""Traverse to the archive subscription for this person."""
if self.context.is_team:
@@ -609,19 +576,21 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
if not archive_id.isdigit():
return None
return traverse_archive_subscription_for_subscriber(
- self.context, archive_id)
+ self.context, archive_id
+ )
else:
# Otherwise we return the normal view for a person's
# archive subscriptions.
return queryMultiAdapter(
- (self.context, self.request), name="+archivesubscriptions")
+ (self.context, self.request), name="+archivesubscriptions"
+ )
- @stepthrough('+recipe')
+ @stepthrough("+recipe")
def traverse_recipe(self, name):
"""Traverse to this person's recipes."""
return self.context.getRecipe(name)
- @stepthrough('+livefs')
+ @stepthrough("+livefs")
def traverse_livefs(self, distribution_name):
"""Traverse to this person's live filesystem images."""
if len(self.request.stepstogo) < 2:
@@ -630,8 +599,11 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
distroseries_name = self.request.stepstogo.consume()
livefs_name = self.request.stepstogo.consume()
livefs = getUtility(ILiveFSSet).interpret(
- self.context.name, distribution_name, distroseries_name,
- livefs_name)
+ self.context.name,
+ distribution_name,
+ distroseries_name,
+ livefs_name,
+ )
if livefs is None:
raise NotFoundError
@@ -641,15 +613,17 @@ class PersonNavigation(BranchTraversalMixin, Navigation):
# distribution's aliases, so we must redirect to its canonical
# URL.
return self.redirectSubTree(
- canonical_url(livefs, request=self.request))
+ canonical_url(livefs, request=self.request)
+ )
return livefs
- @stepthrough('+snap')
+ @stepthrough("+snap")
def traverse_snap(self, name):
"""Traverse to this person's snap packages."""
snap = getUtility(ISnapSet).getByPillarAndName(
- self.context, None, name)
+ self.context, None, name
+ )
if snap is None:
raise NotFoundError(name)
return snap
@@ -666,38 +640,47 @@ class PersonSetNavigation(Navigation):
raise NotFoundError(name)
# Redirect to /~name
return self.redirectSubTree(
- canonical_url(person, request=self.request))
+ canonical_url(person, request=self.request)
+ )
- @stepto('+me')
+ @stepto("+me")
def me(self):
me = getUtility(ILaunchBag).user
if me is None:
raise Unauthorized("You need to be logged in to view this URL.")
return self.redirectSubTree(
- canonical_url(me, request=self.request), status=303)
+ canonical_url(me, request=self.request), status=303
+ )
class PersonSetContextMenu(ContextMenu, TopLevelMenuMixin):
usedfor = IPersonSet
- links = ['projects', 'distributions', 'people', 'meetings',
- 'register_team',
- 'adminpeoplemerge', 'adminteammerge', 'mergeaccounts']
+ links = [
+ "projects",
+ "distributions",
+ "people",
+ "meetings",
+ "register_team",
+ "adminpeoplemerge",
+ "adminteammerge",
+ "mergeaccounts",
+ ]
def mergeaccounts(self):
- text = 'Merge accounts'
- return Link('+requestmerge', text, icon='edit')
+ text = "Merge accounts"
+ return Link("+requestmerge", text, icon="edit")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def adminpeoplemerge(self):
- text = 'Admin merge people'
- return Link('+adminpeoplemerge', text, icon='edit')
+ text = "Admin merge people"
+ return Link("+adminpeoplemerge", text, icon="edit")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def adminteammerge(self):
- text = 'Admin merge teams'
- return Link('+adminteammerge', text, icon='edit')
+ text = "Admin merge teams"
+ return Link("+adminteammerge", text, icon="edit")
class PersonFacets(StandardLaunchpadFacets):
@@ -707,252 +690,260 @@ class PersonFacets(StandardLaunchpadFacets):
class CommonMenuLinks:
-
@property
def person(self):
"""Allow subclasses that use the view as the context."""
return self.context
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def activate_ppa(self):
target = "+activate-ppa"
- text = 'Create a new PPA'
- return Link(target, text, icon='add')
+ text = "Create a new PPA"
+ return Link(target, text, icon="add")
def related_software_summary(self):
- target = '+related-packages'
- text = 'Related packages'
- return Link(target, text, icon='info')
+ target = "+related-packages"
+ text = "Related packages"
+ return Link(target, text, icon="info")
def maintained(self):
- target = '+maintained-packages'
- text = 'Maintained packages'
+ target = "+maintained-packages"
+ text = "Maintained packages"
enabled = self.person.hasMaintainedPackages()
- return Link(target, text, enabled=enabled, icon='info')
+ return Link(target, text, enabled=enabled, icon="info")
def uploaded(self):
- target = '+uploaded-packages'
- text = 'Uploaded packages'
+ target = "+uploaded-packages"
+ text = "Uploaded packages"
enabled = self.person.hasUploadedButNotMaintainedPackages()
- return Link(target, text, enabled=enabled, icon='info')
+ return Link(target, text, enabled=enabled, icon="info")
def ppa(self):
- target = '+ppa-packages'
- text = 'Related PPA packages'
+ target = "+ppa-packages"
+ text = "Related PPA packages"
enabled = self.person.hasUploadedPPAPackages()
- return Link(target, text, enabled=enabled, icon='info')
+ return Link(target, text, enabled=enabled, icon="info")
def synchronised(self):
- target = '+synchronised-packages'
- text = 'Synchronised packages'
+ target = "+synchronised-packages"
+ text = "Synchronised packages"
enabled = self.person.hasSynchronisedPublishings()
- return Link(target, text, enabled=enabled, icon='info')
+ return Link(target, text, enabled=enabled, icon="info")
def projects(self):
- target = '+related-projects'
- text = 'Related projects'
+ target = "+related-projects"
+ text = "Related projects"
user = getUtility(ILaunchBag).user
enabled = bool(self.person.getAffiliatedPillars(user))
- return Link(target, text, enabled=enabled, icon='info')
+ return Link(target, text, enabled=enabled, icon="info")
def owned_teams(self):
- target = '+owned-teams'
- text = 'Owned teams'
- return Link(target, text, icon='info')
+ target = "+owned-teams"
+ text = "Owned teams"
+ return Link(target, text, icon="info")
def subscriptions(self):
- target = '+subscriptions'
- text = 'Direct subscriptions'
- return Link(target, text, icon='info')
+ target = "+subscriptions"
+ text = "Direct subscriptions"
+ return Link(target, text, icon="info")
def structural_subscriptions(self):
- target = '+structural-subscriptions'
- text = 'Structural subscriptions'
- return Link(target, text, icon='info')
+ target = "+structural-subscriptions"
+ text = "Structural subscriptions"
+ return Link(target, text, icon="info")
def oci_registry_credentials(self):
- target = '+oci-registry-credentials'
- text = 'OCI registry credentials'
+ target = "+oci-registry-credentials"
+ text = "OCI registry credentials"
enabled = user_can_edit_credentials_for_owner(self.context, self.user)
- return Link(target, text, enabled=enabled, icon='info')
+ return Link(target, text, enabled=enabled, icon="info")
class PersonMenuMixin(CommonMenuLinks):
-
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def branding(self):
- target = '+branding'
- text = 'Change branding'
- return Link(target, text, icon='edit')
+ target = "+branding"
+ text = "Change branding"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
- target = '+edit'
- text = 'Change details'
- return Link(target, text, icon='edit')
+ target = "+edit"
+ text = "Change details"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def password(self):
target = config.launchpad.openid_provider_root
- text = 'Change password'
- return Link(target, text, icon='edit')
+ text = "Change password"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def administer(self):
- target = '+review'
- text = 'Administer'
- return Link(target, text, icon='edit')
+ target = "+review"
+ text = "Administer"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Moderate')
+ @enabled_with_permission("launchpad.Moderate")
def administer_account(self):
- target = '+reviewaccount'
- text = 'Administer Account'
- return Link(target, text, icon='edit')
+ target = "+reviewaccount"
+ text = "Administer Account"
+ return Link(target, text, icon="edit")
-class PersonOverviewMenu(ApplicationMenu, PersonMenuMixin, HasRecipesMenuMixin,
- HasSnapsMenuMixin, HasOCIRecipesMenuMixin,
- HasCharmRecipesMenuMixin):
+class PersonOverviewMenu(
+ ApplicationMenu,
+ PersonMenuMixin,
+ HasRecipesMenuMixin,
+ HasSnapsMenuMixin,
+ HasOCIRecipesMenuMixin,
+ HasCharmRecipesMenuMixin,
+):
usedfor = IPerson
- facet = 'overview'
+ facet = "overview"
links = [
- 'edit',
- 'branding',
- 'editemailaddresses',
- 'editlanguages',
- 'editmailinglists',
- 'editircnicknames',
- 'editjabberids',
- 'editsshkeys',
- 'editpgpkeys',
- 'editlocation',
- 'memberships',
- 'codesofconduct',
- 'karma',
- 'administer',
- 'administer_account',
- 'projects',
- 'activate_ppa',
- 'maintained',
- 'owned_teams',
- 'synchronised',
- 'view_ppa_subscriptions',
- 'ppa',
- 'oauth_tokens',
- 'oci_registry_credentials',
- 'related_software_summary',
- 'view_charm_recipes',
- 'view_recipes',
- 'view_snaps',
- 'view_oci_recipes',
- 'subscriptions',
- 'structural_subscriptions',
- ]
+ "edit",
+ "branding",
+ "editemailaddresses",
+ "editlanguages",
+ "editmailinglists",
+ "editircnicknames",
+ "editjabberids",
+ "editsshkeys",
+ "editpgpkeys",
+ "editlocation",
+ "memberships",
+ "codesofconduct",
+ "karma",
+ "administer",
+ "administer_account",
+ "projects",
+ "activate_ppa",
+ "maintained",
+ "owned_teams",
+ "synchronised",
+ "view_ppa_subscriptions",
+ "ppa",
+ "oauth_tokens",
+ "oci_registry_credentials",
+ "related_software_summary",
+ "view_charm_recipes",
+ "view_recipes",
+ "view_snaps",
+ "view_oci_recipes",
+ "subscriptions",
+ "structural_subscriptions",
+ ]
def related_software_summary(self):
- target = '+related-packages'
- text = 'Related packages'
- return Link(target, text, icon='info')
+ target = "+related-packages"
+ text = "Related packages"
+ return Link(target, text, icon="info")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def oauth_tokens(self):
- target = '+oauth-tokens'
- text = 'Authorized applications'
+ target = "+oauth-tokens"
+ text = "Authorized applications"
access_tokens = self.context.oauth_access_tokens
request_tokens = self.context.oauth_request_tokens
enabled = bool(access_tokens or request_tokens)
- return Link(target, text, enabled=enabled, icon='info')
+ return Link(target, text, enabled=enabled, icon="info")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def editlanguages(self):
- target = '+editlanguages'
- text = 'Set preferred languages'
- return Link(target, text, icon='edit')
+ target = "+editlanguages"
+ text = "Set preferred languages"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def editemailaddresses(self):
- target = '+editemails'
- text = 'Change email settings'
- return Link(target, text, icon='edit')
+ target = "+editemails"
+ text = "Change email settings"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def editmailinglists(self):
- target = '+editmailinglists'
- text = 'Manage mailing list subscriptions'
- return Link(target, text, icon='edit')
+ target = "+editmailinglists"
+ text = "Manage mailing list subscriptions"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def editircnicknames(self):
- target = '+editircnicknames'
- text = 'Update IRC nicknames'
- return Link(target, text, icon='edit')
+ target = "+editircnicknames"
+ text = "Update IRC nicknames"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def editjabberids(self):
- target = '+editjabberids'
- text = 'Update Jabber IDs'
- return Link(target, text, icon='edit')
+ target = "+editjabberids"
+ text = "Update Jabber IDs"
+ return Link(target, text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def editlocation(self):
- target = '+editlocation'
- text = 'Set location and time zone'
- return Link(target, text, icon='edit')
+ target = "+editlocation"
+ text = "Set location and time zone"
+ return Link(target, text, icon="edit")
def karma(self):
- target = '+karma'
- text = 'Show karma summary'
+ target = "+karma"
+ text = "Show karma summary"
summary = (
- '%s\N{right single quotation mark}s activities '
- 'in Launchpad' % self.context.displayname)
- return Link(target, text, summary, icon='info')
+ "%s\N{right single quotation mark}s activities "
+ "in Launchpad" % self.context.displayname
+ )
+ return Link(target, text, summary, icon="info")
def memberships(self):
- target = '+participation'
- text = 'Show team participation'
- return Link(target, text, icon='info')
+ target = "+participation"
+ text = "Show team participation"
+ return Link(target, text, icon="info")
- @enabled_with_permission('launchpad.Special')
+ @enabled_with_permission("launchpad.Special")
def editsshkeys(self):
- target = '+editsshkeys'
+ target = "+editsshkeys"
if self.context.sshkeys.is_empty():
- text = 'Add an SSH key'
- icon = 'add'
+ text = "Add an SSH key"
+ icon = "add"
else:
- text = 'Update SSH keys'
- icon = 'edit'
- summary = 'Used when storing code on Launchpad'
+ text = "Update SSH keys"
+ icon = "edit"
+ summary = "Used when storing code on Launchpad"
return Link(target, text, summary, icon=icon)
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def editpgpkeys(self):
- target = '+editpgpkeys'
- text = 'Update OpenPGP keys'
- summary = 'Used when maintaining packages'
- return Link(target, text, summary, icon='edit')
+ target = "+editpgpkeys"
+ text = "Update OpenPGP keys"
+ summary = "Used when maintaining packages"
+ return Link(target, text, summary, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def codesofconduct(self):
- target = '+codesofconduct'
- text = 'Codes of Conduct'
+ target = "+codesofconduct"
+ text = "Codes of Conduct"
summary = (
- 'Agreements to abide by the rules of a distribution or project')
- return Link(target, text, summary, icon='edit')
+ "Agreements to abide by the rules of a distribution or project"
+ )
+ return Link(target, text, summary, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def view_ppa_subscriptions(self):
target = "+archivesubscriptions"
text = "View your private PPA subscriptions"
- summary = ('View your personal PPA subscriptions and set yourself '
- 'up to download your software')
+ summary = (
+ "View your personal PPA subscriptions and set yourself "
+ "up to download your software"
+ )
# Only enable the link if the person has some subscriptions.
subscriptions = getUtility(IArchiveSubscriberSet).getBySubscriber(
- self.context)
+ self.context
+ )
enabled = not subscriptions.is_empty()
- return Link(target, text, summary, enabled=enabled, icon='info')
+ return Link(target, text, summary, enabled=enabled, icon="info")
class IPersonEditMenu(Interface):
@@ -967,8 +958,8 @@ class PPANavigationMenuMixIn:
"""PPA-related navigation menu links for Person and Team pages."""
def ppas(self):
- target = '#ppas'
- text = 'Personal Package Archives'
+ target = "#ppas"
+ text = "Personal Package Archives"
view = get_current_view()
if isinstance(view, PersonView):
enabled = view.should_show_ppa_section
@@ -980,9 +971,16 @@ class PPANavigationMenuMixIn:
class PersonRelatedSoftwareNavigationMenu(NavigationMenu, CommonMenuLinks):
usedfor = IPersonRelatedSoftwareMenu
- facet = 'overview'
- links = ('related_software_summary', 'maintained', 'uploaded', 'ppa',
- 'synchronised', 'projects', 'owned_teams')
+ facet = "overview"
+ links = (
+ "related_software_summary",
+ "maintained",
+ "uploaded",
+ "ppa",
+ "synchronised",
+ "projects",
+ "owned_teams",
+ )
@property
def person(self):
@@ -994,43 +992,50 @@ class PersonEditNavigationMenu(NavigationMenu):
"""A sub-menu for different aspects of editing a Person's profile."""
usedfor = IPersonEditMenu
- facet = 'overview'
- links = ('personal', 'email_settings', 'sshkeys', 'gpgkeys')
+ facet = "overview"
+ links = ("personal", "email_settings", "sshkeys", "gpgkeys")
def personal(self):
- target = '+edit'
- text = 'Personal'
+ target = "+edit"
+ text = "Personal"
return Link(target, text)
def email_settings(self):
- target = '+editemails'
- text = 'Email Settings'
+ target = "+editemails"
+ text = "Email Settings"
return Link(target, text)
- @enabled_with_permission('launchpad.Special')
+ @enabled_with_permission("launchpad.Special")
def sshkeys(self):
- target = '+editsshkeys'
- text = 'SSH keys'
+ target = "+editsshkeys"
+ text = "SSH keys"
return Link(target, text)
def gpgkeys(self):
- target = '+editpgpkeys'
- text = 'OpenPGP Keys'
+ target = "+editpgpkeys"
+ text = "OpenPGP Keys"
return Link(target, text)
class PersonSetActionNavigationMenu(RegistryCollectionActionMenuBase):
"""Action menu for `PeopleSearchView`."""
+
usedfor = IPersonSet
- links = ['register_team', 'register_project', 'create_account',
- 'request_merge', 'admin_merge_people', 'admin_merge_teams']
+ links = [
+ "register_team",
+ "register_project",
+ "create_account",
+ "request_merge",
+ "admin_merge_people",
+ "admin_merge_teams",
+ ]
@implementer(IRegistryCollectionNavigationMenu)
class PeopleSearchView(LaunchpadView):
"""Search for people and teams on the /people page."""
- page_title = 'People and teams in Launchpad'
+ page_title = "People and teams in Launchpad"
def __init__(self, context, request):
super().__init__(context, request)
@@ -1048,13 +1053,13 @@ class PeopleSearchView(LaunchpadView):
def is_teams_only(self):
"""Is the search restricted to teams."""
searchfor = self.request.get("searchfor", None)
- return searchfor == 'teamsonly'
+ return searchfor == "teamsonly"
@property
def is_people_only(self):
"""Is the search restricted to people."""
searchfor = self.request.get("searchfor", None)
- return searchfor == 'peopleonly'
+ return searchfor == "peopleonly"
def searchPeopleBatchNavigator(self):
name = self.request.get("name")
@@ -1071,7 +1076,8 @@ class PeopleSearchView(LaunchpadView):
class DeactivateAccountSchema(Interface):
comment = Text(
- title=_("Why are you deactivating your account?"), required=False)
+ title=_("Why are you deactivating your account?"), required=False
+ )
class PersonDeactivateAccountView(LaunchpadFormView):
@@ -1079,7 +1085,8 @@ class PersonDeactivateAccountView(LaunchpadFormView):
schema = DeactivateAccountSchema
label = "Deactivate your Launchpad account"
custom_widget_comment = CustomWidgetFactory(
- TextAreaWidget, height=5, width=60)
+ TextAreaWidget, height=5, width=60
+ )
def validate(self, data):
"""See `LaunchpadFormView`."""
@@ -1087,11 +1094,12 @@ class PersonDeactivateAccountView(LaunchpadFormView):
@action(_("Deactivate My Account"), name="deactivate")
def deactivate_action(self, action, data):
- self.context.preDeactivate(data['comment'])
+ self.context.preDeactivate(data["comment"])
getUtility(IPersonDeactivateJobSource).create(self.context)
logoutPerson(self.request)
self.request.response.addInfoNotification(
- _('Your account has been deactivated.'))
+ _("Your account has been deactivated.")
+ )
self.next_url = self.request.getApplicationURL()
@@ -1101,7 +1109,8 @@ class BeginTeamClaimView(LaunchpadFormView):
This is actually just the first step, where you enter the email address
of the team and we email further instructions to that address.
"""
- label = 'Claim team'
+
+ label = "Claim team"
schema = IPersonClaim
def initialize(self):
@@ -1109,15 +1118,16 @@ class BeginTeamClaimView(LaunchpadFormView):
# Valid teams and people aren't claimable. We pull the path
# out of PATH_INFO to make sure that the exception looks
# good for subclasses. We're that picky!
- name = self.request['PATH_INFO'].split("/")[-1]
+ name = self.request["PATH_INFO"].split("/")[-1]
raise NotFound(self, name, request=self.request)
LaunchpadFormView.initialize(self)
def validate(self, data):
- emailaddress = data.get('emailaddress')
+ emailaddress = data.get("emailaddress")
if emailaddress is None:
self.setFieldError(
- 'emailaddress', 'Please enter the email address')
+ "emailaddress", "Please enter the email address"
+ )
return
email = getUtility(IEmailAddressSet).getByEmail(emailaddress)
@@ -1125,29 +1135,31 @@ class BeginTeamClaimView(LaunchpadFormView):
if email is None:
# Email not registered in launchpad, ask the user to try another
# one.
- error = ("We couldn't find this email address. Please try "
- "another one that could possibly be associated with "
- "this profile. Note that this profile's name (%s) was "
- "generated based on the email address it's "
- "associated with."
- % self.context.name)
+ error = (
+ "We couldn't find this email address. Please try "
+ "another one that could possibly be associated with "
+ "this profile. Note that this profile's name (%s) was "
+ "generated based on the email address it's "
+ "associated with." % self.context.name
+ )
elif email.personID != self.context.id:
error = structured(
- "This email address is associated with yet another "
- "Launchpad profile, which you seem to have used at "
- "some point. If that's the case, you can "
- '<a href="/people/+requestmerge'
- '?field.dupe_person=%s">combine '
- "this profile with the other one</a> (you'll "
- "have to log in with the other profile first, "
- "though). If that's not the case, please try with a "
- "different email address.",
- self.context.name)
+ "This email address is associated with yet another "
+ "Launchpad profile, which you seem to have used at "
+ "some point. If that's the case, you can "
+ '<a href="/people/+requestmerge'
+ '?field.dupe_person=%s">combine '
+ "this profile with the other one</a> (you'll "
+ "have to log in with the other profile first, "
+ "though). If that's not the case, please try with a "
+ "different email address.",
+ self.context.name,
+ )
else:
# Yay! You got the right email this time.
pass
if error:
- self.setFieldError('emailaddress', error)
+ self.setFieldError("emailaddress", error)
@property
def next_url(self):
@@ -1155,19 +1167,25 @@ class BeginTeamClaimView(LaunchpadFormView):
@action(_("Continue"), name="confirm")
def confirm_action(self, action, data):
- email = data['emailaddress']
+ email = data["emailaddress"]
token = getUtility(ILoginTokenSet).new(
- requester=self.user, requesteremail=None, email=email,
- tokentype=LoginTokenType.TEAMCLAIM)
+ requester=self.user,
+ requesteremail=None,
+ email=email,
+ tokentype=LoginTokenType.TEAMCLAIM,
+ )
token.sendClaimTeamEmail()
- self.request.response.addInfoNotification(_(
- "A confirmation message has been sent to '${email}'. "
- "Follow the instructions in that message to finish claiming this "
- "team. "
- "(If the above address is from a mailing list, it may be "
- "necessary to talk with one of its admins to accept the message "
- "from Launchpad so that you can finish the process.)",
- mapping=dict(email=email)))
+ self.request.response.addInfoNotification(
+ _(
+ "A confirmation message has been sent to '${email}'. "
+ "Follow the instructions in that message to finish claiming "
+ "this team. "
+ "(If the above address is from a mailing list, it may be "
+ "necessary to talk with one of its admins to accept the "
+ "message from Launchpad so that you can finish the process.)",
+ mapping=dict(email=email),
+ )
+ )
class RedirectToEditLanguagesView(LaunchpadView):
@@ -1181,18 +1199,18 @@ class RedirectToEditLanguagesView(LaunchpadView):
def initialize(self):
self.request.response.redirect(
- '%s/+editlanguages' % canonical_url(self.user))
+ "%s/+editlanguages" % canonical_url(self.user)
+ )
class PersonRdfView(BaseRdfView):
"""A view that embeds PersonRdfContentsView in a standalone page."""
- template = ViewPageTemplateFile(
- '../templates/person-rdf.pt')
+ template = ViewPageTemplateFile("../templates/person-rdf.pt")
@property
def filename(self):
- return '%s.rdf' % self.context.name
+ return "%s.rdf" % self.context.name
class PersonRdfContentsView:
@@ -1202,8 +1220,9 @@ class PersonRdfContentsView:
# preserve the case of the elements (which is not preserved in the
# parsing of the default text/html content-type.)
template = ViewPageTemplateFile(
- '../templates/person-rdf-contents.pt',
- content_type='application/rdf+xml;charset="utf-8"')
+ "../templates/person-rdf-contents.pt",
+ content_type='application/rdf+xml;charset="utf-8"',
+ )
def __init__(self, context, request):
self.context = context
@@ -1218,12 +1237,11 @@ class PersonRdfContentsView:
+rdf-contents/template.
"""
unicodedata = self.template()
- encodeddata = unicodedata.encode('utf-8')
+ encodeddata = unicodedata.encode("utf-8")
return encodeddata
class PersonRenameFormMixin(LaunchpadEditFormView):
-
def setUpWidgets(self):
"""See `LaunchpadViewForm`.
@@ -1235,23 +1253,27 @@ class PersonRenameFormMixin(LaunchpadEditFormView):
# No message means renames are permitted.
if reason:
# This makes the field's widget display (i.e. read) only.
- self.form_fields['name'].for_display = True
+ self.form_fields["name"].for_display = True
super().setUpWidgets()
if reason:
- self.widgets['name'].hint = reason
+ self.widgets["name"].hint = reason
class PersonAdministerView(PersonRenameFormMixin):
"""Administer an `IPerson`."""
+
schema = IPerson
label = "Review person"
field_names = [
- 'name', 'display_name',
- 'personal_standing', 'personal_standing_reason',
- 'require_strong_email_authentication',
- ]
+ "name",
+ "display_name",
+ "personal_standing",
+ "personal_standing_reason",
+ "require_strong_email_authentication",
+ ]
custom_widget_personal_standing_reason = CustomWidgetFactory(
- TextAreaWidget, height=5, width=60)
+ TextAreaWidget, height=5, width=60
+ )
@property
def is_viewing_person(self):
@@ -1272,7 +1294,7 @@ class PersonAdministerView(PersonRenameFormMixin):
"""See `LaunchpadEditFormView`."""
return canonical_url(self.context)
- @action('Change', name='change')
+ @action("Change", name="change")
def change_action(self, action, data):
"""Update the IPerson."""
self.updateContextFromData(data)
@@ -1280,6 +1302,7 @@ class PersonAdministerView(PersonRenameFormMixin):
class PersonCloseAccountView(LaunchpadFormView):
"""Close an account."""
+
schema = Interface
label = "Close account"
@@ -1293,28 +1316,32 @@ class PersonCloseAccountView(LaunchpadFormView):
"""See `LaunchpadEditFormView`."""
return canonical_url(self.context)
- @action('Close', name='close')
+ @action("Close", name="close")
def delete_action(self, action, data):
"""Close the account."""
getUtility(IPersonCloseAccountJobSource).create(self.context)
self.request.response.addInfoNotification(
- "This account will now be permanently closed.")
+ "This account will now be permanently closed."
+ )
class IAccountAdministerSchema(Interface):
- status = copy_field(IAccount['status'], required=True, readonly=False)
+ status = copy_field(IAccount["status"], required=True, readonly=False)
comment = Text(
- title=_('Status change comment'), required=True, readonly=False)
+ title=_("Status change comment"), required=True, readonly=False
+ )
class PersonAccountAdministerView(LaunchpadFormView):
"""Administer an `IAccount` belonging to an `IPerson`."""
+
schema = IAccountAdministerSchema
label = "Review person's account"
- field_names = ['status', 'comment']
+ field_names = ["status", "comment"]
custom_widget_comment = CustomWidgetFactory(
- TextAreaWidget, height=5, width=60)
+ TextAreaWidget, height=5, width=60
+ )
def __init__(self, context, request):
"""See `LaunchpadEditFormView`."""
@@ -1326,7 +1353,7 @@ class PersonAccountAdministerView(LaunchpadFormView):
@property
def initial_values(self):
- return {'status': self.context.status}
+ return {"status": self.context.status}
@property
def is_viewing_person(self):
@@ -1340,8 +1367,7 @@ class PersonAccountAdministerView(LaunchpadFormView):
@property
def email_addresses(self):
"""A list of the user's preferred and validated email addresses."""
- emails = sorted(
- email.email for email in self.person.validatedemails)
+ emails = sorted(email.email for email in self.person.validatedemails)
if self.person.preferredemail is not None:
emails.insert(0, self.person.preferredemail.email)
return emails
@@ -1365,33 +1391,37 @@ class PersonAccountAdministerView(LaunchpadFormView):
"""See `LaunchpadEditFormView`."""
return canonical_url(self.person)
- @action('Change', name='change')
+ @action("Change", name="change")
def change_action(self, action, data):
"""Update the IAccount."""
- if data['status'] == self.context.status:
+ if data["status"] == self.context.status:
return
- if data['status'] == AccountStatus.SUSPENDED:
+ if data["status"] == AccountStatus.SUSPENDED:
# The preferred email address is removed to ensure no email
# is sent to the user.
self.person.setPreferredEmail(None)
self.request.response.addInfoNotification(
'The account "%s" has been suspended.'
- % self.context.displayname)
- elif data['status'] == AccountStatus.DEACTIVATED:
+ % self.context.displayname
+ )
+ elif data["status"] == AccountStatus.DEACTIVATED:
self.request.response.addInfoNotification(
'The account "%s" is now deactivated. The user can log in '
- 'to reactivate it.' % self.context.displayname)
- elif data['status'] == AccountStatus.DECEASED:
+ "to reactivate it." % self.context.displayname
+ )
+ elif data["status"] == AccountStatus.DECEASED:
# Deliberately leave the email address in place so that it can't
# easily be claimed by somebody else.
self.request.response.addInfoNotification(
'The account "%s" has been marked as having belonged to a '
- 'deceased user.' % self.context.displayname)
- self.context.setStatus(data['status'], self.user, data['comment'])
+ "deceased user." % self.context.displayname
+ )
+ self.context.setStatus(data["status"], self.user, data["comment"])
class PersonLanguagesView(LaunchpadFormView):
"""Edit preferred languages for a person or team."""
+
schema = Interface
@property
@@ -1408,8 +1438,7 @@ class PersonLanguagesView(LaunchpadFormView):
return ICountry(self.request, None)
def browserLanguages(self):
- return (
- IRequestPreferredLanguages(self.request).getPreferredLanguages())
+ return IRequestPreferredLanguages(self.request).getPreferredLanguages()
def visible_checked_languages(self):
return self.context.languages
@@ -1417,17 +1446,21 @@ class PersonLanguagesView(LaunchpadFormView):
def visible_unchecked_languages(self):
common_languages = getUtility(ILanguageSet).common_languages
person_languages = self.context.languages
- return sorted(set(common_languages) - set(person_languages),
- key=attrgetter('englishname'))
+ return sorted(
+ set(common_languages) - set(person_languages),
+ key=attrgetter("englishname"),
+ )
def getRedirectionURL(self):
request = self.request
- referrer = request.getHeader('referer')
- if referrer and (referrer.startswith(request.getApplicationURL()) or
- referrer.find('+languages') != -1):
+ referrer = request.getHeader("referer")
+ if referrer and (
+ referrer.startswith(request.getApplicationURL())
+ or referrer.find("+languages") != -1
+ ):
return referrer
else:
- return ''
+ return ""
@property
def is_current_user(self):
@@ -1437,7 +1470,7 @@ class PersonLanguagesView(LaunchpadFormView):
@property
def next_url(self):
"""Redirect to the +languages page if request originated there."""
- redirection_url = self.request.get('redirection_url')
+ redirection_url = self.request.get("redirection_url")
if redirection_url:
return redirection_url
return canonical_url(self.context)
@@ -1452,18 +1485,18 @@ class PersonLanguagesView(LaunchpadFormView):
@action(_("Save"), name="save")
def submitLanguages(self, action, data):
- '''Process a POST request to the language preference form.
+ """Process a POST request to the language preference form.
This list of languages submitted is compared to the list of
languages the user has, and the latter is matched to the former.
- '''
+ """
all_languages = getUtility(ILanguageSet)
old_languages = self.context.languages
new_languages = []
for key in all_languages.keys():
- if self.request.get(key, None) == 'on':
+ if self.request.get(key, None) == "on":
new_languages.append(all_languages[key])
if self.is_current_user:
@@ -1476,38 +1509,41 @@ class PersonLanguagesView(LaunchpadFormView):
for language in set(new_languages) - set(old_languages):
self.context.addLanguage(language)
messages.append(
- "Added %(language)s to %(subject)s preferred languages." %
- {'language': language.englishname, 'subject': subject})
+ "Added %(language)s to %(subject)s preferred languages."
+ % {"language": language.englishname, "subject": subject}
+ )
# Remove languages from the user's preferences.
for language in set(old_languages) - set(new_languages):
self.context.removeLanguage(language)
messages.append(
- "Removed %(language)s from %(subject)s preferred languages." %
- {'language': language.englishname, 'subject': subject})
+ "Removed %(language)s from %(subject)s preferred languages."
+ % {"language": language.englishname, "subject": subject}
+ )
if len(messages) > 0:
message = structured(
- '<br />'.join(['%s'] * len(messages)), *messages)
+ "<br />".join(["%s"] * len(messages)), *messages
+ )
self.request.response.addInfoNotification(message)
@property
def answers_url(self):
return canonical_url(
- getUtility(ILaunchpadCelebrities).launchpad,
- rootsite='answers')
+ getUtility(ILaunchpadCelebrities).launchpad, rootsite="answers"
+ )
class PersonKarmaView(LaunchpadView):
"""A view class used for ~person/+karma."""
- page_title = 'Karma'
+ page_title = "Karma"
@property
def label(self):
if self.user == self.context:
- return 'Your Launchpad Karma'
+ return "Your Launchpad Karma"
else:
- return 'Launchpad Karma'
+ return "Launchpad Karma"
@cachedproperty
def has_karma(self):
@@ -1521,7 +1557,6 @@ class PersonKarmaView(LaunchpadView):
class ContactViaWebLinksMixin:
-
@cachedproperty
def group_to_contact(self):
"""Contacting a team may contact different email addresses.
@@ -1533,7 +1568,8 @@ class ContactViaWebLinksMixin:
TO_MEMBERS
"""
return ContactViaWebNotificationRecipientSet(
- self.user, self.context).primary_reason
+ self.user, self.context
+ ).primary_reason
@property
def contact_link_title(self):
@@ -1541,15 +1577,15 @@ class ContactViaWebLinksMixin:
ContactViaWeb = ContactViaWebNotificationRecipientSet
if self.group_to_contact == ContactViaWeb.TO_USER:
if self.viewing_own_page:
- return 'Send an email to yourself through Launchpad'
+ return "Send an email to yourself through Launchpad"
else:
- return 'Send an email to this user through Launchpad'
+ return "Send an email to this user through Launchpad"
elif self.group_to_contact == ContactViaWeb.TO_MEMBERS:
return "Send an email to your team's members through Launchpad"
elif self.group_to_contact == ContactViaWeb.TO_ADMINS:
return "Send an email to this team's admins through Launchpad"
else:
- raise AssertionError('Unknown group to contact.')
+ raise AssertionError("Unknown group to contact.")
@property
def specific_contact_text(self):
@@ -1558,13 +1594,13 @@ class ContactViaWebLinksMixin:
if self.group_to_contact == ContactViaWeb.TO_USER:
# Note that we explicitly do not change the text to "Contact
# yourself" when viewing your own page.
- return 'Contact this user'
+ return "Contact this user"
elif self.group_to_contact == ContactViaWeb.TO_MEMBERS:
return "Contact this team's members"
elif self.group_to_contact == ContactViaWeb.TO_ADMINS:
return "Contact this team's admins"
else:
- raise AssertionError('Unknown group to contact.')
+ raise AssertionError("Unknown group to contact.")
class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
@@ -1578,7 +1614,8 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
rights to sign it.
"""
return self.context.is_ubuntu_coc_signer or (
- check_permission('launchpad.Edit', self.context))
+ check_permission("launchpad.Edit", self.context)
+ )
@property
def should_show_ircnicknames_section(self):
@@ -1588,7 +1625,8 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
to register new ones.
"""
return bool(self.context.ircnicknames) or (
- check_permission('launchpad.Edit', self.context))
+ check_permission("launchpad.Edit", self.context)
+ )
@property
def should_show_jabberids_section(self):
@@ -1598,7 +1636,8 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
to register new ones.
"""
return bool(self.context.jabberids) or (
- check_permission('launchpad.Edit', self.context))
+ check_permission("launchpad.Edit", self.context)
+ )
@property
def should_show_sshkeys_section(self):
@@ -1608,7 +1647,8 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
to register new ones.
"""
return bool(self.context.sshkeys) or (
- check_permission('launchpad.Edit', self.context))
+ check_permission("launchpad.Edit", self.context)
+ )
@property
def should_show_gpgkeys_section(self):
@@ -1618,7 +1658,8 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
to register new ones.
"""
return bool(self.gpg_keys) or (
- check_permission('launchpad.Edit', self.context))
+ check_permission("launchpad.Edit", self.context)
+ )
@cachedproperty
def gpg_keys(self):
@@ -1640,21 +1681,24 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
def recently_approved_members(self):
members = self.context.getMembersByStatus(
TeamMembershipStatus.APPROVED,
- orderBy='-TeamMembership.date_joined')
+ orderBy="-TeamMembership.date_joined",
+ )
return members[:5]
@cachedproperty
def recently_proposed_members(self):
members = self.context.getMembersByStatus(
TeamMembershipStatus.PROPOSED,
- orderBy='-TeamMembership.date_proposed')
+ orderBy="-TeamMembership.date_proposed",
+ )
return members[:5]
@cachedproperty
def recently_invited_members(self):
members = self.context.getMembersByStatus(
TeamMembershipStatus.INVITED,
- orderBy='-TeamMembership.date_proposed')
+ orderBy="-TeamMembership.date_proposed",
+ )
return members[:5]
@property
@@ -1665,9 +1709,9 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
but hidden in case it adds a member to the list.
"""
if IResultSet(self.recently_approved_members).is_empty():
- return 'hidden'
+ return "hidden"
else:
- return ''
+ return ""
@property
def recently_proposed_hidden(self):
@@ -1677,9 +1721,9 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
but hidden in case it adds a member to the list.
"""
if IResultSet(self.recently_proposed_members).is_empty():
- return 'hidden'
+ return "hidden"
else:
- return ''
+ return ""
@property
def recently_invited_hidden(self):
@@ -1689,9 +1733,9 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
but hidden in case it adds a member to the list.
"""
if IResultSet(self.recently_invited_members).is_empty():
- return 'hidden'
+ return "hidden"
else:
- return ''
+ return ""
@cachedproperty
def openpolls(self):
@@ -1712,16 +1756,24 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
def contributions(self):
"""Cache the results of getProjectsAndCategoriesContributedTo()."""
return self.context.getProjectsAndCategoriesContributedTo(
- self.user, limit=5)
+ self.user, limit=5
+ )
@cachedproperty
def contributed_categories(self):
"""Return all karma categories in which this person has some karma."""
categories = set()
for contrib in self.contributions:
- categories.update(category for category in contrib['categories'])
- sort = {'code': 0, 'bugs': 1, 'blueprints': 2, 'translations': 3,
- 'answers': 4, 'specs': 5, 'soyuz': 6}
+ categories.update(category for category in contrib["categories"])
+ sort = {
+ "code": 0,
+ "bugs": 1,
+ "blueprints": 2,
+ "translations": 3,
+ "answers": 4,
+ "specs": 5,
+ "soyuz": 6,
+ }
return sorted(categories, key=lambda category: sort[category.name])
@cachedproperty
@@ -1735,11 +1787,12 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
This can only be used when the context is an automatically created
profile (account_status == NOACCOUNT).
"""
- assert self.context.account_status == AccountStatus.NOACCOUNT, (
- "This can only be used when the context has no account.")
+ assert (
+ self.context.account_status == AccountStatus.NOACCOUNT
+ ), "This can only be used when the context has no account."
emails = getUtility(IEmailAddressSet).getByPerson(self.context)
for email in emails:
- if '@lists.' in removeSecurityProxy(email).email:
+ if "@lists." in removeSecurityProxy(email).email:
return True
return False
@@ -1749,8 +1802,10 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
We only do this if it's enabled for the vhost.
"""
- return (self.context.is_valid_person
- and config.vhost.mainsite.openid_delegate_profile)
+ return (
+ self.context.is_valid_person
+ and config.vhost.mainsite.openid_delegate_profile
+ )
@cachedproperty
def openid_identity_url(self):
@@ -1766,17 +1821,24 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
person that are In Progress.
"""
query_string = urlencode(
- [('field.status', BugTaskStatus.INPROGRESS.title)])
+ [("field.status", BugTaskStatus.INPROGRESS.title)]
+ )
url = "%s/+assignedbugs" % canonical_url(self.context)
- return ("%(url)s?search=Search&%(query_string)s"
- % {'url': url, 'query_string': query_string})
+ return "%(url)s?search=Search&%(query_string)s" % {
+ "url": url,
+ "query_string": query_string,
+ }
@cachedproperty
def assigned_bugs_in_progress(self):
"""Return up to 5 assigned bugs that are In Progress."""
params = BugTaskSearchParams(
- user=self.user, assignee=self.context, omit_dupes=True,
- status=BugTaskStatus.INPROGRESS, orderby='-date_last_updated')
+ user=self.user,
+ assignee=self.context,
+ omit_dupes=True,
+ status=BugTaskStatus.INPROGRESS,
+ orderby="-date_last_updated",
+ )
return list(self.context.searchTasks(params)[:5])
@cachedproperty
@@ -1804,17 +1866,19 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
cannot contact persons or teams, and no one can contact an invalid
person (inactive or without a preferred email address).
"""
- return (
- self.user is not None and self.context.is_valid_person_or_team)
+ return self.user is not None and self.context.is_valid_person_or_team
@property
def should_show_polls_portlet(self):
# Circular imports.
from lp.registry.browser.team import TeamOverviewMenu
+
menu = TeamOverviewMenu(self.context)
return (
- self.has_current_polls or self.closedpolls
- or menu.add_poll().enabled)
+ self.has_current_polls
+ or self.closedpolls
+ or menu.add_poll().enabled
+ )
@property
def has_current_polls(self):
@@ -1868,12 +1932,17 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
:return: A list of email address strings that can be seen.
"""
visible_states = (
- EmailAddressVisibleState.PUBLIC, EmailAddressVisibleState.ALLOWED)
+ EmailAddressVisibleState.PUBLIC,
+ EmailAddressVisibleState.ALLOWED,
+ )
if self.email_address_visibility.state in visible_states:
emails = [self.context.preferredemail.email]
if not self.context.is_team:
- emails.extend(sorted(
- email.email for email in self.context.validatedemails))
+ emails.extend(
+ sorted(
+ email.email for email in self.context.validatedemails
+ )
+ )
return emails
else:
return []
@@ -1887,15 +1956,15 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
"""
state = self.email_address_visibility.state
if state is EmailAddressVisibleState.PUBLIC:
- return 'This email address is only visible to Launchpad users.'
+ return "This email address is only visible to Launchpad users."
elif state is EmailAddressVisibleState.ALLOWED:
- return 'This email address is not disclosed to others.'
+ return "This email address is not disclosed to others."
else:
return None
def showSSHKeys(self):
"""Return a data structure used for display of raw SSH keys"""
- self.request.response.setHeader('Content-Type', 'text/plain')
+ self.request.response.setHeader("Content-Type", "text/plain")
keys = [key.getFullKeyText() for key in self.context.sshkeys]
return "".join(key + "\n" for key in keys)
@@ -1916,8 +1985,9 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
return mailing_list.archive_url
elif self.user is None:
return None
- elif (self.user.inTeam(self.context) or
- self.user.inTeam(celebrities.admin)):
+ elif self.user.inTeam(self.context) or self.user.inTeam(
+ celebrities.admin
+ ):
return mailing_list.archive_url
else:
return None
@@ -1928,7 +1998,7 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
languages = list(self.context.languages)
if len(languages) > 0:
englishnames = [language.englishname for language in languages]
- return ', '.join(sorted(englishnames))
+ return ", ".join(sorted(englishnames))
else:
return getUtility(ILaunchpadCelebrities).english.englishname
@@ -1940,7 +2010,7 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
current_user may view at least one PPA or current_user has lp.edit
"""
# If the current user has edit permission, show the section.
- if check_permission('launchpad.Edit', self.context):
+ if check_permission("launchpad.Edit", self.context):
return True
# If the current user can view any PPA, show the section.
@@ -1949,14 +2019,15 @@ class PersonView(LaunchpadView, FeedsMixin, ContactViaWebLinksMixin):
@cachedproperty
def visible_ppas(self):
ppas = self.context.getVisiblePPAs(self.user)
- precache_permission_for_objects(self.request, 'launchpad.View', ppas)
+ precache_permission_for_objects(self.request, "launchpad.View", ppas)
return ppas
@property
def time_zone_offset(self):
"""Return a string with offset from UTC"""
- return datetime.now(
- pytz.timezone(self.context.time_zone)).strftime("%z")
+ return datetime.now(pytz.timezone(self.context.time_zone)).strftime(
+ "%z"
+ )
class PersonParticipationView(LaunchpadView):
@@ -1964,20 +2035,28 @@ class PersonParticipationView(LaunchpadView):
@property
def label(self):
- return 'Team participation for ' + self.context.displayname
-
- def _asParticipation(self, team=None, membership=None, via=None,
- mailing_list=None, subscription=None):
+ return "Team participation for " + self.context.displayname
+
+ def _asParticipation(
+ self,
+ team=None,
+ membership=None,
+ via=None,
+ mailing_list=None,
+ subscription=None,
+ ):
"""Return a dict of participation information for the membership.
Method requires membership or via, not both.
:param via: The team through which the membership in the indirect
team is established.
"""
- if ((membership is None and via is None) or
- (membership is not None and via is not None)):
+ if (membership is None and via is None) or (
+ membership is not None and via is not None
+ ):
raise AssertionError(
- "The membership or via argument must be provided, not both.")
+ "The membership or via argument must be provided, not both."
+ )
if via is not None:
# When showing the path, it's unnecessary to show the team in
@@ -1985,16 +2064,16 @@ class PersonParticipationView(LaunchpadView):
# end of the path.
via_names = []
for via_team in via[1:-1]:
- if check_permission('launchpad.LimitedView', via_team):
+ if check_permission("launchpad.LimitedView", via_team):
via_names.append(via_team.displayname)
else:
- via_names.append('[private team]')
+ via_names.append("[private team]")
via = ", ".join(via_names)
if membership is None:
# Membership is via an indirect team so sane defaults exist.
# An indirect member cannot be an Owner or Admin of a team.
- role = 'Member'
+ role = "Member"
# The Person never joined, and can't have a join date.
datejoined = None
dateexpires = None
@@ -2003,23 +2082,29 @@ class PersonParticipationView(LaunchpadView):
datejoined = membership.datejoined
dateexpires = membership.dateexpires
if membership.personID == team.teamownerID:
- role = 'Owner'
+ role = "Owner"
elif membership.status == TeamMembershipStatus.ADMIN:
- role = 'Admin'
+ role = "Admin"
else:
- role = 'Member'
+ role = "Member"
if mailing_list is not None:
if subscription is None:
- subscribed = 'Not subscribed'
+ subscribed = "Not subscribed"
else:
- subscribed = 'Subscribed'
+ subscribed = "Subscribed"
else:
- subscribed = '—'
+ subscribed = "—"
return dict(
- displayname=team.displayname, team=team, datejoined=datejoined,
- role=role, via=via, subscribed=subscribed, dateexpires=dateexpires)
+ displayname=team.displayname,
+ team=team,
+ datejoined=datejoined,
+ role=role,
+ via=via,
+ subscribed=subscribed,
+ dateexpires=dateexpires,
+ )
@cachedproperty
def active_participations(self):
@@ -2033,20 +2118,24 @@ class PersonParticipationView(LaunchpadView):
if team not in direct_teams:
items.append(dict(team=team, via=via))
items = [
- item for item in items
- if check_permission('launchpad.View', item["team"])]
+ item
+ for item in items
+ if check_permission("launchpad.View", item["team"])
+ ]
participations = []
# Bulk-load mailing list subscriptions.
subscriptions = getUtility(IMailingListSet).getSubscriptionsForTeams(
- self.context, [item["team"] for item in items])
+ self.context, [item["team"] for item in items]
+ )
# Create all the participations.
for item in items:
item["mailing_list"], item["subscription"] = subscriptions.get(
- item["team"].id, (None, None))
+ item["team"].id, (None, None)
+ )
participations.append(self._asParticipation(**item))
- return sorted(participations, key=itemgetter('displayname'))
+ return sorted(participations, key=itemgetter("displayname"))
@cachedproperty
def has_participations(self):
@@ -2072,6 +2161,7 @@ class EmailAddressVisibleState:
viewing their own page or because the user is a privileged
administrator.
"""
+
LOGIN_REQUIRED = object()
NONE_AVAILABLE = object()
PUBLIC = object()
@@ -2091,7 +2181,7 @@ class EmailAddressVisibleState:
self.state = EmailAddressVisibleState.NONE_AVAILABLE
elif not view.context.hide_email_addresses:
self.state = EmailAddressVisibleState.PUBLIC
- elif check_permission('launchpad.View', view.context.preferredemail):
+ elif check_permission("launchpad.View", view.context.preferredemail):
self.state = EmailAddressVisibleState.ALLOWED
else:
self.state = EmailAddressVisibleState.HIDDEN
@@ -2130,23 +2220,24 @@ def is_description_text_linkified(person: IPerson) -> bool:
return not person.is_probationary
-class PersonIndexView(XRDSContentNegotiationMixin, PersonView,
- TeamJoinMixin):
+class PersonIndexView(XRDSContentNegotiationMixin, PersonView, TeamJoinMixin):
"""View class for person +index and +xrds pages."""
xrds_template = ViewPageTemplateFile(
- "../../services/openid/templates/person-xrds.pt")
+ "../../services/openid/templates/person-xrds.pt"
+ )
def initialize(self):
super().initialize()
if self.context.isMergePending():
if self.context.is_team:
- merge_action = 'merged or deleted'
+ merge_action = "merged or deleted"
else:
- merge_action = 'merged'
+ merge_action = "merged"
self.request.response.addInfoNotification(
- "%s is queued to be %s in a few minutes." % (
- self.context.displayname, merge_action))
+ "%s is queued to be %s in a few minutes."
+ % (self.context.displayname, merge_action)
+ )
if self.request.method == "POST":
self.processForm()
@@ -2154,7 +2245,7 @@ class PersonIndexView(XRDSContentNegotiationMixin, PersonView,
def page_title(self):
context = self.context
if context.is_valid_person_or_team:
- return '%s in Launchpad' % context.displayname
+ return "%s in Launchpad" % context.displayname
else:
return "%s does not use Launchpad" % context.displayname
@@ -2162,9 +2253,13 @@ class PersonIndexView(XRDSContentNegotiationMixin, PersonView,
def description_widget(self):
"""The description as a widget."""
return TextAreaEditorWidget(
- self.context, IPerson['description'], title="",
- edit_title='Edit description', hide_empty=False,
- linkify_text=is_description_text_linkified(self.context))
+ self.context,
+ IPerson["description"],
+ title="",
+ edit_title="Edit description",
+ hide_empty=False,
+ linkify_text=is_description_text_linkified(self.context),
+ )
@cachedproperty
def page_description(self):
@@ -2188,28 +2283,32 @@ class PersonIndexView(XRDSContentNegotiationMixin, PersonView,
return IOpenIDPersistentIdentity(self.context).openid_identity_url
def processForm(self):
- if not self.request.form.get('unsubscribe'):
+ if not self.request.form.get("unsubscribe"):
raise UnexpectedFormData(
"The mailing list form did not receive the expected form "
- "fields.")
+ "fields."
+ )
mailing_list = self.context.mailing_list
if mailing_list is None:
raise UnexpectedFormData(
- _("This team does not use Launchpad to host a mailing list."))
+ _("This team does not use Launchpad to host a mailing list.")
+ )
if not self.user:
- raise Unauthorized(
- _("You must be logged in to unsubscribe."))
+ raise Unauthorized(_("You must be logged in to unsubscribe."))
try:
mailing_list.unsubscribe(self.user)
except CannotUnsubscribe:
self.request.response.addErrorNotification(
- _("You could not be unsubscribed from the team mailing "
- "list."))
+ _(
+ "You could not be unsubscribed from the team mailing "
+ "list."
+ )
+ )
else:
self.request.response.addInfoNotification(
- _("You have been unsubscribed from the team "
- "mailing list."))
+ _("You have been unsubscribed from the team " "mailing list.")
+ )
self.request.response.redirect(canonical_url(self.context))
@@ -2219,7 +2318,7 @@ class PersonCodeOfConductEditView(LaunchpadView):
@property
def label(self):
"""See `LaunchpadView`."""
- return 'Codes of Conduct for ' + self.context.displayname
+ return "Codes of Conduct for " + self.context.displayname
def initialize(self):
"""See `LaunchpadView`."""
@@ -2234,7 +2333,7 @@ class PersonCodeOfConductEditView(LaunchpadView):
for sig_id in sig_ids:
sig_id = int(sig_id)
# Deactivating signature.
- comment = 'Deactivated by Owner'
+ comment = "Deactivated by Owner"
sCoC_util.modifySignature(sig_id, self.user, comment, False)
@@ -2264,20 +2363,21 @@ class PersonEditIRCNicknamesView(LaunchpadFormView):
# unique column we have, so we don't have anything else that we
# can use to make field names that allow us to uniquely identify
# them.
- if form.get('remove_%d' % ircnick.id):
+ if form.get("remove_%d" % ircnick.id):
ircnick.destroySelf()
else:
- nick = form.get('nick_%d' % ircnick.id)
- network = form.get('network_%d' % ircnick.id)
+ nick = form.get("nick_%d" % ircnick.id)
+ network = form.get("network_%d" % ircnick.id)
if not (nick and network):
self.request.response.addErrorNotification(
- "Neither Nickname nor Network can be empty.")
+ "Neither Nickname nor Network can be empty."
+ )
return
ircnick.nickname = nick
ircnick.network = network
- nick = form.get('newnick')
- network = form.get('newnetwork')
+ nick = form.get("newnick")
+ network = form.get("newnetwork")
if nick or network:
if nick and network:
getUtility(IIrcIDSet).new(self.context, network, nick)
@@ -2285,13 +2385,14 @@ class PersonEditIRCNicknamesView(LaunchpadFormView):
self.newnick = nick
self.newnetwork = network
self.request.response.addErrorNotification(
- "Neither Nickname nor Network can be empty.")
+ "Neither Nickname nor Network can be empty."
+ )
class PersonEditJabberIDsView(LaunchpadFormView):
schema = IJabberID
- field_names = ['jabberid']
+ field_names = ["jabberid"]
def setUpFields(self):
super().setUpFields()
@@ -2299,7 +2400,7 @@ class PersonEditJabberIDsView(LaunchpadFormView):
# Make the jabberid entry optional on the edit page if one or more
# ids already exist, which allows the removal of ids without
# filling out the new jabberid field.
- jabber_field = self.form_fields['jabberid']
+ jabber_field = self.form_fields["jabberid"]
# Copy the field so as not to modify the interface.
jabber_field.field = copy_field(jabber_field.field)
jabber_field.field.required = False
@@ -2318,32 +2419,36 @@ class PersonEditJabberIDsView(LaunchpadFormView):
def validate(self, data):
"""Ensure the edited data does not already exist."""
- jabberid = data.get('jabberid')
+ jabberid = data.get("jabberid")
if jabberid is not None:
jabberset = getUtility(IJabberIDSet)
existingjabber = jabberset.getByJabberID(jabberid)
if existingjabber is not None:
if existingjabber.person != self.context:
self.setFieldError(
- 'jabberid',
+ "jabberid",
structured(
- 'The Jabber ID %s is already registered by '
+ "The Jabber ID %s is already registered by "
'<a href="%s">%s</a>.',
- jabberid, canonical_url(existingjabber.person),
- existingjabber.person.displayname))
+ jabberid,
+ canonical_url(existingjabber.person),
+ existingjabber.person.displayname,
+ ),
+ )
else:
self.setFieldError(
- 'jabberid',
- 'The Jabber ID %s already belongs to you.' % jabberid)
+ "jabberid",
+ "The Jabber ID %s already belongs to you." % jabberid,
+ )
@action(_("Save Changes"), name="save")
def save(self, action, data):
"""Process the Jabber ID form."""
form = self.request.form
for jabber in self.context.jabberids:
- if form.get('remove_%s' % jabber.jabberid):
+ if form.get("remove_%s" % jabber.jabberid):
jabber.destroySelf()
- jabberid = data.get('jabberid')
+ jabberid = data.get("jabberid")
if jabberid is not None:
jabberset = getUtility(IJabberIDSet)
jabberset.new(self.context, jabberid)
@@ -2356,17 +2461,17 @@ class PersonEditSSHKeysView(LaunchpadView):
error_message = None
def initialize(self):
- require_fresh_login(self.request, self.context, '+editsshkeys')
+ require_fresh_login(self.request, self.context, "+editsshkeys")
if self.request.method != "POST":
# Nothing to do
return
- action = self.request.form.get('action')
+ action = self.request.form.get("action")
- if action == 'add_ssh':
+ if action == "add_ssh":
self.add_ssh()
- elif action == 'remove_ssh':
+ elif action == "remove_ssh":
self.remove_ssh()
else:
raise UnexpectedFormData("Unexpected action: %s" % action)
@@ -2382,23 +2487,24 @@ class PersonEditSSHKeysView(LaunchpadView):
return canonical_url(self.context, view_name="+edit")
def add_ssh(self):
- sshkey = self.request.form.get('sshkey')
+ sshkey = self.request.form.get("sshkey")
try:
getUtility(ISSHKeySet).new(self.user, sshkey)
except SSHKeyAdditionError:
- self.error_message = structured('Invalid public key')
+ self.error_message = structured("Invalid public key")
else:
- self.info_message = structured('SSH public key added.')
+ self.info_message = structured("SSH public key added.")
def remove_ssh(self):
- key_id = self.request.form.get('key')
+ key_id = self.request.form.get("key")
if not key_id:
- raise UnexpectedFormData('SSH Key was not defined')
+ raise UnexpectedFormData("SSH Key was not defined")
sshkey = getUtility(ISSHKeySet).getByID(key_id)
if sshkey is None:
self.error_message = structured(
- "Cannot remove a key that doesn't exist")
+ "Cannot remove a key that doesn't exist"
+ )
return
if sshkey.person != self.user:
@@ -2430,7 +2536,7 @@ class PersonGPGView(LaunchpadView):
info_message = None
def initialize(self):
- require_fresh_login(self.request, self.context, '+editpgpkeys')
+ require_fresh_login(self.request, self.context, "+editpgpkeys")
super().initialize()
@property
@@ -2445,19 +2551,20 @@ class PersonGPGView(LaunchpadView):
def keyserver_url(self):
assert self.fingerprint
- return getUtility(
- IGPGHandler).getURLForKeyInServer(self.fingerprint, public=True)
+ return getUtility(IGPGHandler).getURLForKeyInServer(
+ self.fingerprint, public=True
+ )
def form_action(self):
if self.request.method != "POST":
- return ''
+ return ""
permitted_actions = [
- 'claim_gpg',
- 'deactivate_gpg',
- 'remove_gpgtoken',
- 'reactivate_gpg',
- ]
- action = self.request.form.get('action')
+ "claim_gpg",
+ "deactivate_gpg",
+ "remove_gpgtoken",
+ "reactivate_gpg",
+ ]
+ action = self.request.form.get("action")
if action not in permitted_actions:
raise UnexpectedFormData("Action not permitted: %s" % action)
getattr(self, action)()
@@ -2466,7 +2573,7 @@ class PersonGPGView(LaunchpadView):
# XXX cprov 2005-04-01: As "Claim GPG key" takes a lot of time, we
# should process it throught the NotificationEngine.
gpghandler = getUtility(IGPGHandler)
- fingerprint = self.request.form.get('fingerprint')
+ fingerprint = self.request.form.get("fingerprint")
self.fingerprint = gpghandler.sanitizeFingerprint(fingerprint)
if not self.fingerprint:
@@ -2492,11 +2599,12 @@ class PersonGPGView(LaunchpadView):
self.key_ok = True
def deactivate_gpg(self):
- key_fingerprints = self.request.form.get('DEACTIVATE_GPGKEY')
+ key_fingerprints = self.request.form.get("DEACTIVATE_GPGKEY")
if key_fingerprints is None:
self.error_message = structured(
- 'No key(s) selected for deactivation.')
+ "No key(s) selected for deactivation."
+ )
return
# verify if we have multiple entries to deactive
@@ -2511,21 +2619,24 @@ class PersonGPGView(LaunchpadView):
continue
if gpgkey.owner != self.user:
self.error_message = structured(
- "Cannot deactivate someone else's key")
+ "Cannot deactivate someone else's key"
+ )
return
gpgkeyset.deactivate(gpgkey)
deactivated_keys.append(gpgkey.displayname)
flush_database_updates()
self.info_message = structured(
- 'Deactivated key(s): %s', ", ".join(deactivated_keys))
+ "Deactivated key(s): %s", ", ".join(deactivated_keys)
+ )
def remove_gpgtoken(self):
- token_fingerprints = self.request.form.get('REMOVE_GPGTOKEN')
+ token_fingerprints = self.request.form.get("REMOVE_GPGTOKEN")
if token_fingerprints is None:
self.error_message = structured(
- 'No key(s) pending validation selected.')
+ "No key(s) pending validation selected."
+ )
return
logintokenset = getUtility(ILoginTokenSet)
@@ -2535,21 +2646,25 @@ class PersonGPGView(LaunchpadView):
cancelled_fingerprints = []
for fingerprint in token_fingerprints:
logintokenset.deleteByFingerprintRequesterAndType(
- fingerprint, self.user, LoginTokenType.VALIDATEGPG)
+ fingerprint, self.user, LoginTokenType.VALIDATEGPG
+ )
logintokenset.deleteByFingerprintRequesterAndType(
- fingerprint, self.user, LoginTokenType.VALIDATESIGNONLYGPG)
+ fingerprint, self.user, LoginTokenType.VALIDATESIGNONLYGPG
+ )
cancelled_fingerprints.append(fingerprint)
self.info_message = structured(
- 'Cancelled validation of key(s): %s',
- ", ".join(cancelled_fingerprints))
+ "Cancelled validation of key(s): %s",
+ ", ".join(cancelled_fingerprints),
+ )
def reactivate_gpg(self):
- key_fingerprints = self.request.form.get('REACTIVATE_GPGKEY')
+ key_fingerprints = self.request.form.get("REACTIVATE_GPGKEY")
if key_fingerprints is None:
self.error_message = structured(
- 'No key(s) selected for reactivation.')
+ "No key(s) selected for reactivation."
+ )
return
found = []
@@ -2574,27 +2689,30 @@ class PersonGPGView(LaunchpadView):
comments = []
if len(found) > 0:
comments.append(
- 'A message has been sent to %s with instructions to '
- 'reactivate these key(s): %s'
- % (self.context.preferredemail.email, ', '.join(found)))
+ "A message has been sent to %s with instructions to "
+ "reactivate these key(s): %s"
+ % (self.context.preferredemail.email, ", ".join(found))
+ )
if len(notfound) > 0:
if len(notfound) == 1:
comments.append(
- 'Launchpad failed to retrieve this key from '
- 'the keyserver: %s. Please make sure the key is '
- 'published in a keyserver (such as '
+ "Launchpad failed to retrieve this key from "
+ "the keyserver: %s. Please make sure the key is "
+ "published in a keyserver (such as "
'<a href="http://pgp.mit.edu">pgp.mit.edu</a>) before '
- 'trying to reactivate it again.' % (', '.join(notfound)))
+ "trying to reactivate it again." % (", ".join(notfound))
+ )
else:
comments.append(
- 'Launchpad failed to retrieve these keys from '
- 'the keyserver: %s. Please make sure the keys '
- 'are published in a keyserver (such as '
+ "Launchpad failed to retrieve these keys from "
+ "the keyserver: %s. Please make sure the keys "
+ "are published in a keyserver (such as "
'<a href="http://pgp.mit.edu">pgp.mit.edu</a>) '
- 'before trying to reactivate them '
- 'again.' % (', '.join(notfound)))
+ "before trying to reactivate them "
+ "again." % (", ".join(notfound))
+ )
- self.info_message = structured('\n<br />\n'.join(comments))
+ self.info_message = structured("\n<br />\n".join(comments))
def _validateGPG(self, key):
bag = getUtility(ILaunchBag)
@@ -2607,8 +2725,12 @@ class PersonGPGView(LaunchpadView):
tokentype = LoginTokenType.VALIDATESIGNONLYGPG
token = getUtility(ILoginTokenSet).new(
- self.context, login, preferredemail, tokentype,
- fingerprint=key.fingerprint)
+ self.context,
+ login,
+ preferredemail,
+ tokentype,
+ fingerprint=key.fingerprint,
+ )
token.sendGPGValidationRequest(key)
@@ -2633,14 +2755,21 @@ class BasePersonEditView(LaunchpadEditFormView):
class PersonEditView(PersonRenameFormMixin, BasePersonEditView):
"""The Person 'Edit' page."""
- field_names = ['display_name', 'name', 'mugshot', 'description',
- 'hide_email_addresses', 'verbose_bugnotifications',
- 'selfgenerated_bugnotifications',
- 'expanded_notification_footers']
+ field_names = [
+ "display_name",
+ "name",
+ "mugshot",
+ "description",
+ "hide_email_addresses",
+ "verbose_bugnotifications",
+ "selfgenerated_bugnotifications",
+ "expanded_notification_footers",
+ ]
custom_widget_mugshot = CustomWidgetFactory(
- ImageChangeWidget, ImageChangeWidget.EDIT_STYLE)
+ ImageChangeWidget, ImageChangeWidget.EDIT_STYLE
+ )
- label = 'Change your personal details'
+ label = "Change your personal details"
page_title = label
# Will contain an hidden input when the user is renaming their
@@ -2655,11 +2784,12 @@ class PersonEditView(PersonRenameFormMixin, BasePersonEditView):
if not is_description_text_linkified(self.context):
self.widgets["description"].hint = _(
"Details about interests and goals. Use plain text, "
- "paragraphs are preserved.")
+ "paragraphs are preserved."
+ )
def validate(self, data):
"""If the name changed, warn the user about the implications."""
- new_name = data.get('name')
+ new_name = data.get("name")
# Name was not changed, carry on ...
if not new_name or new_name == self.context.name:
@@ -2669,15 +2799,20 @@ class PersonEditView(PersonRenameFormMixin, BasePersonEditView):
try:
username_validator(new_name)
except LaunchpadValidationError as err:
- self.setFieldError('name', str(err))
+ self.setFieldError("name", str(err))
return
# Ensure the user is aware of the implications of changing username.
bypass_check = self.request.form_ng.getOne(
- 'i_know_this_is_an_openid_security_issue', 0)
+ "i_know_this_is_an_openid_security_issue", 0
+ )
if not bypass_check:
# Warn the user that they might shoot themselves in the foot.
- self.setFieldError('name', structured(dedent('''
+ self.setFieldError(
+ "name",
+ structured(
+ dedent(
+ """
<div class="inline-warning">
<p>Changing your name will change your
public OpenID identifier. This means that you might be
@@ -2691,12 +2826,17 @@ class PersonEditView(PersonRenameFormMixin, BasePersonEditView):
<p>If you click 'Save' again, we will rename your account
anyway.
</p>
- </div>'''),))
- self.i_know_this_is_an_openid_security_issue_input = dedent("""\
+ </div>"""
+ ),
+ ),
+ )
+ self.i_know_this_is_an_openid_security_issue_input = dedent(
+ """\
<input type="hidden"
id="i_know_this_is_an_openid_security_issue"
name="i_know_this_is_an_openid_security_issue"
- value="1">""")
+ value="1">"""
+ )
@action(_("Save Changes"), name="save")
def action_save(self, action, data):
@@ -2705,18 +2845,19 @@ class PersonEditView(PersonRenameFormMixin, BasePersonEditView):
# change their name, but the permission setting for the attribute is
# launchpad.Moderate, which only allows admins and registry. A user
# must have launchpad.Edit to access this page.
- if 'name' in data:
- new_name = data['name']
+ if "name" in data:
+ new_name = data["name"]
removeSecurityProxy(self.context).name = new_name
- del data['name']
+ del data["name"]
self.updateContextFromData(data)
self.request.response.addInfoNotification(
- 'The changes to your personal details have been saved.')
+ "The changes to your personal details have been saved."
+ )
class PersonBrandingView(BrandingChangeView):
- field_names = ['logo', 'mugshot']
+ field_names = ["logo", "mugshot"]
schema = IPerson
@@ -2732,17 +2873,19 @@ class PersonEditEmailsView(LaunchpadFormView):
schema = IEmailAddress
custom_widget_VALIDATED_SELECTED = CustomWidgetFactory(
- LaunchpadRadioWidget, orientation='vertical')
+ LaunchpadRadioWidget, orientation="vertical"
+ )
custom_widget_UNVALIDATED_SELECTED = CustomWidgetFactory(
- LaunchpadRadioWidget, orientation='vertical')
+ LaunchpadRadioWidget, orientation="vertical"
+ )
- label = 'Change your email settings'
+ label = "Change your email settings"
def initialize(self):
- require_fresh_login(self.request, self.context, '+editemails')
+ require_fresh_login(self.request, self.context, "+editemails")
if self.context.is_team:
# +editemails is not available on teams.
- name = self.request['PATH_INFO'].split('/')[-1]
+ name = self.request["PATH_INFO"].split("/")[-1]
raise NotFound(self, name, request=self.request)
super().initialize()
@@ -2754,10 +2897,13 @@ class PersonEditEmailsView(LaunchpadFormView):
addresses.
"""
super().setUpFields()
- self.form_fields = (self._validated_emails_field() +
- self._unvalidated_emails_field() +
- FormFields(TextLine(__name__='newemail',
- title='Add a new address')))
+ self.form_fields = (
+ self._validated_emails_field()
+ + self._unvalidated_emails_field()
+ + FormFields(
+ TextLine(__name__="newemail", title="Add a new address")
+ )
+ )
@property
def initial_values(self):
@@ -2778,25 +2924,30 @@ class PersonEditEmailsView(LaunchpadFormView):
unvalidated = self.unvalidated_addresses
if len(unvalidated) > 0:
unvalidated = unvalidated.pop()
- return dict(VALIDATED_SELECTED=validated,
- UNVALIDATED_SELECTED=unvalidated)
+ return dict(
+ VALIDATED_SELECTED=validated, UNVALIDATED_SELECTED=unvalidated
+ )
def _validated_emails_field(self):
"""Create a field with a vocabulary of validated emails.
:return: A Choice field containing the list of validated emails
"""
- terms = [SimpleTerm(term, term.email)
- for term in self.context.validatedemails]
+ terms = [
+ SimpleTerm(term, term.email)
+ for term in self.context.validatedemails
+ ]
preferred = self.context.preferredemail
if preferred:
terms.insert(0, SimpleTerm(preferred, preferred.email))
return FormFields(
- Choice(__name__='VALIDATED_SELECTED',
- title=_('These addresses are confirmed as being yours'),
- source=SimpleVocabulary(terms),
- ))
+ Choice(
+ __name__="VALIDATED_SELECTED",
+ title=_("These addresses are confirmed as being yours"),
+ source=SimpleVocabulary(terms),
+ )
+ )
def _unvalidated_emails_field(self):
"""Create a field with a vocabulary of unvalidated and guessed emails.
@@ -2811,15 +2962,19 @@ class PersonEditEmailsView(LaunchpadFormView):
term = SimpleTerm(term, term.email)
terms.append(term)
if self.validated_addresses:
- title = _('These addresses may also be yours')
+ title = _("These addresses may also be yours")
else:
- title = _('These addresses may be yours')
+ title = _("These addresses may be yours")
return FormFields(
- Choice(__name__='UNVALIDATED_SELECTED', title=title,
- source=SimpleVocabulary(terms)))
-
- def _validate_selected_address(self, data, field='VALIDATED_SELECTED'):
+ Choice(
+ __name__="UNVALIDATED_SELECTED",
+ title=title,
+ source=SimpleVocabulary(terms),
+ )
+ )
+
+ def _validate_selected_address(self, data, field="VALIDATED_SELECTED"):
"""A generic validator for this view's actions.
Makes sure one (and only one) email address is selected and that
@@ -2844,16 +2999,24 @@ class PersonEditEmailsView(LaunchpadFormView):
assert person == self.context, (
"differing ids in emailaddress.person.id(%s,%d) == "
"self.context.id(%s,%d) (%s)"
- % (person.name, person.id, self.context.name, self.context.id,
- email.email))
+ % (
+ person.name,
+ person.id,
+ self.context.name,
+ self.context.id,
+ email.email,
+ )
+ )
elif isinstance(email, str):
tokenset = getUtility(ILoginTokenSet)
email = tokenset.searchByEmailRequesterAndType(
- email, self.context, LoginTokenType.VALIDATEEMAIL)
+ email, self.context, LoginTokenType.VALIDATEEMAIL
+ )
assert email is not None, "Couldn't find login token!"
else:
- raise AssertionError("Selected address was not EmailAddress "
- "or unicode string!")
+ raise AssertionError(
+ "Selected address was not EmailAddress " "or unicode string!"
+ )
# Return the EmailAddress/LoginToken object for use in any
# further validation.
@@ -2879,110 +3042,136 @@ class PersonEditEmailsView(LaunchpadFormView):
"""
emailset = set(self.context.unvalidatedemails)
emailset = emailset.union(
- [guessed for guessed in self.context.guessedemails
- if guessed.email not in emailset])
+ [
+ guessed
+ for guessed in self.context.guessedemails
+ if guessed.email not in emailset
+ ]
+ )
return emailset
def validate_action_remove_validated(self, action, data):
"""Make sure the user selected an email address to remove."""
- emailaddress = self._validate_selected_address(data,
- 'VALIDATED_SELECTED')
+ emailaddress = self._validate_selected_address(
+ data, "VALIDATED_SELECTED"
+ )
if emailaddress is None:
return self.errors
if self.context.preferredemail == emailaddress:
self.addError(
"You can't remove %s because it's your contact email "
- "address." % self.context.preferredemail.email)
+ "address." % self.context.preferredemail.email
+ )
return self.errors
return self.errors
- @action(_("Remove"), name="remove_validated",
- validator=validate_action_remove_validated)
+ @action(
+ _("Remove"),
+ name="remove_validated",
+ validator=validate_action_remove_validated,
+ )
def action_remove_validated(self, action, data):
"""Delete the selected (validated) email address."""
- emailaddress = data['VALIDATED_SELECTED']
+ emailaddress = data["VALIDATED_SELECTED"]
emailaddress.destroySelf()
self.request.response.addInfoNotification(
- "The email address '%s' has been removed." % emailaddress.email)
+ "The email address '%s' has been removed." % emailaddress.email
+ )
self.next_url = self.action_url
def validate_action_set_preferred(self, action, data):
"""Make sure the user selected an address."""
- emailaddress = self._validate_selected_address(data,
- 'VALIDATED_SELECTED')
+ emailaddress = self._validate_selected_address(
+ data, "VALIDATED_SELECTED"
+ )
if emailaddress is None:
return self.errors
if emailaddress.status == EmailAddressStatus.PREFERRED:
self.request.response.addInfoNotification(
- "%s is already set as your contact address." % (
- emailaddress.email))
+ "%s is already set as your contact address."
+ % (emailaddress.email)
+ )
return self.errors
- @action(_("Set as Contact Address"), name="set_preferred",
- validator=validate_action_set_preferred)
+ @action(
+ _("Set as Contact Address"),
+ name="set_preferred",
+ validator=validate_action_set_preferred,
+ )
def action_set_preferred(self, action, data):
"""Set the selected email as preferred for the person in context."""
- emailaddress = data['VALIDATED_SELECTED']
+ emailaddress = data["VALIDATED_SELECTED"]
if emailaddress.status != EmailAddressStatus.PREFERRED:
self.context.setPreferredEmail(emailaddress)
self.request.response.addInfoNotification(
- "Your contact address has been changed to: %s" % (
- emailaddress.email))
+ "Your contact address has been changed to: %s"
+ % (emailaddress.email)
+ )
self.next_url = self.action_url
def validate_action_confirm(self, action, data):
"""Make sure the user selected an email address to confirm."""
- self._validate_selected_address(data, 'UNVALIDATED_SELECTED')
+ self._validate_selected_address(data, "UNVALIDATED_SELECTED")
return self.errors
- @action(_('Confirm'), name='validate', validator=validate_action_confirm)
+ @action(_("Confirm"), name="validate", validator=validate_action_confirm)
def action_confirm(self, action, data):
"""Mail a validation URL to the selected email address."""
- email = data['UNVALIDATED_SELECTED']
+ email = data["UNVALIDATED_SELECTED"]
if IEmailAddress.providedBy(email):
email = email.email
token = getUtility(ILoginTokenSet).new(
- self.context, getUtility(ILaunchBag).login, email,
- LoginTokenType.VALIDATEEMAIL)
+ self.context,
+ getUtility(ILaunchBag).login,
+ email,
+ LoginTokenType.VALIDATEEMAIL,
+ )
token.sendEmailValidationRequest()
self.request.response.addInfoNotification(
"An email message was sent to '%s' with "
"instructions on how to confirm that "
- "it belongs to you." % email)
+ "it belongs to you." % email
+ )
self.next_url = self.action_url
def validate_action_remove_unvalidated(self, action, data):
"""Make sure the user selected an email address to remove."""
- email = self._validate_selected_address(data, 'UNVALIDATED_SELECTED')
+ email = self._validate_selected_address(data, "UNVALIDATED_SELECTED")
if email is not None and IEmailAddress.providedBy(email):
assert self.context.preferredemail.id != email.id
return self.errors
- @action(_("Remove"), name="remove_unvalidated",
- validator=validate_action_remove_unvalidated)
+ @action(
+ _("Remove"),
+ name="remove_unvalidated",
+ validator=validate_action_remove_unvalidated,
+ )
def action_remove_unvalidated(self, action, data):
"""Delete the selected (un-validated) email address.
This selected address can be either on the EmailAddress table
marked with status NEW, or in the LoginToken table.
"""
- emailaddress = data['UNVALIDATED_SELECTED']
+ emailaddress = data["UNVALIDATED_SELECTED"]
if IEmailAddress.providedBy(emailaddress):
emailaddress.destroySelf()
email = emailaddress.email
elif isinstance(emailaddress, str):
logintokenset = getUtility(ILoginTokenSet)
logintokenset.deleteByEmailRequesterAndType(
- emailaddress, self.context, LoginTokenType.VALIDATEEMAIL)
+ emailaddress, self.context, LoginTokenType.VALIDATEEMAIL
+ )
email = emailaddress
else:
- raise AssertionError("Selected address was not EmailAddress "
- "or Unicode string!")
+ raise AssertionError(
+ "Selected address was not EmailAddress " "or Unicode string!"
+ )
self.request.response.addInfoNotification(
- "The email address '%s' has been removed." % email)
+ "The email address '%s' has been removed." % email
+ )
self.next_url = self.action_url
def validate_action_add_email(self, action, data):
@@ -2991,15 +3180,16 @@ class PersonEditEmailsView(LaunchpadFormView):
The email address must be syntactically valid and must not already
be in use.
"""
- has_errors = bool(self.validate_widgets(data, ['newemail']))
+ has_errors = bool(self.validate_widgets(data, ["newemail"]))
if has_errors:
# We know that 'newemail' is empty.
return self.errors
- newemail = data['newemail']
+ newemail = data["newemail"]
if not valid_email(newemail):
self.addError(
- "'%s' doesn't seem to be a valid email address." % newemail)
+ "'%s' doesn't seem to be a valid email address." % newemail
+ )
return self.errors
# XXX j.c.sackett 2010-09-15 bug=628247, 576757 There is a validation
@@ -3017,38 +3207,49 @@ class PersonEditEmailsView(LaunchpadFormView):
"added this email address before or because our system "
"detected it as being yours. If it was detected by our "
"system, it's probably shown on this page and is waiting "
- "to be confirmed as yours." % newemail)
+ "to be confirmed as yours." % newemail
+ )
else:
owner = email.person
owner_name = quote(owner.name)
- merge_url = (
- '%s/+requestmerge?field.dupe_person=%s'
- % (canonical_url(getUtility(IPersonSet)), owner_name))
- self.addError(structured(
- "The email address '%s' is already registered to "
- '<a href="%s">%s</a>. If you think that is a '
- 'duplicated account, you can <a href="%s">merge it'
- "</a> into your account.",
- newemail, canonical_url(owner), owner.displayname,
- merge_url))
+ merge_url = "%s/+requestmerge?field.dupe_person=%s" % (
+ canonical_url(getUtility(IPersonSet)),
+ owner_name,
+ )
+ self.addError(
+ structured(
+ "The email address '%s' is already registered to "
+ '<a href="%s">%s</a>. If you think that is a '
+ 'duplicated account, you can <a href="%s">merge it'
+ "</a> into your account.",
+ newemail,
+ canonical_url(owner),
+ owner.displayname,
+ merge_url,
+ )
+ )
return self.errors
@action(_("Add"), name="add_email", validator=validate_action_add_email)
def action_add_email(self, action, data):
"""Register a new email for the person in context."""
- newemail = data['newemail']
+ newemail = data["newemail"]
token = getUtility(ILoginTokenSet).new(
- self.context, getUtility(ILaunchBag).login, newemail,
- LoginTokenType.VALIDATEEMAIL)
+ self.context,
+ getUtility(ILaunchBag).login,
+ newemail,
+ LoginTokenType.VALIDATEEMAIL,
+ )
token.sendEmailValidationRequest()
self.request.response.addInfoNotification(
- "A confirmation message has been sent to '%s'. "
- "Follow the instructions in that message to confirm that the "
- "address is yours. "
- "(If the message doesn't arrive in a few minutes, your mail "
- "provider might use 'greylisting', which could delay the "
- "message for up to an hour or two.)" % newemail)
+ "A confirmation message has been sent to '%s'. "
+ "Follow the instructions in that message to confirm that the "
+ "address is yours. "
+ "(If the message doesn't arrive in a few minutes, your mail "
+ "provider might use 'greylisting', which could delay the "
+ "message for up to an hour or two.)" % newemail
+ )
self.next_url = self.action_url
@@ -3059,14 +3260,15 @@ class PersonEditMailingListsView(LaunchpadFormView):
schema = IEmailAddress
custom_widget_mailing_list_auto_subscribe_policy = (
- LaunchpadRadioWidgetWithDescription)
+ LaunchpadRadioWidgetWithDescription
+ )
- label = 'Change your mailing list subscriptions'
+ label = "Change your mailing list subscriptions"
def initialize(self):
if self.context.is_team:
# +editmailinglists is not available on teams.
- name = self.request['PATH_INFO'].split('/')[-1]
+ name = self.request["PATH_INFO"].split("/")[-1]
raise NotFound(self, name, request=self.request)
super().initialize()
@@ -3078,8 +3280,9 @@ class PersonEditMailingListsView(LaunchpadFormView):
addresses.
"""
super().setUpFields()
- self.form_fields = (self._mailing_list_fields()
- + self._autosubscribe_policy_fields())
+ self.form_fields = (
+ self._mailing_list_fields() + self._autosubscribe_policy_fields()
+ )
@property
def initial_values(self):
@@ -3096,12 +3299,14 @@ class PersonEditMailingListsView(LaunchpadFormView):
# Defaults for the mailing list autosubscribe buttons.
return dict(
mailing_list_auto_subscribe_policy=(
- self.context.mailing_list_auto_subscribe_policy))
+ self.context.mailing_list_auto_subscribe_policy
+ )
+ )
def setUpWidgets(self, context=None):
"""See `LaunchpadFormView`."""
super().setUpWidgets(context)
- widget = self.widgets['mailing_list_auto_subscribe_policy']
+ widget = self.widgets["mailing_list_auto_subscribe_policy"]
widget.display_label = False
def _mailing_list_subscription_type(self, mailing_list):
@@ -3116,7 +3321,7 @@ class PersonEditMailingListsView(LaunchpadFormView):
if subscription is None:
return "Don't subscribe"
elif subscription.email_address is None:
- return 'Preferred address'
+ return "Preferred address"
else:
return subscription.email_address
@@ -3131,27 +3336,35 @@ class PersonEditMailingListsView(LaunchpadFormView):
terms = [
SimpleTerm("Preferred address"),
SimpleTerm("Don't subscribe"),
- ]
+ ]
for email in self.validated_addresses:
terms.append(SimpleTerm(email, email.email))
for team in self.context.teams_participated_in:
mailing_list = mailing_list_set.get(team.name)
if mailing_list is not None and mailing_list.is_usable:
- name = 'subscription.%s' % team.name
+ name = "subscription.%s" % team.name
value = self._mailing_list_subscription_type(mailing_list)
- field = Choice(__name__=name,
- title=team.name,
- source=SimpleVocabulary(terms), default=value)
+ field = Choice(
+ __name__=name,
+ title=team.name,
+ source=SimpleVocabulary(terms),
+ default=value,
+ )
fields.append(field)
return FormFields(*fields)
def _autosubscribe_policy_fields(self):
"""Create a field for each mailing list auto-subscription option."""
return FormFields(
- Choice(__name__='mailing_list_auto_subscribe_policy',
- title=_('When should Launchpad automatically subscribe '
- 'you to a team’s mailing list?'),
- source=MailingListAutoSubscribePolicy))
+ Choice(
+ __name__="mailing_list_auto_subscribe_policy",
+ title=_(
+ "When should Launchpad automatically subscribe "
+ "you to a team’s mailing list?"
+ ),
+ source=MailingListAutoSubscribePolicy,
+ )
+ )
@property
def mailing_list_widgets(self):
@@ -3159,14 +3372,14 @@ class PersonEditMailingListsView(LaunchpadFormView):
mailing_list_set = getUtility(IMailingListSet)
widgets = []
for widget in self.widgets:
- if widget.name.startswith('field.subscription.'):
+ if widget.name.startswith("field.subscription."):
team_name = widget.label
mailing_list = mailing_list_set.get(team_name)
- assert mailing_list is not None, 'Missing mailing list'
+ assert mailing_list is not None, "Missing mailing list"
widget_dict = dict(
team=mailing_list.team,
widget=widget,
- )
+ )
widgets.append(widget_dict)
# We'll put the label in the first column, so don't include it
# in the second column.
@@ -3190,20 +3403,25 @@ class PersonEditMailingListsView(LaunchpadFormView):
Valid addresses are the ones presented as options for the mailing
list widgets.
"""
- names = [widget_dict['widget'].context.getName()
- for widget_dict in self.mailing_list_widgets]
+ names = [
+ widget_dict["widget"].context.getName()
+ for widget_dict in self.mailing_list_widgets
+ ]
self.validate_widgets(data, names)
return self.errors
- @action(_("Update Subscriptions"), name="update_subscriptions",
- validator=validate_action_update_subscriptions)
+ @action(
+ _("Update Subscriptions"),
+ name="update_subscriptions",
+ validator=validate_action_update_subscriptions,
+ )
def action_update_subscriptions(self, action, data):
"""Change the user's mailing list subscriptions."""
mailing_list_set = getUtility(IMailingListSet)
dirty = False
- prefix_length = len('subscription.')
+ prefix_length = len("subscription.")
for widget_dict in self.mailing_list_widgets:
- widget = widget_dict['widget']
+ widget = widget_dict["widget"]
mailing_list_name = widget.context.getName()[prefix_length:]
mailing_list = mailing_list_set.get(mailing_list_name)
new_value = data[widget.context.getName()]
@@ -3229,8 +3447,7 @@ class PersonEditMailingListsView(LaunchpadFormView):
else:
mailing_list.changeAddress(self.context, new_value)
if dirty:
- self.request.response.addInfoNotification(
- "Subscriptions updated.")
+ self.request.response.addInfoNotification("Subscriptions updated.")
self.next_url = self.action_url
def validate_action_update_autosubscribe_policy(self, action, data):
@@ -3240,19 +3457,21 @@ class PersonEditMailingListsView(LaunchpadFormView):
# required for LaunchpadFormView to tell apart the three <form>
# elements on the page.
- widget = self.widgets['mailing_list_auto_subscribe_policy']
+ widget = self.widgets["mailing_list_auto_subscribe_policy"]
self.validate_widgets(data, widget.name)
return self.errors
@action(
- _('Update Policy'),
+ _("Update Policy"),
name="update_autosubscribe_policy",
- validator=validate_action_update_autosubscribe_policy)
+ validator=validate_action_update_autosubscribe_policy,
+ )
def action_update_autosubscribe_policy(self, action, data):
- newpolicy = data['mailing_list_auto_subscribe_policy']
+ newpolicy = data["mailing_list_auto_subscribe_policy"]
self.context.mailing_list_auto_subscribe_policy = newpolicy
self.request.response.addInfoNotification(
- 'Your auto-subscribe policy has been updated.')
+ "Your auto-subscribe policy has been updated."
+ )
self.next_url = self.action_url
@@ -3275,6 +3494,7 @@ class BaseWithStats:
@delegate_to(ISourcePackageRelease)
class SourcePackageReleaseWithStats(BaseWithStats):
"""An ISourcePackageRelease, with extra stats added."""
+
pass
@@ -3287,7 +3507,8 @@ class SourcePackagePublishingHistoryWithStats(BaseWithStats):
@implementer(IPersonRelatedSoftwareMenu)
class PersonRelatedSoftwareView(LaunchpadView):
"""View for +related-packages."""
- _max_results_key = 'summary_list_size'
+
+ _max_results_key = "summary_list_size"
@property
def max_results_to_display(self):
@@ -3295,7 +3516,7 @@ class PersonRelatedSoftwareView(LaunchpadView):
@property
def page_title(self):
- return 'Related packages'
+ return "Related packages"
@cachedproperty
def related_projects(self):
@@ -3305,24 +3526,28 @@ class PersonRelatedSoftwareView(LaunchpadView):
A project dict has the following keys: title, url, is_owner,
is_driver, is_bugsupervisor.
"""
+
def decorate(pillarnames):
projects = []
for pillarname in pillarnames:
pillar = pillarname.pillar
project = {}
- project['title'] = pillar.title
- project['url'] = canonical_url(pillar)
+ project["title"] = pillar.title
+ project["url"] = canonical_url(pillar)
person = self.context
- project['is_owner'] = person.inTeam(pillar.owner)
- project['is_driver'] = person.inTeam(pillar.driver)
- project['is_bug_supervisor'] = False
+ project["is_owner"] = person.inTeam(pillar.owner)
+ project["is_driver"] = person.inTeam(pillar.driver)
+ project["is_bug_supervisor"] = False
if IHasBugSupervisor.providedBy(pillar):
- project['is_bug_supervisor'] = (
- person.inTeam(pillar.bug_supervisor))
+ project["is_bug_supervisor"] = person.inTeam(
+ pillar.bug_supervisor
+ )
projects.append(project)
return projects
+
return DecoratedResultSet(
- self._related_projects, bulk_decorator=decorate)
+ self._related_projects, bulk_decorator=decorate
+ )
@cachedproperty
def first_five_related_projects(self):
@@ -3342,7 +3567,8 @@ class PersonRelatedSoftwareView(LaunchpadView):
@cachedproperty
def projects_header_message(self):
return self._tableHeaderMessage(
- self.related_projects_count, label='project')
+ self.related_projects_count, label="project"
+ )
@cachedproperty
def _related_projects(self):
@@ -3350,14 +3576,16 @@ class PersonRelatedSoftwareView(LaunchpadView):
user = getUtility(ILaunchBag).user
return self.context.getAffiliatedPillars(user)
- def _tableHeaderMessage(self, count, label='package'):
+ def _tableHeaderMessage(self, count, label="package"):
"""Format a header message for the tables on the summary page."""
if count > 1:
- label += 's'
+ label += "s"
if count > self.max_results_to_display:
- header_message = (
- "Displaying first %d %s out of %d total" % (
- self.max_results_to_display, label, count))
+ header_message = "Displaying first %d %s out of %d total" % (
+ self.max_results_to_display,
+ label,
+ count,
+ )
else:
header_message = "%d %s" % (count, label)
@@ -3383,7 +3611,7 @@ class PersonRelatedSoftwareView(LaunchpadView):
# Ensure the SPR.upload_archive is also considered.
archives.add(package.upload_archive)
for archive in archives:
- if check_permission('launchpad.View', archive):
+ if check_permission("launchpad.View", archive):
results.append(package)
break
@@ -3402,7 +3630,8 @@ class PersonRelatedSoftwareView(LaunchpadView):
"""
# This code causes two SQL queries to be generated.
results = self._addStatsToPackages(
- packages[:self.max_results_to_display])
+ packages[: self.max_results_to_display]
+ )
header_message = self._tableHeaderMessage(packages.count())
return results, header_message
@@ -3419,7 +3648,8 @@ class PersonRelatedSoftwareView(LaunchpadView):
"""
# This code causes two SQL queries to be generated.
results = self._addStatsToPublishings(
- publishings[:self.max_results_to_display])
+ publishings[: self.max_results_to_display]
+ )
header_message = self._tableHeaderMessage(publishings.count())
return results, header_message
@@ -3456,12 +3686,11 @@ class PersonRelatedSoftwareView(LaunchpadView):
@property
def latest_synchronised_publishings_with_stats(self):
- """Return the latest synchronised publishings, including stats.
-
- """
+ """Return the latest synchronised publishings, including stats."""
publishings = self.context.getLatestSynchronisedPublishings()
results, header_message = self._getDecoratedPublishingsSummary(
- publishings)
+ publishings
+ )
self.synchronised_packages_header_message = header_message
return results
@@ -3477,9 +3706,11 @@ class PersonRelatedSoftwareView(LaunchpadView):
# Calculate all the failed builds with one query.
build_set = getUtility(IBinaryPackageBuildSet)
package_release_ids = [
- package_release.id for package_release in package_releases]
+ package_release.id for package_release in package_releases
+ ]
all_builds = build_set.getBuildsBySourcePackageRelease(
- package_release_ids)
+ package_release_ids
+ )
# Make a dictionary of lists of builds keyed by SourcePackageRelease
# and a dictionary of "needs build" state keyed by the same.
builds_by_package = {}
@@ -3489,42 +3720,54 @@ class PersonRelatedSoftwareView(LaunchpadView):
needs_build_by_package[package.id] = False
for build in all_builds:
if build.status == BuildStatus.FAILEDTOBUILD:
- builds_by_package[
- build.source_package_release.id].append(build)
+ builds_by_package[build.source_package_release.id].append(
+ build
+ )
needs_build = build.status in [
BuildStatus.NEEDSBUILD,
BuildStatus.MANUALDEPWAIT,
BuildStatus.CHROOTWAIT,
- ]
+ ]
needs_build_by_package[
- build.source_package_release.id] = needs_build
+ build.source_package_release.id
+ ] = needs_build
return (builds_by_package, needs_build_by_package)
def _addStatsToPackages(self, package_releases):
"""Add stats to the given package releases, and return them."""
builds_by_package, needs_build_by_package = self._calculateBuildStats(
- package_releases)
+ package_releases
+ )
return [
SourcePackageReleaseWithStats(
- package, builds_by_package[package.id],
- needs_build_by_package[package.id])
- for package in package_releases]
+ package,
+ builds_by_package[package.id],
+ needs_build_by_package[package.id],
+ )
+ for package in package_releases
+ ]
def _addStatsToPublishings(self, publishings):
"""Add stats to the given publishings, and return them."""
filtered_spphs = [
- spph for spph in publishings if
- check_permission('launchpad.View', spph)]
+ spph
+ for spph in publishings
+ if check_permission("launchpad.View", spph)
+ ]
builds_by_package, needs_build_by_package = self._calculateBuildStats(
- [spph.sourcepackagerelease for spph in filtered_spphs])
+ [spph.sourcepackagerelease for spph in filtered_spphs]
+ )
return [
SourcePackagePublishingHistoryWithStats(
- spph, builds_by_package[spph.sourcepackagerelease.id],
- needs_build_by_package[spph.sourcepackagerelease.id])
- for spph in filtered_spphs]
+ spph,
+ builds_by_package[spph.sourcepackagerelease.id],
+ needs_build_by_package[spph.sourcepackagerelease.id],
+ )
+ for spph in filtered_spphs
+ ]
def setUpBatch(self, packages):
"""Set up the batch navigation for the page being viewed.
@@ -3539,7 +3782,8 @@ class PersonRelatedSoftwareView(LaunchpadView):
class PersonMaintainedPackagesView(PersonRelatedSoftwareView):
"""View for +maintained-packages."""
- _max_results_key = 'default_batch_size'
+
+ _max_results_key = "default_batch_size"
def initialize(self):
"""Set up the batch navigation."""
@@ -3553,7 +3797,8 @@ class PersonMaintainedPackagesView(PersonRelatedSoftwareView):
class PersonUploadedPackagesView(PersonRelatedSoftwareView):
"""View for +uploaded-packages."""
- _max_results_key = 'default_batch_size'
+
+ _max_results_key = "default_batch_size"
def initialize(self):
"""Set up the batch navigation."""
@@ -3567,7 +3812,8 @@ class PersonUploadedPackagesView(PersonRelatedSoftwareView):
class PersonPPAPackagesView(PersonRelatedSoftwareView):
"""View for +ppa-packages."""
- _max_results_key = 'default_batch_size'
+
+ _max_results_key = "default_batch_size"
def initialize(self):
"""Set up the batch navigation."""
@@ -3588,7 +3834,8 @@ class PersonPPAPackagesView(PersonRelatedSoftwareView):
class PersonSynchronisedPackagesView(PersonRelatedSoftwareView):
"""View for +synchronised-packages."""
- _max_results_key = 'default_batch_size'
+
+ _max_results_key = "default_batch_size"
def initialize(self):
"""Set up the batch navigation."""
@@ -3612,12 +3859,12 @@ class PersonSynchronisedPackagesView(PersonRelatedSoftwareView):
class PersonRelatedProjectsView(PersonRelatedSoftwareView):
"""View for +related-projects."""
- _max_results_key = 'default_batch_size'
+
+ _max_results_key = "default_batch_size"
def initialize(self):
"""Set up the batch navigation."""
- self.batchnav = BatchNavigator(
- self.related_projects, self.request)
+ self.batchnav = BatchNavigator(self.related_projects, self.request)
self.batch = list(self.batchnav.currentBatch())
@property
@@ -3627,62 +3874,71 @@ class PersonRelatedProjectsView(PersonRelatedSoftwareView):
class PersonOwnedTeamsView(PersonRelatedSoftwareView):
"""View for +owned-teams."""
+
page_title = "Owned teams"
def initialize(self):
"""Set up the batch navigation."""
self.batchnav = BatchNavigator(
- self.context.getOwnedTeams(self.user), self.request)
- self.batchnav.setHeadings('team', 'teams')
+ self.context.getOwnedTeams(self.user), self.request
+ )
+ self.batchnav.setHeadings("team", "teams")
self.batch = list(self.batchnav.currentBatch())
class PersonOAuthTokensView(LaunchpadView):
"""Where users can see/revoke their non-expired access tokens."""
- label = 'Authorized applications'
+ label = "Authorized applications"
def initialize(self):
- if self.request.method == 'POST':
+ if self.request.method == "POST":
self.expireToken()
@property
def access_tokens(self):
return sorted(
self.context.oauth_access_tokens,
- key=lambda token: token.consumer.key)
+ key=lambda token: token.consumer.key,
+ )
@property
def request_tokens(self):
return sorted(
self.context.oauth_request_tokens,
- key=lambda token: token.consumer.key)
+ key=lambda token: token.consumer.key,
+ )
def expireToken(self):
"""Expire the token with the key contained in the request's form."""
form = self.request.form
consumer = getUtility(IOAuthConsumerSet).getByKey(
- form.get('consumer_key'))
- token_key = form.get('token_key')
- token_type = form.get('token_type')
- if token_type == 'access_token':
+ form.get("consumer_key")
+ )
+ token_key = form.get("token_key")
+ token_type = form.get("token_type")
+ if token_type == "access_token":
token = consumer.getAccessToken(token_key)
- elif token_type == 'request_token':
+ elif token_type == "request_token":
token = consumer.getRequestToken(token_key)
else:
- raise UnexpectedFormData("Invalid form value for token_type: %r"
- % token_type)
+ raise UnexpectedFormData(
+ "Invalid form value for token_type: %r" % token_type
+ )
if token is not None:
- token.date_expires = datetime.now(pytz.timezone('UTC'))
+ token.date_expires = datetime.now(pytz.timezone("UTC"))
self.request.response.addInfoNotification(
- "Authorization revoked successfully.")
+ "Authorization revoked successfully."
+ )
self.request.response.redirect(canonical_url(self.user))
else:
self.request.response.addInfoNotification(
"Couldn't find authorization given to %s. Maybe it has been "
- "revoked already?" % consumer.key)
+ "revoked already?" % consumer.key
+ )
self.request.response.redirect(
- canonical_url(self.context, view_name='+oauth-tokens'))
+ canonical_url(self.context, view_name="+oauth-tokens")
+ )
class PersonOCIRegistryCredentialsView(LaunchpadView):
@@ -3690,8 +3946,9 @@ class PersonOCIRegistryCredentialsView(LaunchpadView):
@cachedproperty
def oci_registry_credentials(self):
- return list(getUtility(
- IOCIRegistryCredentialsSet).findByOwner(self.context))
+ return list(
+ getUtility(IOCIRegistryCredentialsSet).findByOwner(self.context)
+ )
page_title = "OCI registry credentials"
@@ -3723,8 +3980,11 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
@cachedproperty
def oci_registry_credentials(self):
- return list(getUtility(IOCIRegistryCredentialsSet).findByOwner(
- self.default_owner))
+ return list(
+ getUtility(IOCIRegistryCredentialsSet).findByOwner(
+ self.default_owner
+ )
+ )
schema = Interface
@@ -3742,71 +4002,87 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
return "%s.%d" % (name, credentials_id)
def getEditFieldsRow(self, credentials=None):
- id = getattr(credentials, 'id', None)
+ id = getattr(credentials, "id", None)
owner = Choice(
- vocabulary=(
- 'AllUserTeamsParticipationPlusSelfSimpleDisplay'),
+ vocabulary=("AllUserTeamsParticipationPlusSelfSimpleDisplay"),
default=credentials.owner,
- __name__=self._getFieldName('owner', id))
+ __name__=self._getFieldName("owner", id),
+ )
username = TextLine(
- __name__=self._getFieldName('username', id),
+ __name__=self._getFieldName("username", id),
default=credentials.username,
- required=False, readonly=False)
+ required=False,
+ readonly=False,
+ )
password = Password(
- __name__=self._getFieldName('password', id),
+ __name__=self._getFieldName("password", id),
default=None,
- required=False, readonly=False)
+ required=False,
+ readonly=False,
+ )
confirm_password = Password(
- __name__=self._getFieldName('confirm_password', id),
+ __name__=self._getFieldName("confirm_password", id),
default=None,
- required=False, readonly=False)
+ required=False,
+ readonly=False,
+ )
url = TextLine(
- __name__=self._getFieldName('url', id),
+ __name__=self._getFieldName("url", id),
default=credentials.url,
- required=True, readonly=False)
+ required=True,
+ readonly=False,
+ )
region = TextLine(
- __name__=self._getFieldName('region', id),
+ __name__=self._getFieldName("region", id),
default=credentials.region,
- required=False, readonly=False)
+ required=False,
+ readonly=False,
+ )
delete = Bool(
- __name__=self._getFieldName('delete', id),
+ __name__=self._getFieldName("delete", id),
default=False,
- required=True, readonly=False)
+ required=True,
+ readonly=False,
+ )
return owner, username, password, confirm_password, url, region, delete
def getAddFieldsRow(self):
- add_url = TextLine(
- __name__='add_url',
- required=False, readonly=False)
+ add_url = TextLine(__name__="add_url", required=False, readonly=False)
add_region = TextLine(
- __name__='add_region',
- required=False, readonly=False)
+ __name__="add_region", required=False, readonly=False
+ )
add_owner = Choice(
- __name__='add_owner',
- vocabulary=(
- 'AllUserTeamsParticipationPlusSelfSimpleDisplay'),
+ __name__="add_owner",
+ vocabulary=("AllUserTeamsParticipationPlusSelfSimpleDisplay"),
default=self.default_owner,
- required=False, readonly=False)
+ required=False,
+ readonly=False,
+ )
add_username = TextLine(
- __name__='add_username',
- required=False, readonly=False)
+ __name__="add_username", required=False, readonly=False
+ )
add_password = Password(
- __name__='add_password',
- required=False, readonly=False)
+ __name__="add_password", required=False, readonly=False
+ )
add_confirm_password = Password(
- __name__='add_confirm_password',
- required=False, readonly=False)
+ __name__="add_confirm_password", required=False, readonly=False
+ )
return (
- add_url, add_region, add_owner, add_username,
- add_password, add_confirm_password)
+ add_url,
+ add_region,
+ add_owner,
+ add_username,
+ add_password,
+ add_confirm_password,
+ )
def _parseFieldName(self, field_name):
"""Parse a combined field name as described in `_getFieldName`.
@@ -3817,13 +4093,15 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
field_bits = field_name.split(".")
if len(field_bits) != 2:
raise UnexpectedFormData(
- "Cannot parse field name: %s" % field_name)
+ "Cannot parse field name: %s" % field_name
+ )
field_type = field_bits[0]
try:
credentials_id = int(field_bits[1])
except ValueError:
raise UnexpectedFormData(
- "Cannot parse field name: %s" % field_name)
+ "Cannot parse field name: %s" % field_name
+ )
return field_type, credentials_id
def setUpFields(self):
@@ -3845,7 +4123,7 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
@property
def label(self):
- return 'Edit OCI registry credentials'
+ return "Edit OCI registry credentials"
@property
def cancel_url(self):
@@ -3853,20 +4131,25 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
def getCredentialsWidgets(self, credentials):
widgets_by_name = {widget.name: widget for widget in self.widgets}
- owner_field_name = (
- "field." + self._getFieldName("owner", credentials.id))
- username_field_name = (
- "field." + self._getFieldName("username", credentials.id))
- password_field_name = (
- "field." + self._getFieldName("password", credentials.id))
- confirm_password_field_name = (
- "field." + self._getFieldName("confirm_password",
- credentials.id))
+ owner_field_name = "field." + self._getFieldName(
+ "owner", credentials.id
+ )
+ username_field_name = "field." + self._getFieldName(
+ "username", credentials.id
+ )
+ password_field_name = "field." + self._getFieldName(
+ "password", credentials.id
+ )
+ confirm_password_field_name = "field." + self._getFieldName(
+ "confirm_password", credentials.id
+ )
url_field_name = "field." + self._getFieldName("url", credentials.id)
region_field_name = "field." + self._getFieldName(
- "region", credentials.id)
- delete_field_name = (
- "field." + self._getFieldName("delete", credentials.id))
+ "region", credentials.id
+ )
+ delete_field_name = "field." + self._getFieldName(
+ "delete", credentials.id
+ )
return {
"owner": widgets_by_name[owner_field_name],
"username": widgets_by_name[username_field_name],
@@ -3874,7 +4157,7 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
"confirm_password": widgets_by_name[confirm_password_field_name],
"url": widgets_by_name[url_field_name],
"region": widgets_by_name[region_field_name],
- "delete": widgets_by_name[delete_field_name]
+ "delete": widgets_by_name[delete_field_name],
}
def parseData(self, data):
@@ -3887,26 +4170,32 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
add_password = data["add_password"]
add_confirm_password = data["add_confirm_password"]
if add_url or add_username or add_password or add_confirm_password:
- parsed_data.setdefault(None, {
- "username": add_username,
- "password": add_password,
- "confirm_password": add_confirm_password,
- "url": add_url,
- "region": add_region,
- "owner": add_owner,
- "action": "add",
- })
+ parsed_data.setdefault(
+ None,
+ {
+ "username": add_username,
+ "password": add_password,
+ "confirm_password": add_confirm_password,
+ "url": add_url,
+ "region": add_region,
+ "owner": add_owner,
+ "action": "add",
+ },
+ )
for field_name in (
- name for name in data if name.split(".")[0] == "owner"):
+ name for name in data if name.split(".")[0] == "owner"
+ ):
_, credentials_id = self._parseFieldName(field_name)
- owner_field_name = self._getFieldName(
- "owner", credentials_id)
+ owner_field_name = self._getFieldName("owner", credentials_id)
username_field_name = self._getFieldName(
- "username", credentials_id)
+ "username", credentials_id
+ )
password_field_name = self._getFieldName(
- "password", credentials_id)
+ "password", credentials_id
+ )
confirm_password_field_name = self._getFieldName(
- "confirm_password", credentials_id)
+ "confirm_password", credentials_id
+ )
url_field_name = self._getFieldName("url", credentials_id)
region_field_name = self._getFieldName("region", credentials_id)
delete_field_name = self._getFieldName("delete", credentials_id)
@@ -3914,15 +4203,18 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
action = "delete"
else:
action = "change"
- parsed_data.setdefault(credentials_id, {
- "username": data.get(username_field_name),
- "password": data.get(password_field_name),
- "confirm_password": data.get(confirm_password_field_name),
- "url": data.get(url_field_name),
- "region": data.get(region_field_name),
- "owner": data.get(owner_field_name),
- "action": action,
- })
+ parsed_data.setdefault(
+ credentials_id,
+ {
+ "username": data.get(username_field_name),
+ "password": data.get(password_field_name),
+ "confirm_password": data.get(confirm_password_field_name),
+ "url": data.get(url_field_name),
+ "region": data.get(region_field_name),
+ "owner": data.get(owner_field_name),
+ "action": action,
+ },
+ )
return parsed_data
@@ -3935,14 +4227,14 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
if password or confirm_password:
if password != confirm_password:
self.setFieldError(
- self._getFieldName(
- "confirm_password", credentials.id),
- "Passwords do not match.")
+ self._getFieldName("confirm_password", credentials.id),
+ "Passwords do not match.",
+ )
else:
raw_credentials = {
"username": username,
"password": password,
- }
+ }
if region:
raw_credentials["region"] = region
credentials.setCredentials(raw_credentials)
@@ -3957,13 +4249,12 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
def deleteCredentials(self, credentials):
push_rule_set = getUtility(IOCIPushRuleSet)
- if not push_rule_set.findByRegistryCredentials(
- credentials).is_empty():
+ if not push_rule_set.findByRegistryCredentials(credentials).is_empty():
self.setFieldError(
- self._getFieldName(
- "delete", credentials.id),
+ self._getFieldName("delete", credentials.id),
"These credentials cannot be deleted as there are "
- "push rules defined that still use them.")
+ "push rules defined that still use them.",
+ )
else:
credentials.destroySelf()
@@ -3981,46 +4272,52 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
"add_password",
"Please make sure the new "
"password matches the "
- "confirm password field.")
+ "confirm password field.",
+ )
return
- credentials = {
- 'username': username,
- 'password': password}
+ credentials = {"username": username, "password": password}
if region:
credentials["region"] = region
try:
getUtility(IOCIRegistryCredentialsSet).new(
- registrant=self.user, owner=owner, url=url,
- credentials=credentials)
+ registrant=self.user,
+ owner=owner,
+ url=url,
+ credentials=credentials,
+ )
except OCIRegistryCredentialsAlreadyExist:
self.setFieldError(
"add_url",
"Credentials already exist "
"with the same URL and "
- "username.")
+ "username.",
+ )
else:
- credentials = {'username': username}
+ credentials = {"username": username}
if region:
credentials["region"] = region
try:
getUtility(IOCIRegistryCredentialsSet).new(
- registrant=self.user, owner=owner, url=url,
- credentials=credentials)
+ registrant=self.user,
+ owner=owner,
+ url=url,
+ credentials=credentials,
+ )
except OCIRegistryCredentialsAlreadyExist:
self.setFieldError(
"add_url",
"Credentials already exist "
- "with the same URL and username.")
+ "with the same URL and username.",
+ )
else:
- self.setFieldError(
- "add_url",
- "Registry URL cannot be empty.")
+ self.setFieldError("add_url", "Registry URL cannot be empty.")
def updateCredentialsFromData(self, parsed_data):
credentials_map = {
credentials.id: credentials
- for credentials in self.oci_registry_credentials}
+ for credentials in self.oci_registry_credentials
+ }
for credentials_id, parsed_credentials in parsed_data.items():
credentials = credentials_map.get(credentials_id)
@@ -4048,16 +4345,17 @@ class PersonEditOCIRegistryCredentialsView(LaunchpadFormView):
class PersonLiveFSView(LaunchpadView):
"""Default view for the list of live filesystems owned by a person."""
- page_title = 'LiveFS'
+
+ page_title = "LiveFS"
@property
def label(self):
- return 'Live filesystems for %s' % self.context.display_name
+ return "Live filesystems for %s" % self.context.display_name
@property
def livefses(self):
livefses = getUtility(ILiveFSSet).getByPerson(self.context)
- return livefses.order_by('name')
+ return livefses.order_by("name")
@property
def livefses_navigator(self):
@@ -4071,21 +4369,25 @@ class PersonLiveFSView(LaunchpadView):
class PersonTimeZoneForm(Interface):
time_zone = Choice(
- vocabulary='TimezoneName', title=_('Time zone'), required=True,
+ vocabulary="TimezoneName",
+ title=_("Time zone"),
+ required=True,
description=_(
- 'Once the time zone is correctly set, events '
- 'in Launchpad will be displayed in local time.'))
+ "Once the time zone is correctly set, events "
+ "in Launchpad will be displayed in local time."
+ ),
+ )
class PersonEditTimeZoneView(LaunchpadFormView):
"""Edit a person's time zone."""
schema = PersonTimeZoneForm
- page_title = label = 'Set timezone'
+ page_title = label = "Set timezone"
@property
def initial_values(self):
- return {'time_zone': self.context.time_zone}
+ return {"time_zone": self.context.time_zone}
@property
def next_url(self):
@@ -4096,9 +4398,9 @@ class PersonEditTimeZoneView(LaunchpadFormView):
@action(_("Update"), name="update")
def action_update(self, action, data):
"""Set the time zone for the person."""
- timezone = data.get('time_zone')
+ timezone = data.get("time_zone")
if timezone is None:
- raise UnexpectedFormData('No location received.')
+ raise UnexpectedFormData("No location received.")
# XXX salgado, 2012-02-16, bug=933699: Use setLocation() because it's
# the cheaper way to set the timezone of a person. Once the bug is
# fixed we'll be able to get rid of this hack.
@@ -4113,20 +4415,17 @@ def archive_to_person(archive):
class IEmailToPerson(Interface):
"""Schema for contacting a user via email through Launchpad."""
- from_ = TextLine(
- title=_('From'), required=True, readonly=False)
+ from_ = TextLine(title=_("From"), required=True, readonly=False)
- subject = TextLine(
- title=_('Subject'), required=True, readonly=False)
+ subject = TextLine(title=_("Subject"), required=True, readonly=False)
- message = Text(
- title=_('Message'), required=True, readonly=False)
+ message = Text(title=_("Message"), required=True, readonly=False)
@invariant
def subject_and_message_are_not_empty(data):
"""Raise an Invalid error if the message or subject is empty."""
- if '' in (data.message.strip(), data.subject.strip()):
- raise Invalid('You must provide a subject and a message.')
+ if "" in (data.message.strip(), data.subject.strip()):
+ raise Invalid("You must provide a subject and a message.")
@implementer(INotificationRecipientSet)
@@ -4191,24 +4490,28 @@ class ContactViaWebNotificationRecipientSet:
if self.primary_reason is self.TO_USER:
reason = (
'using the "Contact this user" link on your profile page '
- '(%s)' % canonical_url(person_or_team))
- header = 'ContactViaWeb user'
+ "(%s)" % canonical_url(person_or_team)
+ )
+ header = "ContactViaWeb user"
elif self.primary_reason is self.TO_ADMINS:
reason = (
'using the "Contact this team\'s admins" link on the '
- '%s team page (%s)' % (
- person_or_team.displayname,
- canonical_url(person_or_team)))
- header = 'ContactViaWeb owner (%s team)' % person_or_team.name
+ "%s team page (%s)"
+ % (person_or_team.displayname, canonical_url(person_or_team))
+ )
+ header = "ContactViaWeb owner (%s team)" % person_or_team.name
else:
# self.primary_reason is self.TO_MEMBERS.
reason = (
- 'to each member of the %s team using the '
- '"Contact this team" link on the %s team page (%s)' % (
+ "to each member of the %s team using the "
+ '"Contact this team" link on the %s team page (%s)'
+ % (
person_or_team.displayname,
person_or_team.displayname,
- canonical_url(person_or_team)))
- header = 'ContactViaWeb member (%s team)' % person_or_team.name
+ canonical_url(person_or_team),
+ )
+ )
+ header = "ContactViaWeb member (%s team)" % person_or_team.name
return (reason, header)
def _getDescription(self, person_or_team):
@@ -4218,24 +4521,28 @@ class ContactViaWebNotificationRecipientSet:
:type person_or_team: `IPerson`.
"""
if self.primary_reason is self.TO_USER:
- return (
- 'You are contacting %s (%s).' %
- (person_or_team.displayname, person_or_team.name))
+ return "You are contacting %s (%s)." % (
+ person_or_team.displayname,
+ person_or_team.name,
+ )
elif self.primary_reason is self.TO_ADMINS:
- return (
- 'You are contacting the %s (%s) team admins.' %
- (person_or_team.displayname, person_or_team.name))
+ return "You are contacting the %s (%s) team admins." % (
+ person_or_team.displayname,
+ person_or_team.name,
+ )
else:
# This is a team without a contact address (self.TO_MEMBERS).
recipients_count = len(self)
if recipients_count == 1:
- plural_suffix = ''
+ plural_suffix = ""
else:
- plural_suffix = 's'
- text = '%d member%s' % (recipients_count, plural_suffix)
- return (
- 'You are contacting %s of the %s (%s) team directly.'
- % (text, person_or_team.displayname, person_or_team.name))
+ plural_suffix = "s"
+ text = "%d member%s" % (recipients_count, plural_suffix)
+ return "You are contacting %s of the %s (%s) team directly." % (
+ text,
+ person_or_team.displayname,
+ person_or_team.name,
+ )
@cachedproperty
def _all_recipients(self):
@@ -4258,7 +4565,8 @@ class ContactViaWebNotificationRecipientSet:
all_recipients[email] = recipient
elif self._primary_recipient.is_valid_person_or_team:
email = removeSecurityProxy(
- self._primary_recipient).preferredemail.email
+ self._primary_recipient
+ ).preferredemail.email
all_recipients[email] = self._primary_recipient
else:
# The user or team owner is not active.
@@ -4272,7 +4580,8 @@ class ContactViaWebNotificationRecipientSet:
def getRecipients(self):
"""See `INotificationRecipientSet`."""
yield from sorted(
- self._all_recipients.values(), key=attrgetter('displayname'))
+ self._all_recipients.values(), key=attrgetter("displayname")
+ )
def getRecipientPersons(self):
"""See `INotificationRecipientSet`."""
@@ -4296,7 +4605,8 @@ class ContactViaWebNotificationRecipientSet:
if self.primary_reason is self.TO_MEMBERS:
# Get the count without loading all the members.
self._count_recipients = (
- recipient.getMembersWithPreferredEmailsCount())
+ recipient.getMembersWithPreferredEmailsCount()
+ )
elif self.primary_reason is self.TO_ADMINS:
self._count_recipients = len(self._all_recipients)
elif recipient.is_valid_person_or_team:
@@ -4314,7 +4624,8 @@ class ContactViaWebNotificationRecipientSet:
"""See `INotificationRecipientSet`."""
if person_or_email not in self:
raise UnknownRecipientError(
- '%s in not in the recipients' % person_or_email)
+ "%s in not in the recipients" % person_or_email
+ )
# All users have the same reason based on the primary recipient.
return (self._reason, self._header)
@@ -4349,7 +4660,7 @@ class EmailToPersonView(LaunchpadFormView):
"""The 'Contact this user' page."""
schema = IEmailToPerson
- field_names = ['subject', 'message']
+ field_names = ["subject", "message"]
custom_widget_subject = CustomWidgetFactory(TextWidget, displayWidth=60)
def initialize(self):
@@ -4370,15 +4681,17 @@ class EmailToPersonView(LaunchpadFormView):
usable_addresses = [self.user.preferredemail]
usable_addresses.extend(self.user.validatedemails)
terms = [SimpleTerm(email, email.email) for email in usable_addresses]
- field = Choice(__name__='field.from_',
- title=_('From'),
- source=SimpleVocabulary(terms),
- default=terms[0].value)
+ field = Choice(
+ __name__="field.from_",
+ title=_("From"),
+ source=SimpleVocabulary(terms),
+ default=terms[0].value,
+ )
# Get the order right; the From field should be first, followed by the
# Subject and then Message fields.
- self.form_fields = FormFields(*chain((field, ), self.form_fields))
+ self.form_fields = FormFields(*chain((field,), self.form_fields))
- label = 'Contact user'
+ label = "Contact user"
@cachedproperty
def recipients(self):
@@ -4389,35 +4702,46 @@ class EmailToPersonView(LaunchpadFormView):
"""
return ContactViaWebNotificationRecipientSet(self.user, self.context)
- @action(_('Send'), name='send')
+ @action(_("Send"), name="send")
def action_send(self, action, data):
"""Send an email to the user."""
- sender_email = data['field.from_'].email
- subject = data['subject']
- message = data['message']
+ sender_email = data["field.from_"].email
+ subject = data["subject"]
+ message = data["message"]
if not self.recipients:
self.request.response.addErrorNotification(
- _('Your message was not sent because the recipient '
- 'does not have a preferred email address.'))
+ _(
+ "Your message was not sent because the recipient "
+ "does not have a preferred email address."
+ )
+ )
self.next_url = canonical_url(self.context)
return
try:
send_direct_contact_email(
- sender_email, self.recipients, self.context, subject, message)
+ sender_email, self.recipients, self.context, subject, message
+ )
except QuotaReachedError as error:
fmt_date = DateTimeFormatterAPI(self.next_try)
self.request.response.addErrorNotification(
- _('Your message was not sent because you have exceeded your '
- 'daily quota of $quota messages to contact users. '
- 'Try again $when.', mapping=dict(
- quota=error.authorization.message_quota,
- when=fmt_date.approximatedate(),
- )))
+ _(
+ "Your message was not sent because you have exceeded your "
+ "daily quota of $quota messages to contact users. "
+ "Try again $when.",
+ mapping=dict(
+ quota=error.authorization.message_quota,
+ when=fmt_date.approximatedate(),
+ ),
+ )
+ )
else:
self.request.response.addInfoNotification(
- _('Message sent to $name',
- mapping=dict(name=self.context.displayname)))
+ _(
+ "Message sent to $name",
+ mapping=dict(name=self.context.displayname),
+ )
+ )
self.next_url = canonical_url(self.context)
@property
@@ -4445,7 +4769,8 @@ class EmailToPersonView(LaunchpadFormView):
"""When can the user try again?"""
throttle_date = IDirectEmailAuthorization(self.user).throttle_date
interval = as_timedelta(
- config.launchpad.user_to_user_throttle_interval)
+ config.launchpad.user_to_user_throttle_interval
+ )
return throttle_date + interval
@property
@@ -4465,13 +4790,13 @@ class EmailToPersonView(LaunchpadFormView):
"""Return the appropriate pagetitle."""
if self.context.is_team:
if self.user.inTeam(self.context):
- return 'Contact your team'
+ return "Contact your team"
else:
- return 'Contact this team'
+ return "Contact this team"
elif self.context == self.user:
- return 'Contact yourself'
+ return "Contact yourself"
else:
- return 'Contact this user'
+ return "Contact this user"
class IPersonIndexMenu(Interface):
@@ -4480,10 +4805,15 @@ class IPersonIndexMenu(Interface):
class PersonIndexMenu(NavigationMenu, PersonMenuMixin):
usedfor = IPersonIndexMenu
- facet = 'overview'
- title = 'Change person'
- links = ('edit', 'administer', 'administer_account', 'branding',
- 'password')
+ facet = "overview"
+ title = "Change person"
+ links = (
+ "edit",
+ "administer",
+ "administer_account",
+ "branding",
+ "password",
+ )
classImplements(PersonIndexView, IPersonIndexMenu)
diff --git a/lib/lp/registry/browser/persondistributionsourcepackage.py b/lib/lp/registry/browser/persondistributionsourcepackage.py
index 71e2456..73cbf50 100644
--- a/lib/lp/registry/browser/persondistributionsourcepackage.py
+++ b/lib/lp/registry/browser/persondistributionsourcepackage.py
@@ -4,10 +4,10 @@
"""Views, menus and traversal related to PersonDistributionSourcePackages."""
__all__ = [
- 'PersonDistributionSourcePackageBreadcrumb',
- 'PersonDistributionSourcePackageFacets',
- 'PersonDistributionSourcePackageNavigation',
- ]
+ "PersonDistributionSourcePackageBreadcrumb",
+ "PersonDistributionSourcePackageFacets",
+ "PersonDistributionSourcePackageNavigation",
+]
from zope.component import queryAdapter
@@ -18,18 +18,19 @@ from lp.app.errors import NotFoundError
from lp.code.browser.vcslisting import PersonTargetDefaultVCSNavigationMixin
from lp.registry.interfaces.persondistributionsourcepackage import (
IPersonDistributionSourcePackage,
- )
+)
from lp.services.webapp import (
- canonical_url,
Navigation,
StandardLaunchpadFacets,
- )
+ canonical_url,
+)
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
class PersonDistributionSourcePackageNavigation(
- PersonTargetDefaultVCSNavigationMixin, Navigation):
+ PersonTargetDefaultVCSNavigationMixin, Navigation
+):
usedfor = IPersonDistributionSourcePackage
def traverse(self, branch_name):
@@ -52,19 +53,20 @@ class PersonDistributionSourcePackageBreadcrumb(Breadcrumb):
def url(self):
if self._url is None:
return canonical_url(
- self.context.distro_source_package, rootsite=self.rootsite)
+ self.context.distro_source_package, rootsite=self.rootsite
+ )
else:
return self._url
@property
def icon(self):
return queryAdapter(
- self.context.distro_source_package, IPathAdapter,
- name='image').icon()
+ self.context.distro_source_package, IPathAdapter, name="image"
+ ).icon()
class PersonDistributionSourcePackageFacets(StandardLaunchpadFacets):
"""The links that will appear in the facet menu for an IPersonDSP."""
usedfor = IPersonDistributionSourcePackage
- enable_only = ['branches']
+ enable_only = ["branches"]
diff --git a/lib/lp/registry/browser/personociproject.py b/lib/lp/registry/browser/personociproject.py
index 251f5db..47344c8 100644
--- a/lib/lp/registry/browser/personociproject.py
+++ b/lib/lp/registry/browser/personociproject.py
@@ -4,13 +4,10 @@
"""Views, menus, and traversal related to `PersonOCIProject`s."""
__all__ = [
- 'PersonOCIProjectNavigation',
- ]
+ "PersonOCIProjectNavigation",
+]
-from zope.component import (
- getUtility,
- queryAdapter,
- )
+from zope.component import getUtility, queryAdapter
from zope.interface import implementer
from zope.traversing.interfaces import IPathAdapter
@@ -18,24 +15,26 @@ from lp.code.browser.vcslisting import PersonTargetDefaultVCSNavigationMixin
from lp.oci.interfaces.ocirecipe import IOCIRecipeSet
from lp.registry.interfaces.personociproject import IPersonOCIProject
from lp.services.webapp import (
- canonical_url,
Navigation,
StandardLaunchpadFacets,
+ canonical_url,
stepthrough,
- )
+)
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
class PersonOCIProjectNavigation(
- PersonTargetDefaultVCSNavigationMixin, Navigation):
+ PersonTargetDefaultVCSNavigationMixin, Navigation
+):
usedfor = IPersonOCIProject
- @stepthrough('+recipe')
+ @stepthrough("+recipe")
def traverse_recipe(self, name):
return getUtility(IOCIRecipeSet).getByName(
- self.context.person, self.context.oci_project, name)
+ self.context.person, self.context.oci_project, name
+ )
# XXX cjwatson 2019-11-26: Do we need two breadcrumbs, one for the
@@ -52,19 +51,20 @@ class PersonOCIProjectBreadcrumb(Breadcrumb):
def url(self):
if self._url is None:
return canonical_url(
- self.context.oci_project, rootsite=self.rootsite)
+ self.context.oci_project, rootsite=self.rootsite
+ )
else:
return self._url
@property
def icon(self):
return queryAdapter(
- self.context.oci_project, IPathAdapter, name='image').icon()
+ self.context.oci_project, IPathAdapter, name="image"
+ ).icon()
class PersonOCIProjectFacets(StandardLaunchpadFacets):
- """The links that will appear in the facet menu for an `IPersonOCIProject`.
- """
+ """The facet menu links for an `IPersonOCIProject`."""
usedfor = IPersonOCIProject
- enable_only = ['branches']
+ enable_only = ["branches"]
diff --git a/lib/lp/registry/browser/personproduct.py b/lib/lp/registry/browser/personproduct.py
index 0af1156..86c8f7d 100644
--- a/lib/lp/registry/browser/personproduct.py
+++ b/lib/lp/registry/browser/personproduct.py
@@ -4,15 +4,12 @@
"""Views, menus and traversal related to PersonProducts."""
__all__ = [
- 'PersonProductBreadcrumb',
- 'PersonProductFacets',
- 'PersonProductNavigation',
- ]
-
-from zope.component import (
- getUtility,
- queryAdapter,
- )
+ "PersonProductBreadcrumb",
+ "PersonProductFacets",
+ "PersonProductNavigation",
+]
+
+from zope.component import getUtility, queryAdapter
from zope.interface import implementer
from zope.traversing.interfaces import IPathAdapter
@@ -23,50 +20,52 @@ from lp.code.interfaces.branchnamespace import get_branch_namespace
from lp.registry.interfaces.personociproject import IPersonOCIProjectFactory
from lp.registry.interfaces.personproduct import IPersonProduct
from lp.services.webapp import (
- canonical_url,
Navigation,
StandardLaunchpadFacets,
+ canonical_url,
stepthrough,
- )
+)
from lp.services.webapp.breadcrumb import Breadcrumb
from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
from lp.snappy.interfaces.snap import ISnapSet
-class PersonProductNavigation(PersonTargetDefaultVCSNavigationMixin,
- Navigation):
+class PersonProductNavigation(
+ PersonTargetDefaultVCSNavigationMixin, Navigation
+):
"""Navigation to branches for this person/product."""
+
usedfor = IPersonProduct
- @stepthrough('+oci')
+ @stepthrough("+oci")
def traverse_oci(self, name):
oci_project = self.context.product.getOCIProject(name)
return getUtility(IPersonOCIProjectFactory).create(
- self.context.person, oci_project)
+ self.context.person, oci_project
+ )
def traverse(self, branch_name):
"""Look for a branch in the person/product namespace."""
namespace = get_branch_namespace(
- person=self.context.person, product=self.context.product)
+ person=self.context.person, product=self.context.product
+ )
branch = namespace.getByName(branch_name)
if branch is None:
raise NotFoundError
else:
return branch
- @stepthrough('+snap')
+ @stepthrough("+snap")
def traverse_snap(self, name):
return getUtility(ISnapSet).getByPillarAndName(
- owner=self.context.person,
- pillar=self.context.product,
- name=name)
+ owner=self.context.person, pillar=self.context.product, name=name
+ )
- @stepthrough('+charm')
+ @stepthrough("+charm")
def traverse_charm(self, name):
return getUtility(ICharmRecipeSet).getByName(
- owner=self.context.person,
- project=self.context.product,
- name=name)
+ owner=self.context.person, project=self.context.product, name=name
+ )
@implementer(IMultiFacetedBreadcrumb)
@@ -87,11 +86,12 @@ class PersonProductBreadcrumb(Breadcrumb):
@property
def icon(self):
return queryAdapter(
- self.context.product, IPathAdapter, name='image').icon()
+ self.context.product, IPathAdapter, name="image"
+ ).icon()
class PersonProductFacets(StandardLaunchpadFacets):
"""The links that will appear in the facet menu for an IPerson."""
usedfor = IPersonProduct
- enable_only = ['branches']
+ enable_only = ["branches"]
diff --git a/lib/lp/registry/browser/pillar.py b/lib/lp/registry/browser/pillar.py
index 7817a7c..4f776e9 100644
--- a/lib/lp/registry/browser/pillar.py
+++ b/lib/lp/registry/browser/pillar.py
@@ -4,14 +4,14 @@
"""Common views for objects that implement `IPillar`."""
__all__ = [
- 'InvolvedMenu',
- 'PillarBugsMenu',
- 'PillarInvolvementView',
- 'PillarViewMixin',
- 'PillarNavigationMixin',
- 'PillarPersonSharingView',
- 'PillarSharingView',
- ]
+ "InvolvedMenu",
+ "PillarBugsMenu",
+ "PillarInvolvementView",
+ "PillarViewMixin",
+ "PillarNavigationMixin",
+ "PillarPersonSharingView",
+ "PillarSharingView",
+]
import json
from operator import attrgetter
@@ -20,33 +20,24 @@ from lazr.restful import ResourceJSONEncoder
from lazr.restful.interfaces import IJSONRequestCache
from lazr.restful.utils import get_current_web_service_request
from zope.component import getUtility
-from zope.interface import (
- implementer,
- Interface,
- )
-from zope.schema.vocabulary import (
- getVocabularyRegistry,
- SimpleVocabulary,
- )
+from zope.interface import Interface, implementer
+from zope.schema.vocabulary import SimpleVocabulary, getVocabularyRegistry
from zope.traversing.browser.absoluteurl import absoluteURL
from lp.app.browser.lazrjs import vocabulary_to_choice_edit_items
from lp.app.browser.tales import MenuAPI
from lp.app.browser.vocabulary import vocabulary_filters
-from lp.app.enums import (
- service_uses_launchpad,
- ServiceUsage,
- )
+from lp.app.enums import ServiceUsage, service_uses_launchpad
from lp.app.interfaces.headings import IHeadingBreadcrumb
from lp.app.interfaces.launchpad import IServiceUsage
from lp.app.interfaces.services import IService
from lp.bugs.browser.structuralsubscription import (
StructuralSubscriptionMenuMixin,
- )
+)
from lp.registry.enums import EXCLUSIVE_TEAM_POLICY
from lp.registry.interfaces.distributionsourcepackage import (
IDistributionSourcePackage,
- )
+)
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.pillar import IPillar
@@ -57,29 +48,26 @@ from lp.services.propertycache import cachedproperty
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.services.webapp.batching import (
BatchNavigator,
- get_batch_properties_for_json_cache,
StormRangeFactory,
- )
-from lp.services.webapp.breadcrumb import (
- Breadcrumb,
- DisplaynameBreadcrumb,
- )
+ get_batch_properties_for_json_cache,
+)
+from lp.services.webapp.breadcrumb import Breadcrumb, DisplaynameBreadcrumb
from lp.services.webapp.interfaces import IMultiFacetedBreadcrumb
from lp.services.webapp.menu import (
ApplicationMenu,
- enabled_with_permission,
Link,
NavigationMenu,
- )
+ enabled_with_permission,
+)
from lp.services.webapp.publisher import (
- canonical_url,
LaunchpadView,
+ canonical_url,
nearest,
stepthrough,
- )
+)
@implementer(IHeadingBreadcrumb, IMultiFacetedBreadcrumb)
@@ -103,12 +91,13 @@ class PillarPersonBreadcrumb(Breadcrumb):
return Breadcrumb(
self.context.pillar,
url=canonical_url(self.context.pillar, view_name="+sharing"),
- text="Sharing", inside=self.context.pillar)
+ text="Sharing",
+ inside=self.context.pillar,
+ )
class PillarNavigationMixin:
-
- @stepthrough('+sharing')
+ @stepthrough("+sharing")
def traverse_details(self, name):
"""Traverse to the sharing details for a given person."""
person = getUtility(IPersonSet).getByName(name)
@@ -123,9 +112,14 @@ class IInvolved(Interface):
class InvolvedMenu(NavigationMenu):
"""The get involved menu."""
+
usedfor = IInvolved
links = [
- 'report_bug', 'ask_question', 'help_translate', 'register_blueprint']
+ "report_bug",
+ "ask_question",
+ "help_translate",
+ "register_blueprint",
+ ]
@property
def pillar(self):
@@ -133,26 +127,39 @@ class InvolvedMenu(NavigationMenu):
def report_bug(self):
return Link(
- '+filebug', 'Report a bug', site='bugs', icon='bugs',
- enabled=self.pillar.official_malone)
+ "+filebug",
+ "Report a bug",
+ site="bugs",
+ icon="bugs",
+ enabled=self.pillar.official_malone,
+ )
def ask_question(self):
return Link(
- '+addquestion', 'Ask a question', site='answers', icon='answers',
- enabled=service_uses_launchpad(self.pillar.answers_usage))
+ "+addquestion",
+ "Ask a question",
+ site="answers",
+ icon="answers",
+ enabled=service_uses_launchpad(self.pillar.answers_usage),
+ )
def help_translate(self):
return Link(
- '', 'Help translate', site='translations', icon='translations',
- enabled=service_uses_launchpad(self.pillar.translations_usage))
+ "",
+ "Help translate",
+ site="translations",
+ icon="translations",
+ enabled=service_uses_launchpad(self.pillar.translations_usage),
+ )
def register_blueprint(self):
return Link(
- '+addspec',
- 'Register a blueprint',
- site='blueprints',
- icon='blueprints',
- enabled=service_uses_launchpad(self.pillar.blueprints_usage))
+ "+addspec",
+ "Register a blueprint",
+ site="blueprints",
+ icon="blueprints",
+ enabled=service_uses_launchpad(self.pillar.blueprints_usage),
+ )
@implementer(IInvolved)
@@ -210,19 +217,22 @@ class PillarInvolvementView(LaunchpadView):
@property
def has_involvement(self):
"""This `IPillar` uses Launchpad."""
- return (self.official_malone
- or service_uses_launchpad(self.answers_usage)
- or service_uses_launchpad(self.blueprints_usage)
- or service_uses_launchpad(self.translations_usage)
- or service_uses_launchpad(self.codehosting_usage))
+ return (
+ self.official_malone
+ or service_uses_launchpad(self.answers_usage)
+ or service_uses_launchpad(self.blueprints_usage)
+ or service_uses_launchpad(self.translations_usage)
+ or service_uses_launchpad(self.codehosting_usage)
+ )
@property
def enabled_links(self):
"""The enabled involvement links."""
menuapi = MenuAPI(self)
- return sorted((
- link for link in menuapi.navigation.values() if link.enabled),
- key=attrgetter('sort_key'))
+ return sorted(
+ (link for link in menuapi.navigation.values() if link.enabled),
+ key=attrgetter("sort_key"),
+ )
@cachedproperty
def visible_disabled_links(self):
@@ -236,11 +246,12 @@ class PillarInvolvementView(LaunchpadView):
"""
involved_menu = MenuAPI(self).navigation
important_links = [
- involved_menu[name]
- for name in self.visible_disabled_link_names]
- return sorted((
- link for link in important_links if not link.enabled),
- key=attrgetter('sort_key'))
+ involved_menu[name] for name in self.visible_disabled_link_names
+ ]
+ return sorted(
+ (link for link in important_links if not link.enabled),
+ key=attrgetter("sort_key"),
+ )
@property
def registration_completeness(self):
@@ -254,24 +265,24 @@ class PillarInvolvementView(LaunchpadView):
class PillarBugsMenu(ApplicationMenu, StructuralSubscriptionMenuMixin):
"""Base class for pillar bugs menus."""
- facet = 'bugs'
+ facet = "bugs"
configurable_bugtracker = False
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def bugsupervisor(self):
- text = 'Change bug supervisor'
- return Link('+bugsupervisor', text, icon='edit')
+ text = "Change bug supervisor"
+ return Link("+bugsupervisor", text, icon="edit")
def cve(self):
- text = 'CVE reports'
- return Link('+cve', text, icon='cve')
+ text = "CVE reports"
+ return Link("+cve", text, icon="cve")
def filebug(self):
- text = 'Report a bug'
- return Link('+filebug', text, icon='bug')
+ text = "Report a bug"
+ return Link("+filebug", text, icon="bug")
-class PillarViewMixin():
+class PillarViewMixin:
"""A mixin for pillar views to populate the json request cache."""
def initialize(self):
@@ -282,9 +293,11 @@ class PillarViewMixin():
policy_items = [(item.name, item) for item in EXCLUSIVE_TEAM_POLICY]
team_membership_policy_data = vocabulary_to_choice_edit_items(
SimpleVocabulary.fromItems(policy_items),
- value_fn=lambda item: item.name)
- cache.objects['team_membership_policy_data'] = (
- team_membership_policy_data)
+ value_fn=lambda item: item.name,
+ )
+ cache.objects[
+ "team_membership_policy_data"
+ ] = team_membership_policy_data
class PillarSharingView(LaunchpadView):
@@ -292,17 +305,18 @@ class PillarSharingView(LaunchpadView):
page_title = "Sharing"
label = "Sharing information"
- sharing_vocabulary_name = 'NewPillarGrantee'
+ sharing_vocabulary_name = "NewPillarGrantee"
_batch_navigator = None
def _getSharingService(self):
- return getUtility(IService, 'sharing')
+ return getUtility(IService, "sharing")
@property
def information_types(self):
return self._getSharingService().getAllowedInformationTypes(
- self.context)
+ self.context
+ )
@property
def bug_sharing_policies(self):
@@ -315,7 +329,8 @@ class PillarSharingView(LaunchpadView):
@property
def specification_sharing_policies(self):
return self._getSharingService().getSpecificationSharingPolicies(
- self.context)
+ self.context
+ )
@property
def sharing_permissions(self):
@@ -324,8 +339,7 @@ class PillarSharingView(LaunchpadView):
@cachedproperty
def sharing_vocabulary(self):
registry = getVocabularyRegistry()
- return registry.get(
- self.context, self.sharing_vocabulary_name)
+ return registry.get(self.context, self.sharing_vocabulary_name)
@cachedproperty
def sharing_vocabulary_filters(self):
@@ -337,7 +351,8 @@ class PillarSharingView(LaunchpadView):
vocabulary=self.sharing_vocabulary_name,
vocabulary_filters=self.sharing_vocabulary_filters,
header=self.sharing_vocabulary.displayname,
- steptitle=self.sharing_vocabulary.step_title)
+ steptitle=self.sharing_vocabulary.step_title,
+ )
@property
def json_sharing_picker_config(self):
@@ -346,10 +361,12 @@ class PillarSharingView(LaunchpadView):
def _getBatchNavigator(self, grantees):
"""Return the batch navigator to be used to batch the grantees."""
return BatchNavigator(
- grantees, self.request,
+ grantees,
+ self.request,
hide_counts=True,
size=config.launchpad.default_batch_size,
- range_factory=StormRangeFactory(grantees))
+ range_factory=StormRangeFactory(grantees),
+ )
def grantees(self):
"""An `IBatchNavigator` for grantees."""
@@ -365,15 +382,16 @@ class PillarSharingView(LaunchpadView):
def initialize(self):
super().initialize()
cache = IJSONRequestCache(self.request)
- cache.objects['information_types'] = self.information_types
- cache.objects['sharing_permissions'] = self.sharing_permissions
- cache.objects['bug_sharing_policies'] = self.bug_sharing_policies
- cache.objects['branch_sharing_policies'] = (
- self.branch_sharing_policies)
- cache.objects['specification_sharing_policies'] = (
- self.specification_sharing_policies)
- cache.objects['has_edit_permission'] = check_permission(
- "launchpad.Edit", self.context)
+ cache.objects["information_types"] = self.information_types
+ cache.objects["sharing_permissions"] = self.sharing_permissions
+ cache.objects["bug_sharing_policies"] = self.bug_sharing_policies
+ cache.objects["branch_sharing_policies"] = self.branch_sharing_policies
+ cache.objects[
+ "specification_sharing_policies"
+ ] = self.specification_sharing_policies
+ cache.objects["has_edit_permission"] = check_permission(
+ "launchpad.Edit", self.context
+ )
batch_navigator = self.grantees()
# Precache LimitedView for all the grantees, partly for performance
# but mainly because it's possible that the user won't strictly have
@@ -381,18 +399,25 @@ class PillarSharingView(LaunchpadView):
# see who has access to pillars they drive. Fixing this in
# PublicOrPrivateTeamsExistence would very likely be too expensive.
precache_permission_for_objects(
- None, 'launchpad.LimitedView',
- [grantee for grantee, _, _ in batch_navigator.batch])
- cache.objects['grantee_data'] = (
- self._getSharingService().jsonGranteeData(batch_navigator.batch))
+ None,
+ "launchpad.LimitedView",
+ [grantee for grantee, _, _ in batch_navigator.batch],
+ )
+ cache.objects[
+ "grantee_data"
+ ] = self._getSharingService().jsonGranteeData(batch_navigator.batch)
cache.objects.update(
- get_batch_properties_for_json_cache(self, batch_navigator))
+ get_batch_properties_for_json_cache(self, batch_navigator)
+ )
- grant_counts = (
- self._getSharingService().getAccessPolicyGrantCounts(self.context))
- cache.objects['invisible_information_types'] = [
- count_info[0].title for count_info in grant_counts
- if count_info[1] == 0]
+ grant_counts = self._getSharingService().getAccessPolicyGrantCounts(
+ self.context
+ )
+ cache.objects["invisible_information_types"] = [
+ count_info[0].title
+ for count_info in grant_counts
+ if count_info[1] == 0
+ ]
class PillarPersonSharingView(LaunchpadView):
@@ -406,7 +431,7 @@ class PillarPersonSharingView(LaunchpadView):
self.label = "Information shared with %s" % self.person.displayname
self.page_title = "%s" % self.person.displayname
- self.sharing_service = getUtility(IService, 'sharing')
+ self.sharing_service = getUtility(IService, "sharing")
self._loadSharedArtifacts()
@@ -414,34 +439,36 @@ class PillarPersonSharingView(LaunchpadView):
request = get_current_web_service_request()
branch_data = self._build_branch_template_data(self.branches, request)
gitrepository_data = self._build_gitrepository_template_data(
- self.gitrepositories, request)
+ self.gitrepositories, request
+ )
bug_data = self._build_bug_template_data(self.bugtasks, request)
spec_data = self._build_specification_template_data(
- self.specifications, request)
+ self.specifications, request
+ )
snap_data = self._build_ocirecipe_template_data(self.snaps, request)
ocirecipe_data = self._build_ocirecipe_template_data(
- self.ocirecipes, request)
+ self.ocirecipes, request
+ )
grantee_data = {
- 'displayname': self.person.displayname,
- 'self_link': absoluteURL(self.person, request)
- }
- pillar_data = {
- 'self_link': absoluteURL(self.pillar, request)
+ "displayname": self.person.displayname,
+ "self_link": absoluteURL(self.person, request),
}
- cache.objects['grantee'] = grantee_data
- cache.objects['pillar'] = pillar_data
- cache.objects['bugs'] = bug_data
- cache.objects['branches'] = branch_data
- cache.objects['gitrepositories'] = gitrepository_data
- cache.objects['specifications'] = spec_data
- cache.objects['snaps'] = snap_data
- cache.objects['ocirecipes'] = ocirecipe_data
+ pillar_data = {"self_link": absoluteURL(self.pillar, request)}
+ cache.objects["grantee"] = grantee_data
+ cache.objects["pillar"] = pillar_data
+ cache.objects["bugs"] = bug_data
+ cache.objects["branches"] = branch_data
+ cache.objects["gitrepositories"] = gitrepository_data
+ cache.objects["specifications"] = spec_data
+ cache.objects["snaps"] = snap_data
+ cache.objects["ocirecipes"] = ocirecipe_data
def _loadSharedArtifacts(self):
# As a concrete can by linked via more than one policy, we use sets to
# filter out dupes.
artifacts = self.sharing_service.getSharedArtifacts(
- self.pillar, self.person, self.user)
+ self.pillar, self.person, self.user
+ )
self.bugtasks = artifacts["bugtasks"]
self.branches = artifacts["branches"]
self.gitrepositories = artifacts["gitrepositories"]
@@ -460,34 +487,45 @@ class PillarPersonSharingView(LaunchpadView):
def _build_specification_template_data(self, specs, request):
spec_data = []
for spec in specs:
- spec_data.append(dict(
- self_link=absoluteURL(spec, request),
- web_link=canonical_url(spec, path_only_if_possible=True),
- name=spec.name,
- id=spec.id,
- information_type=spec.information_type.title))
+ spec_data.append(
+ dict(
+ self_link=absoluteURL(spec, request),
+ web_link=canonical_url(spec, path_only_if_possible=True),
+ name=spec.name,
+ id=spec.id,
+ information_type=spec.information_type.title,
+ )
+ )
return spec_data
def _build_branch_template_data(self, branches, request):
branch_data = []
for branch in branches:
- branch_data.append(dict(
- self_link=absoluteURL(branch, request),
- web_link=canonical_url(branch, path_only_if_possible=True),
- branch_name=branch.unique_name,
- branch_id=branch.id,
- information_type=branch.information_type.title))
+ branch_data.append(
+ dict(
+ self_link=absoluteURL(branch, request),
+ web_link=canonical_url(branch, path_only_if_possible=True),
+ branch_name=branch.unique_name,
+ branch_id=branch.id,
+ information_type=branch.information_type.title,
+ )
+ )
return branch_data
def _build_gitrepository_template_data(self, repositories, request):
repository_data = []
for repository in repositories:
- repository_data.append(dict(
- self_link=absoluteURL(repository, request),
- web_link=canonical_url(repository, path_only_if_possible=True),
- repository_name=repository.unique_name,
- repository_id=repository.id,
- information_type=repository.information_type.title))
+ repository_data.append(
+ dict(
+ self_link=absoluteURL(repository, request),
+ web_link=canonical_url(
+ repository, path_only_if_possible=True
+ ),
+ repository_name=repository.unique_name,
+ repository_id=repository.id,
+ information_type=repository.information_type.title,
+ )
+ )
return repository_data
def _build_bug_template_data(self, bugtasks, request):
@@ -497,33 +535,42 @@ class PillarPersonSharingView(LaunchpadView):
self_link = absoluteURL(bugtask.bug, request)
importance = bugtask.importance.title.lower()
information_type = bugtask.bug.information_type.title
- bug_data.append(dict(
- self_link=self_link,
- web_link=web_link,
- bug_summary=bugtask.bug.title,
- bug_id=bugtask.bug.id,
- bug_importance=importance,
- information_type=information_type))
+ bug_data.append(
+ dict(
+ self_link=self_link,
+ web_link=web_link,
+ bug_summary=bugtask.bug.title,
+ bug_id=bugtask.bug.id,
+ bug_importance=importance,
+ information_type=information_type,
+ )
+ )
return bug_data
def _build_ocirecipe_template_data(self, oci_recipes, request):
recipe_data = []
for recipe in oci_recipes:
- recipe_data.append(dict(
- self_link=absoluteURL(recipe, request),
- web_link=canonical_url(recipe, path_only_if_possible=True),
- name=recipe.name,
- id=recipe.id,
- information_type=recipe.information_type.title))
+ recipe_data.append(
+ dict(
+ self_link=absoluteURL(recipe, request),
+ web_link=canonical_url(recipe, path_only_if_possible=True),
+ name=recipe.name,
+ id=recipe.id,
+ information_type=recipe.information_type.title,
+ )
+ )
return recipe_data
def _build_snap_template_data(self, snaps, request):
snap_data = []
for snap in snaps:
- snap_data.append(dict(
- self_link=absoluteURL(snap, request),
- web_link=canonical_url(snap, path_only_if_possible=True),
- name=snap.name,
- id=snap.id,
- information_type=snap.information_type.title))
+ snap_data.append(
+ dict(
+ self_link=absoluteURL(snap, request),
+ web_link=canonical_url(snap, path_only_if_possible=True),
+ name=snap.name,
+ id=snap.id,
+ information_type=snap.information_type.title,
+ )
+ )
return snap_data
diff --git a/lib/lp/registry/browser/poll.py b/lib/lp/registry/browser/poll.py
index 66049fe..b182a91 100644
--- a/lib/lp/registry/browser/poll.py
+++ b/lib/lp/registry/browser/poll.py
@@ -2,36 +2,33 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'BasePollView',
- 'PollAddView',
- 'PollEditNavigationMenu',
- 'PollEditView',
- 'PollNavigation',
- 'PollOptionAddView',
- 'PollOptionEditView',
- 'PollOverviewMenu',
- 'PollView',
- 'PollVoteView',
- 'PollBreadcrumb',
- 'TeamPollsView',
- ]
+ "BasePollView",
+ "PollAddView",
+ "PollEditNavigationMenu",
+ "PollEditView",
+ "PollNavigation",
+ "PollOptionAddView",
+ "PollOptionEditView",
+ "PollOverviewMenu",
+ "PollView",
+ "PollVoteView",
+ "PollBreadcrumb",
+ "TeamPollsView",
+]
from zope.browserpage import ViewPageTemplateFile
from zope.component import getUtility
from zope.event import notify
from zope.formlib.widget import CustomWidgetFactory
from zope.formlib.widgets import TextWidget
-from zope.interface import (
- implementer,
- Interface,
- )
+from zope.interface import Interface, implementer
from zope.lifecycleevent import ObjectCreatedEvent
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
- )
+ action,
+)
from lp.registry.browser.person import PersonView
from lp.registry.interfaces.poll import (
IPoll,
@@ -41,38 +38,37 @@ from lp.registry.interfaces.poll import (
IVoteSet,
PollAlgorithm,
PollSecrecy,
- )
+)
from lp.services.helpers import shortlist
from lp.services.webapp import (
ApplicationMenu,
- canonical_url,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
NavigationMenu,
+ canonical_url,
+ enabled_with_permission,
stepthrough,
- )
+)
from lp.services.webapp.breadcrumb import TitleBreadcrumb
class PollEditLinksMixin:
-
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def addnew(self):
- text = 'Add new option'
- return Link('+newoption', text, icon='add')
+ text = "Add new option"
+ return Link("+newoption", text, icon="add")
- @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")
class PollOverviewMenu(ApplicationMenu, PollEditLinksMixin):
usedfor = IPoll
- facet = 'overview'
- links = ['addnew']
+ facet = "overview"
+ links = ["addnew"]
class IPollEditMenu(Interface):
@@ -81,8 +77,8 @@ class IPollEditMenu(Interface):
class PollEditNavigationMenu(NavigationMenu, PollEditLinksMixin):
usedfor = IPollEditMenu
- facet = 'overview'
- links = ['addnew', 'edit']
+ facet = "overview"
+ links = ["addnew", "edit"]
class IPollActionMenu(Interface):
@@ -91,17 +87,18 @@ class IPollActionMenu(Interface):
class PollActionNavigationMenu(PollEditNavigationMenu):
usedfor = IPollActionMenu
- links = ['edit']
+ links = ["edit"]
class PollNavigation(Navigation):
usedfor = IPoll
- @stepthrough('+option')
+ @stepthrough("+option")
def traverse_option(self, name):
return getUtility(IPollOptionSet).getByPollAndId(
- self.context, int(name))
+ self.context, int(name)
+ )
def vote_sort_key(vote):
@@ -127,7 +124,7 @@ class BasePollView(LaunchpadView):
# For secret polls we can only display the votes after the token
# is submitted.
- if self.request.method == 'POST' and self.isSecret():
+ if self.request.method == "POST" and self.isSecret():
self.setUpTokenAndVotesForSecretPolls()
elif not self.isSecret():
self.setUpTokenAndVotesForNonSecretPolls()
@@ -143,8 +140,10 @@ class BasePollView(LaunchpadView):
"""
assert not self.isSecret() and self.userVoted()
votes = self.context.getVotesByPerson(self.user)
- assert votes, (
- "User %r hasn't voted on poll %r" % (self.user, self.context))
+ assert votes, "User %r hasn't voted on poll %r" % (
+ self.user,
+ self.context,
+ )
if self.isSimple():
# Here we have only one vote.
self.currentVote = votes[0]
@@ -170,22 +169,25 @@ class BasePollView(LaunchpadView):
in user has voted on this poll.
"""
assert self.isSecret() and self.userVoted()
- token = self.request.form.get('token')
+ token = self.request.form.get("token")
# Only overwrite self.token if the request contains a 'token'
# variable.
if token is not None:
self.token = token
votes = getUtility(IVoteSet).getByToken(self.token)
if not votes:
- self.feedback = ("There's no vote associated with the token %s"
- % self.token)
+ self.feedback = (
+ "There's no vote associated with the token %s" % self.token
+ )
return False
# All votes with a given token must be on the same poll. That means
# checking the poll of the first vote is enough.
if votes[0].poll != self.context:
- self.feedback = ("The vote associated with the token %s is not "
- "a vote on this poll." % self.token)
+ self.feedback = (
+ "The vote associated with the token %s is not "
+ "a vote on this poll." % self.token
+ )
return False
if self.isSimple():
@@ -199,11 +201,11 @@ class BasePollView(LaunchpadView):
def userCanVote(self):
"""Return True if the user is/was eligible to vote on this poll."""
- return (self.user and self.user.inTeam(self.context.team))
+ return self.user and self.user.inTeam(self.context.team)
def userVoted(self):
"""Return True if the user voted on this poll."""
- return (self.user and self.context.personVoted(self.user))
+ return self.user and self.context.personVoted(self.user)
def isCondorcet(self):
"""Return True if this poll's type is Condorcet."""
@@ -229,9 +231,12 @@ class PollView(BasePollView):
def initialize(self):
super().initialize()
request = self.request
- if (self.userCanVote() and self.context.isOpen() and
- self.context.getActiveOptions()):
- vote_url = canonical_url(self.context, view_name='+vote')
+ if (
+ self.userCanVote()
+ and self.context.isOpen()
+ and self.context.getActiveOptions()
+ ):
+ vote_url = canonical_url(self.context, view_name="+vote")
request.response.redirect(vote_url)
def getVotesByOption(self, option):
@@ -265,12 +270,12 @@ class PollVoteView(BasePollView):
change it. Otherwise they can register their vote.
"""
- default_template = ViewPageTemplateFile(
- '../templates/poll-vote-simple.pt')
+ default_template = ViewPageTemplateFile("../templates/poll-vote-simple.pt")
condorcet_template = ViewPageTemplateFile(
- '../templates/poll-vote-condorcet.pt')
+ "../templates/poll-vote-condorcet.pt"
+ )
- page_title = 'Vote'
+ page_title = "Vote"
@property
def template(self):
@@ -286,7 +291,7 @@ class PollVoteView(BasePollView):
# For non-secret polls, the user's vote is always displayed
self.setUpTokenAndVotesForNonSecretPolls()
- if self.request.method != 'POST':
+ if self.request.method != "POST":
return
if self.isSecret() and self.userVoted():
@@ -294,7 +299,7 @@ class PollVoteView(BasePollView):
# Not possible to get the votes. Probably the token was wrong.
return
- if 'showvote' in self.request.form:
+ if "showvote" in self.request.form:
# The user only wants to see the vote.
return
@@ -318,18 +323,19 @@ class PollVoteView(BasePollView):
"""
assert self.context.isOpen()