launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18488
[Merge] lp:~cjwatson/launchpad/livefs-delete into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/livefs-delete into lp:launchpad.
Commit message:
Allow deleting live filesystems, provided that they have no builds.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1452449 in Launchpad itself: "Please allow me to delete livefs definitions"
https://bugs.launchpad.net/launchpad/+bug/1452449
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/livefs-delete/+merge/258481
Allow deleting live filesystems, provided that they have no builds.
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/livefs-delete into lp:launchpad.
=== modified file 'lib/lp/soyuz/browser/configure.zcml'
--- lib/lp/soyuz/browser/configure.zcml 2015-04-20 11:24:21 +0000
+++ lib/lp/soyuz/browser/configure.zcml 2015-05-07 10:56:40 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009-2014 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2009-2015 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -723,6 +723,12 @@
permission="launchpad.Edit"
name="+edit"
template="../../app/templates/generic-edit.pt"/>
+ <browser:page
+ for="lp.soyuz.interfaces.livefs.ILiveFS"
+ class="lp.soyuz.browser.livefs.LiveFSDeleteView"
+ permission="launchpad.Edit"
+ name="+delete"
+ template="../templates/livefs-delete.pt"/>
<adapter
provides="lp.services.webapp.interfaces.IBreadcrumb"
for="lp.soyuz.interfaces.livefs.ILiveFS"
=== modified file 'lib/lp/soyuz/browser/livefs.py'
--- lib/lp/soyuz/browser/livefs.py 2014-11-16 09:49:59 +0000
+++ lib/lp/soyuz/browser/livefs.py 2015-05-07 10:56:40 +0000
@@ -1,4 +1,4 @@
-# Copyright 2014 Canonical Ltd. This software is licensed under the
+# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""LiveFS views."""
@@ -6,6 +6,7 @@
__metaclass__ = type
__all__ = [
'LiveFSAddView',
+ 'LiveFSDeleteView',
'LiveFSEditView',
'LiveFSNavigation',
'LiveFSNavigationMenu',
@@ -94,7 +95,7 @@
facet = 'overview'
- links = ('admin', 'edit',)
+ links = ('admin', 'delete', 'edit')
@enabled_with_permission('launchpad.Admin')
def admin(self):
@@ -104,6 +105,10 @@
def edit(self):
return Link('+edit', 'Edit live filesystem', icon='edit')
+ @enabled_with_permission('launchpad.Edit')
+ def delete(self):
+ return Link('+delete', 'Delete live filesystem', icon='trash-icon')
+
class LiveFSView(LaunchpadView):
"""Default view of a LiveFS."""
@@ -329,3 +334,27 @@
distro_series.displayname, owner.displayname))
except NoSuchLiveFS:
pass
+
+
+class LiveFSDeleteView(BaseLiveFSEditView):
+ """View for deleting live filesystems."""
+
+ @property
+ def title(self):
+ return 'Delete %s live filesystem' % self.context.name
+
+ label = title
+
+ field_names = []
+
+ @property
+ def has_builds(self):
+ return not self.context.builds.is_empty()
+
+ @action('Delete live filesystem', name='delete')
+ def delete_action(self, action, data):
+ owner = self.context.owner
+ self.context.destroySelf()
+ # XXX cjwatson 2015-05-07 bug=1332479: This should go to
+ # Person:+livefs once that exists.
+ self.next_url = canonical_url(owner)
=== modified file 'lib/lp/soyuz/browser/tests/test_livefs.py'
--- lib/lp/soyuz/browser/tests/test_livefs.py 2015-04-20 09:48:57 +0000
+++ lib/lp/soyuz/browser/tests/test_livefs.py 2015-05-07 10:56:40 +0000
@@ -1,4 +1,4 @@
-# Copyright 2014 Canonical Ltd. This software is licensed under the
+# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test live filesystem views."""
@@ -15,6 +15,7 @@
from mechanize import LinkNotFoundError
import pytz
from zope.component import getUtility
+from zope.publisher.interfaces import NotFound
from zope.security.interfaces import Unauthorized
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
@@ -316,6 +317,56 @@
extract_text(find_tags_by_class(browser.contents, "message")[1]))
+class TestLiveFSDeleteView(BrowserTestCase):
+
+ layer = LaunchpadFunctionalLayer
+
+ def setUp(self):
+ super(TestLiveFSDeleteView, self).setUp()
+ self.useFixture(FeatureFixture({LIVEFS_FEATURE_FLAG: u"on"}))
+ self.person = self.factory.makePerson(
+ name="test-person", displayname="Test Person")
+
+ def test_unauthorized(self):
+ # A user without edit access cannot delete a live filesystem.
+ self.useFixture(FakeLogger())
+ livefs = self.factory.makeLiveFS(
+ registrant=self.person, owner=self.person)
+ livefs_url = canonical_url(livefs)
+ other_person = self.factory.makePerson()
+ browser = self.getViewBrowser(livefs, user=other_person)
+ self.assertRaises(
+ LinkNotFoundError, browser.getLink, "Delete live filesystem")
+ self.assertRaises(
+ Unauthorized, self.getUserBrowser, livefs_url + "/+delete",
+ user=other_person)
+
+ def test_delete_livefs_without_builds(self):
+ # A live filesystem without builds can be deleted.
+ self.useFixture(FakeLogger())
+ livefs = self.factory.makeLiveFS(
+ registrant=self.person, owner=self.person)
+ livefs_url = canonical_url(livefs)
+ owner_url = canonical_url(self.person)
+ browser = self.getViewBrowser(livefs, user=self.person)
+ browser.getLink("Delete live filesystem").click()
+ browser.getControl("Delete live filesystem").click()
+ self.assertEqual(owner_url, browser.url)
+ self.assertRaises(NotFound, browser.open, livefs_url)
+
+ def test_delete_livefs_with_builds(self):
+ # A live filesystem without builds cannot be deleted.
+ livefs = self.factory.makeLiveFS(
+ registrant=self.person, owner=self.person)
+ self.factory.makeLiveFSBuild(livefs=livefs)
+ browser = self.getViewBrowser(livefs, user=self.person)
+ browser.getLink("Delete live filesystem").click()
+ self.assertIn(
+ "This live filesystem cannot be deleted", browser.contents)
+ self.assertRaises(
+ LookupError, browser.getControl, "Delete live filesystem")
+
+
class TestLiveFSView(BrowserTestCase):
layer = LaunchpadFunctionalLayer
=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml 2015-04-20 11:27:54 +0000
+++ lib/lp/soyuz/configure.zcml 2015-05-07 10:56:40 +0000
@@ -1,4 +1,4 @@
-<!-- Copyright 2009-2014 Canonical Ltd. This software is licensed under the
+<!-- Copyright 2009-2015 Canonical Ltd. This software is licensed under the
GNU Affero General Public License version 3 (see the file LICENSE).
-->
@@ -963,6 +963,7 @@
.interfaces.livefs.ILiveFSAdminAttributes"/>
<require
permission="launchpad.Edit"
+ interface=".interfaces.livefs.ILiveFSEdit"
set_schema=".interfaces.livefs.ILiveFSEditableAttributes"
set_attributes="date_last_modified"/>
<require
=== modified file 'lib/lp/soyuz/interfaces/livefs.py'
--- lib/lp/soyuz/interfaces/livefs.py 2014-06-18 18:29:13 +0000
+++ lib/lp/soyuz/interfaces/livefs.py 2015-05-07 10:56:40 +0000
@@ -1,4 +1,4 @@
-# Copyright 2014 Canonical Ltd. This software is licensed under the
+# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Live filesystem interfaces."""
@@ -6,6 +6,7 @@
__metaclass__ = type
__all__ = [
+ 'CannotDeleteLiveFS',
'DuplicateLiveFSName',
'ILiveFS',
'ILiveFSEditableAttributes',
@@ -28,6 +29,7 @@
error_status,
export_as_webservice_collection,
export_as_webservice_entry,
+ export_destructor_operation,
export_factory_operation,
export_read_operation,
exported,
@@ -131,6 +133,11 @@
_message_prefix = "No such live filesystem with this owner/distroseries"
+@error_status(httplib.BAD_REQUEST)
+class CannotDeleteLiveFS(Exception):
+ """This live filesystem cannot be deleted."""
+
+
class ILiveFSView(Interface):
"""`ILiveFS` attributes that require launchpad.View permission."""
@@ -200,6 +207,15 @@
value_type=Reference(schema=Interface), readonly=True)))
+class ILiveFSEdit(Interface):
+ """`ILiveFS` methods that require launchpad.Edit permission."""
+
+ @export_destructor_operation()
+ @operation_for_version("devel")
+ def destroySelf():
+ """Delete this live filesystem, provided that it has no builds."""
+
+
class ILiveFSEditableAttributes(IHasOwner):
"""`ILiveFS` attributes that can be edited.
@@ -240,7 +256,9 @@
"Only build this live filesystem image on virtual builders.")))
-class ILiveFS(ILiveFSView, ILiveFSEditableAttributes, ILiveFSAdminAttributes):
+class ILiveFS(
+ ILiveFSView, ILiveFSEdit, ILiveFSEditableAttributes,
+ ILiveFSAdminAttributes):
"""A buildable live filesystem image."""
# XXX cjwatson 2014-05-06 bug=760849: "beta" is a lie to get WADL
=== modified file 'lib/lp/soyuz/model/livefs.py'
--- lib/lp/soyuz/model/livefs.py 2014-06-18 18:29:13 +0000
+++ lib/lp/soyuz/model/livefs.py 2015-05-07 10:56:40 +0000
@@ -1,4 +1,4 @@
-# Copyright 2014 Canonical Ltd. This software is licensed under the
+# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
__metaclass__ = type
@@ -52,6 +52,7 @@
from lp.services.webapp.interfaces import ILaunchBag
from lp.soyuz.interfaces.archive import ArchiveDisabled
from lp.soyuz.interfaces.livefs import (
+ CannotDeleteLiveFS,
DuplicateLiveFSName,
ILiveFS,
ILiveFSSet,
@@ -210,6 +211,13 @@
order_by = Desc(LiveFSBuild.id)
return self._getBuilds(filter_term, order_by)
+ def destroySelf(self):
+ """See `ILiveFS`."""
+ if not self.builds.is_empty():
+ raise CannotDeleteLiveFS(
+ "Cannot delete a live filesystem with builds.")
+ IStore(LiveFS).remove(self)
+
class LiveFSSet:
"""See `ILiveFSSet`."""
=== added file 'lib/lp/soyuz/templates/livefs-delete.pt'
--- lib/lp/soyuz/templates/livefs-delete.pt 1970-01-01 00:00:00 +0000
+++ lib/lp/soyuz/templates/livefs-delete.pt 2015-05-07 10:56:40 +0000
@@ -0,0 +1,22 @@
+<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">
+ <tal:has-builds condition="view/has_builds">
+ This live filesystem cannot be deleted as builds have been created for
+ it.
+ </tal:has-builds>
+
+ <tal:has-no-builds condition="not: view/has_builds">
+ <div metal:use-macro="context/@@launchpad_form/form"/>
+ </tal:has-no-builds>
+ </div>
+
+</body>
+</html>
=== modified file 'lib/lp/soyuz/tests/test_livefs.py'
--- lib/lp/soyuz/tests/test_livefs.py 2014-09-01 03:45:08 +0000
+++ lib/lp/soyuz/tests/test_livefs.py 2015-05-07 10:56:40 +0000
@@ -1,4 +1,4 @@
-# Copyright 2014 Canonical Ltd. This software is licensed under the
+# Copyright 2014-2015 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test live filesystems."""
@@ -32,6 +32,7 @@
from lp.services.features.testing import FeatureFixture
from lp.services.webapp.interfaces import OAuthPermission
from lp.soyuz.interfaces.livefs import (
+ CannotDeleteLiveFS,
ILiveFS,
ILiveFSSet,
ILiveFSView,
@@ -83,7 +84,8 @@
def test_implements_interfaces(self):
# LiveFS implements ILiveFS.
livefs = self.factory.makeLiveFS()
- self.assertProvides(livefs, ILiveFS)
+ with person_logged_in(livefs.owner):
+ self.assertProvides(livefs, ILiveFS)
def test_avoids_problematic_snapshots(self):
self.assertThat(
@@ -245,6 +247,35 @@
self.assertEqual([], list(livefs.builds))
self.assertEqual([], list(livefs.pending_builds))
+ def test_delete_without_builds(self):
+ # A live filesystem with no builds can be deleted.
+ owner = self.factory.makePerson()
+ distroseries = self.factory.makeDistroSeries()
+ livefs = self.factory.makeLiveFS(
+ registrant=owner, owner=owner, distroseries=distroseries,
+ name=u"condemned")
+ self.assertTrue(
+ getUtility(ILiveFSSet).exists(owner, distroseries, u"condemned"))
+ with person_logged_in(livefs.owner):
+ livefs.destroySelf()
+ self.assertFalse(
+ getUtility(ILiveFSSet).exists(owner, distroseries, u"condemned"))
+
+ def test_delete_with_builds(self):
+ # A live filesystem with builds cannot be deleted.
+ owner = self.factory.makePerson()
+ distroseries = self.factory.makeDistroSeries()
+ livefs = self.factory.makeLiveFS(
+ registrant=owner, owner=owner, distroseries=distroseries,
+ name=u"condemned")
+ self.factory.makeLiveFSBuild(livefs=livefs)
+ self.assertTrue(
+ getUtility(ILiveFSSet).exists(owner, distroseries, u"condemned"))
+ with person_logged_in(livefs.owner):
+ self.assertRaises(CannotDeleteLiveFS, livefs.destroySelf)
+ self.assertTrue(
+ getUtility(ILiveFSSet).exists(owner, distroseries, u"condemned"))
+
class TestLiveFSSet(TestCaseWithFactory):
Follow ups