launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #21449
[Merge] lp:~cjwatson/launchpad/delete-sprint into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/delete-sprint into lp:launchpad.
Commit message:
Add sprint deletion support.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #2888 in Launchpad itself: "cannot hide or remove sprints"
https://bugs.launchpad.net/launchpad/+bug/2888
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/delete-sprint/+merge/322255
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/delete-sprint into lp:launchpad.
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2017-03-20 00:03:52 +0000
+++ database/schema/security.cfg 2017-04-09 08:43:16 +0000
@@ -303,7 +303,7 @@
public.specificationsubscription = SELECT, INSERT, UPDATE, DELETE
public.specificationworkitem = SELECT, INSERT, UPDATE
public.spokenin = SELECT, INSERT, DELETE
-public.sprint = SELECT, INSERT, UPDATE
+public.sprint = SELECT, INSERT, UPDATE, DELETE
public.sprintattendance = SELECT, INSERT, UPDATE, DELETE
public.sprintspecification = SELECT, INSERT, UPDATE, DELETE
public.structuralsubscription = SELECT, INSERT, UPDATE, DELETE
=== modified file 'lib/lp/blueprints/browser/configure.zcml'
--- lib/lp/blueprints/browser/configure.zcml 2014-11-24 06:20:03 +0000
+++ lib/lp/blueprints/browser/configure.zcml 2017-04-09 08:43:16 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009-2012 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2009-2017 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -78,6 +78,13 @@
facet="overview"
template="../../../lp/registry/templates/object-branding.pt"/>
<browser:page
+ name="+delete"
+ for="lp.blueprints.interfaces.sprint.ISprint"
+ class="lp.blueprints.browser.sprint.SprintDeleteView"
+ permission="launchpad.Edit"
+ facet="overview"
+ template="../templates/sprint-delete.pt"/>
+ <browser:page
name="+attend"
for="lp.blueprints.interfaces.sprint.ISprint"
class="lp.blueprints.browser.sprintattendance.SprintAttendanceAttendView"
=== modified file 'lib/lp/blueprints/browser/sprint.py'
--- lib/lp/blueprints/browser/sprint.py 2015-07-08 16:05:11 +0000
+++ lib/lp/blueprints/browser/sprint.py 2017-04-09 08:43:16 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Sprint views."""
@@ -9,6 +9,7 @@
'SprintAddView',
'SprintAttendeesCsvExportView',
'SprintBrandingView',
+ 'SprintDeleteView',
'SprintEditView',
'SprintFacets',
'SprintMeetingExportView',
@@ -113,7 +114,7 @@
usedfor = ISprint
facet = 'overview'
links = ['attendance', 'registration', 'attendee_export', 'edit',
- 'branding']
+ 'branding', 'delete']
def attendance(self):
text = 'Register yourself'
@@ -143,6 +144,10 @@
summary = 'Modify the imagery used to represent this meeting'
return Link('+branding', text, summary, icon='edit')
+ @enabled_with_permission('launchpad.Edit')
+ def delete(self):
+ return Link('+delete', 'Delete sprint', icon='trash-icon')
+
class SprintSpecificationsMenu(NavigationMenu,
HasSpecificationsMenuMixin):
@@ -365,6 +370,25 @@
return canonical_url(self.context)
+class SprintDeleteView(LaunchpadFormView):
+ """Form for deleting sprints."""
+
+ schema = ISprint
+ field_names = []
+
+ @property
+ def label(self):
+ return smartquote('Delete "%s" sprint' % self.context.displayname)
+
+ page_title = label
+
+ @action("Delete sprint", name="delete")
+ def delete_action(self, action, data):
+ owner = self.context.owner
+ self.context.destroySelf()
+ self.next_url = canonical_url(owner)
+
+
class SprintTopicSetView(HasSpecificationsView, LaunchpadView):
"""Custom view class to process the results of this unusual page.
=== modified file 'lib/lp/blueprints/browser/tests/test_sprint.py'
--- lib/lp/blueprints/browser/tests/test_sprint.py 2015-09-29 01:38:34 +0000
+++ lib/lp/blueprints/browser/tests/test_sprint.py 2017-04-09 08:43:16 +0000
@@ -1,14 +1,19 @@
-# Copyright 2011-2012 Canonical Ltd. This software is licensed under the
+# Copyright 2011-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Tests for Sprint pages and views."""
__metaclass__ = type
+from fixtures import FakeLogger
+from mechanize import LinkNotFoundError
from testtools.matchers import Equals
+from zope.publisher.interfaces import NotFound
+from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
from lp.app.enums import InformationType
+from lp.services.webapp.publisher import canonical_url
from lp.testing import (
BrowserTestCase,
RequestTimelineCollector,
@@ -55,3 +60,37 @@
with RequestTimelineCollector() as recorder:
self.getViewBrowser(sprint)
self.assertThat(recorder, HasQueryCount(Equals(20)))
+
+
+class TestSprintDeleteView(BrowserTestCase):
+
+ layer = DatabaseFunctionalLayer
+
+ def test_unauthorized(self):
+ # A user without edit access cannot delete a sprint.
+ self.useFixture(FakeLogger())
+ sprint = self.factory.makeSprint()
+ sprint_url = canonical_url(sprint)
+ other_person = self.factory.makePerson()
+ browser = self.getViewBrowser(sprint, user=other_person)
+ self.assertRaises(LinkNotFoundError, browser.getLink, "Delete sprint")
+ self.assertRaises(
+ Unauthorized, self.getUserBrowser, sprint_url + "/+delete",
+ user=other_person)
+
+ def test_delete_sprint(self):
+ # A sprint can be deleted, even if it has attendees and specifications.
+ self.useFixture(FakeLogger())
+ sprint = self.factory.makeSprint()
+ sprint_url = canonical_url(sprint)
+ owner_url = canonical_url(sprint.owner)
+ sprint.attend(
+ self.factory.makePerson(), sprint.time_starts, sprint.time_ends,
+ True)
+ blueprint = self.factory.makeSpecification()
+ blueprint.linkSprint(sprint, blueprint.owner).acceptBy(sprint.owner)
+ browser = self.getViewBrowser(sprint, user=sprint.owner)
+ browser.getLink("Delete sprint").click()
+ browser.getControl("Delete sprint").click()
+ self.assertEqual(owner_url, browser.url)
+ self.assertRaises(NotFound, browser.open, sprint_url)
=== modified file 'lib/lp/blueprints/doc/sprint.txt'
--- lib/lp/blueprints/doc/sprint.txt 2016-01-26 15:47:37 +0000
+++ lib/lp/blueprints/doc/sprint.txt 2017-04-09 08:43:16 +0000
@@ -268,3 +268,10 @@
mustard
+Sprint deletion
+---------------
+
+The sprint destroySelf() method deletes a sprint.
+
+ >>> ubz.destroySelf()
+ >>> sprintset["ubz"]
=== modified file 'lib/lp/blueprints/interfaces/sprint.py'
--- lib/lp/blueprints/interfaces/sprint.py 2015-01-06 12:13:28 +0000
+++ lib/lp/blueprints/interfaces/sprint.py 2017-04-09 08:43:16 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Interfaces for a Sprint (a meeting, conference or hack session).
@@ -30,8 +30,8 @@
)
from lp import _
+from lp.app.interfaces.launchpad import IHeadingContext
from lp.app.validators.name import name_validator
-from lp.app.interfaces.launchpad import IHeadingContext
from lp.blueprints.interfaces.specificationtarget import IHasSpecifications
from lp.registry.interfaces.role import (
IHasDrivers,
@@ -182,6 +182,12 @@
in the `driver` attribute or an administrator.
"""
+ def destroySelf():
+ """Remove this sprint.
+
+ :raises CannotDeleteSprint: if the sprint cannot be deleted.
+ """
+
class IHasSprints(Interface):
"""An interface for things that have lists of sprints associated with
=== modified file 'lib/lp/blueprints/model/sprint.py'
--- lib/lp/blueprints/model/sprint.py 2015-07-08 16:05:11 +0000
+++ lib/lp/blueprints/model/sprint.py 2017-04-09 08:43:16 +0000
@@ -1,4 +1,4 @@
-# Copyright 2009-2013 Canonical Ltd. This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -298,6 +298,15 @@
user.inTeam(self.driver) or
user.inTeam(admins))
+ def destroySelf(self):
+ Store.of(self).find(
+ SprintSpecification,
+ SprintSpecification.sprint == self).remove()
+ Store.of(self).find(
+ SprintAttendance,
+ SprintAttendance.sprint == self).remove()
+ Store.of(self).remove(self)
+
@implementer(ISprintSet)
class SprintSet:
=== added file 'lib/lp/blueprints/templates/sprint-delete.pt'
--- lib/lp/blueprints/templates/sprint-delete.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/blueprints/templates/sprint-delete.pt 2017-04-09 08:43:16 +0000
@@ -0,0 +1,17 @@
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:tal="http://xml.zope.org/namespaces/tal"
+ xmlns:metal="http://xml.zope.org/namespaces/metal"
+ xmlns:i18n="http://xml.zope.org/namespaces/i18n"
+ metal:use-macro="view/macro:page/main_only"
+ i18n:domain="launchpad">
+<body>
+<div metal:fill-slot="main">
+
+ <p>Sprint deletion is permanent.</p>
+
+ <div metal:use-macro="context/@@launchpad_form/form" />
+
+</div>
+</body>
+</html>
Follow ups