# HG changeset patch # User cmlenz # Date 1126547295 0 # Node ID bd6234ed6ac53ee60f3419939c032949e50ed5b4 # Parent 8dbddcd0ef004d52e51c0bfe117b2becb4eed225 Allow deletion of build configurations from the web interface. Closes #27. diff --git a/bitten/model.py b/bitten/model.py --- a/bitten/model.py +++ b/bitten/model.py @@ -41,6 +41,29 @@ exists = property(fget=lambda self: self._old_name is not None) + def delete(self, db=None): + """Remove a build configuration and all dependent objects from the + database.""" + assert self.exists, 'Cannot delete non-existing configuration' + if not db: + db = self.env.get_db_cnx() + handle_ta = True + else: + handle_ta = False + + for platform in TargetPlatform.select(self.env, self.name, db=db): + platform.delete(db=db) + + for build in Build.select(self.env, config=self.name, db=db): + build.delete(db=db) + + cursor = db.cursor() + cursor.execute("DELETE FROM bitten_config WHERE name=%s", (self.name,)) + + if handle_ta: + db.commit() + self._old_name = None + def insert(self, db=None): """Insert a new configuration into the database.""" assert not self.exists, 'Cannot insert existing configuration' @@ -340,8 +363,6 @@ else: handle_ta = False - assert self.status == self.PENDING, 'Only pending builds can be deleted' - for step in BuildStep.select(self.env, build=self.id): step.delete(db=db) diff --git a/bitten/tests/model.py b/bitten/tests/model.py --- a/bitten/tests/model.py +++ b/bitten/tests/model.py @@ -10,7 +10,8 @@ import unittest from trac.test import EnvironmentStub -from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, BuildLog +from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ + BuildLog, schema class BuildConfigTestCase(unittest.TestCase): @@ -19,7 +20,7 @@ self.env = EnvironmentStub() db = self.env.get_db_cnx() cursor = db.cursor() - for table in BuildConfig._schema: + for table in schema: for stmt in db.to_sql(table): cursor.execute(stmt) db.commit() @@ -87,7 +88,7 @@ cursor.fetchone()) self.assertEqual(None, cursor.fetchone()) - def test_config_update_name(self): + def test_update_name(self): db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("INSERT INTO bitten_config (name,path,label,active) " @@ -114,6 +115,23 @@ config.name = None self.assertRaises(AssertionError, config.update) + def test_delete(self): + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute("INSERT INTO bitten_config (name,path,label,active) " + "VALUES (%s,%s,%s,%s)", ('test', 'trunk', 'Test', 0)) + + config = BuildConfig.fetch(self.env, 'test') + config.delete() + self.assertEqual(False, config.exists) + + cursor.execute("SELECT * FROM bitten_config WHERE name=%s", ('test',)) + self.assertEqual(None, cursor.fetchone()) + + def test_delete_non_existing(self): + config = BuildConfig(self.env, 'test') + self.assertRaises(AssertionError, config.delete) + class TargetPlatformTestCase(unittest.TestCase): diff --git a/bitten/trac_ext/templates/bitten_config.cs b/bitten/trac_ext/templates/bitten_config.cs --- a/bitten/trac_ext/templates/bitten_config.cs +++ b/bitten/trac_ext/templates/bitten_config.cs @@ -77,6 +77,16 @@ +

Are you sure you want to delete the build configuration "?

+

This will also delete all builds performed for that configuration.

+
+ + + +
This build configuration is currently @@ -113,13 +123,15 @@ /each ?>
-
-
- - -
-
+ + +
+ + +
diff --git a/bitten/trac_ext/tests/web_ui.py b/bitten/trac_ext/tests/web_ui.py --- a/bitten/trac_ext/tests/web_ui.py +++ b/bitten/trac_ext/tests/web_ui.py @@ -43,7 +43,6 @@ PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(Request, cgi_location='', path_info='/build', args={}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -56,7 +55,6 @@ PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') req = Mock(Request, cgi_location='', path_info='/build', args={}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -72,13 +70,13 @@ PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(Request, cgi_location='', path_info='/build/test', args={}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) self.assertEqual('view_config', req.hdf['page.mode']) + self.assertEqual('0', req.hdf.get('build.config.can_delete', '0')) self.assertEqual('0', req.hdf.get('build.config.can_modify', '0')) def test_view_config_admin(self): @@ -89,12 +87,12 @@ PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') req = Mock(Request, cgi_location='', path_info='/build/test', args={}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) module.process_request(req) + self.assertEqual('1', req.hdf.get('config.can_delete')) self.assertEqual('1', req.hdf.get('config.can_modify')) def test_new_config(self): @@ -102,7 +100,6 @@ req = Mock(Request, cgi_location='', path_info='/build', args={'action': 'new'}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -121,7 +118,6 @@ perm=PermissionCache(self.env, 'joe'), args={'action': 'new', 'name': 'test', 'path': 'test/trunk', 'label': 'Test', 'description': 'Bla bla'}) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -145,7 +141,6 @@ redirect=redirect, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'), args={'action': 'new', 'cancel': '1', 'name': 'test'}) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -154,6 +149,66 @@ self.assertEqual(None, BuildConfig.fetch(self.env, 'test')) + def test_delete_config(self): + config = BuildConfig(self.env) + config.name = 'test' + config.insert() + + PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') + req = Mock(Request, cgi_location='', path_info='/build/test', + args={'action': 'delete'}, hdf=HDFWrapper(), + perm=PermissionCache(self.env, 'joe')) + + module = BuildConfigController(self.env) + assert module.match_request(req) + module.process_request(req) + + self.assertEqual('delete_config', req.hdf['page.mode']) + + def test_delete_config_submit(self): + config = BuildConfig(self.env) + config.name = 'test' + config.insert() + + PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(Request, method='POST', cgi_location='', + path_info='/build/test', redirect=redirect, hdf=HDFWrapper(), + perm=PermissionCache(self.env, 'joe'), + args={'action': 'delete'}) + + module = BuildConfigController(self.env) + assert module.match_request(req) + self.assertRaises(RequestDone, module.process_request, req) + self.assertEqual('/trac.cgi/build', redirected_to[0]) + + self.assertEqual(None, BuildConfig.fetch(self.env, 'test')) + + def test_edit_config_cancel(self): + config = BuildConfig(self.env) + config.name = 'test' + config.insert() + + PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(Request, method='POST', cgi_location='', + path_info='/build/test', redirect=redirect, hdf=HDFWrapper(), + perm=PermissionCache(self.env, 'joe'), + args={'action': 'delete', 'cancel': ''}) + + module = BuildConfigController(self.env) + assert module.match_request(req) + self.assertRaises(RequestDone, module.process_request, req) + self.assertEqual('/trac.cgi/build/test', redirected_to[0]) + + self.assertEqual(True, BuildConfig.fetch(self.env, 'test').exists) + def test_edit_config(self): config = BuildConfig(self.env) config.name = 'test' @@ -163,7 +218,6 @@ req = Mock(Request, cgi_location='', path_info='/build/test', args={'action': 'edit'}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -186,7 +240,6 @@ perm=PermissionCache(self.env, 'joe'), args={'action': 'edit', 'name': 'foo', 'path': 'test/trunk', 'label': 'Test', 'description': 'Bla bla'}) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -214,8 +267,7 @@ req = Mock(Request, method='POST', cgi_location='', path_info='/build/test', redirect=redirect, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'), - args={'action': 'edit', 'cancel': '1'}) - req.hdf['htdocs_location'] = '/htdocs' + args={'action': 'edit', 'cancel': ''}) module = BuildConfigController(self.env) assert module.match_request(req) @@ -231,7 +283,6 @@ req = Mock(Request, cgi_location='', path_info='/build/test', args={'action': 'edit', 'new': '1'}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -253,7 +304,6 @@ path_info='/build/test', redirect=redirect, args={'action': 'new', 'name': 'Test'}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -272,9 +322,8 @@ raise RequestDone req = Mock(Request, method='POST', cgi_location='', path_info='/build/test', redirect=redirect, - args={'action': 'new', 'cancel': '1'}, hdf=HDFWrapper(), + args={'action': 'new', 'cancel': ''}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -295,7 +344,6 @@ req = Mock(Request, cgi_location='', path_info='/build/test', args={'action': 'edit', 'platform': platform.id}, hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -323,7 +371,6 @@ args={'action': 'edit', 'platform': platform.id, 'name': 'Test'}, perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) @@ -348,9 +395,8 @@ req = Mock(Request, method='POST', cgi_location='', path_info='/build/test', redirect=redirect, hdf=HDFWrapper(), args={'action': 'edit', 'platform': platform.id, - 'cancel': '1'}, + 'cancel': ''}, perm=PermissionCache(self.env, 'joe')) - req.hdf['htdocs_location'] = '/htdocs' module = BuildConfigController(self.env) assert module.match_request(req) diff --git a/bitten/trac_ext/web_ui.py b/bitten/trac_ext/web_ui.py --- a/bitten/trac_ext/web_ui.py +++ b/bitten/trac_ext/web_ui.py @@ -100,6 +100,8 @@ if config: if action == 'new': self._do_create_platform(req, config) + elif action == 'delete': + self._do_delete_config(req, config) else: platform_id = req.args.get('platform') if platform_id: @@ -118,7 +120,9 @@ self._do_create_config(req) else: if config: - if action == 'edit': + if action == 'delete': + self._render_config_confirm(req, config) + elif action == 'edit': platform_id = req.args.get('platform') if platform_id: platform = TargetPlatform.fetch(self.env, @@ -166,6 +170,19 @@ req.redirect(self.env.href.build(config.name)) + def _do_delete_config(self, req, config_name): + """Save changes to a build configuration.""" + req.perm.assert_permission('BUILD_DELETE') + + if 'cancel' in req.args: + req.redirect(self.env.href.build(config_name)) + + config = BuildConfig.fetch(self.env, config_name) + assert config, 'Build configuration "%s" does not exist' % config_name + + config.delete() + req.redirect(self.env.href.build()) + def _do_save_config(self, req, config_name): """Save changes to a build configuration.""" req.perm.assert_permission('BUILD_MODIFY') @@ -288,7 +305,8 @@ 'name': config.name, 'label': config.label, 'path': config.path, 'active': config.active, 'description': description, 'browser_href': self.env.href.browser(config.path), - 'can_modify': req.perm.has_permission('BUILD_MODIFY') + 'can_modify': req.perm.has_permission('BUILD_MODIFY'), + 'can_delete': req.perm.has_permission('BUILD_DELETE') } req.hdf['page.mode'] = 'view_config' @@ -321,6 +339,14 @@ except TracError, e: self.log.error('Error accessing repository info', exc_info=True) + def _render_config_confirm(self, req, config_name): + req.perm.assert_permission('BUILD_DELETE') + config = BuildConfig.fetch(self.env, config_name) + req.hdf['title'] = 'Delete Build Configuration "%s"' \ + % escape(config.label or config.name) + req.hdf['config'] = {'name': config.name} + req.hdf['page.mode'] = 'delete_config' + def _render_config_form(self, req, config_name=None): config = BuildConfig.fetch(self.env, config_name) if config: