Merge lp:~trapnine/maas/fix-1570152 into lp:~maas-committers/maas/trunk

Proposed by Jeffrey C Jones
Status: Merged
Approved by: Jeffrey C Jones
Approved revision: no longer in the source branch.
Merged at revision: 4939
Proposed branch: lp:~trapnine/maas/fix-1570152
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 690 lines (+255/-198)
16 files modified
src/maasserver/static/js/angular/controllers/add_device.js (+2/-2)
src/maasserver/static/js/angular/controllers/domain_details.js (+4/-23)
src/maasserver/static/js/angular/controllers/fabric_details.js (+4/-23)
src/maasserver/static/js/angular/controllers/space_details.js (+4/-23)
src/maasserver/static/js/angular/controllers/subnet_details.js (+36/-4)
src/maasserver/static/js/angular/controllers/tests/test_add_device.js (+4/-4)
src/maasserver/static/js/angular/controllers/tests/test_add_domain.js (+0/-35)
src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js (+39/-0)
src/maasserver/static/js/angular/factories/subnets.js (+5/-0)
src/maasserver/static/js/angular/factories/tests/test_spaces.js (+13/-0)
src/maasserver/static/js/angular/factories/tests/test_subnets.js (+13/-0)
src/maasserver/static/js/angular/factories/tests/test_vlans.js (+13/-0)
src/maasserver/static/partials/subnet-details.html (+38/-84)
src/maasserver/websockets/handlers/subnet.py (+9/-0)
src/maasserver/websockets/handlers/tests/test_space.py (+36/-0)
src/maasserver/websockets/handlers/tests/test_subnet.py (+35/-0)
To merge this branch: bzr merge lp:~trapnine/maas/fix-1570152
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+292108@code.launchpad.net

Commit message

Add delete button to subnet details page.

Description of the change

Add delete button to subnet details page.

Also added missing 'delete' testing in space and vlan factories/handlers (drive-by).

To post a comment you must log in.
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Not going to block you on anything, but please fix the issues before landing.

review: Approve
Revision history for this message
Jeffrey C Jones (trapnine) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/static/js/angular/controllers/add_device.js'
2--- src/maasserver/static/js/angular/controllers/add_device.js 2016-03-28 13:54:47 +0000
3+++ src/maasserver/static/js/angular/controllers/add_device.js 2016-04-20 05:12:39 +0000
4@@ -238,7 +238,7 @@
5 };
6
7 // Convert the Python dict error message to displayed message.
8- $scope.convertPythonDictToErrorMsg = function(pythonError) {
9+ $scope.devicePythonDictToErrorMsg = function(pythonError) {
10 var elements = pythonError.match(/'([A-Za-z0-9 \.:_\-]+)'/g);
11 var result = '', msg = '';
12 for (k=0; k < elements.length; ++k) {
13@@ -282,7 +282,7 @@
14 $scope.hide();
15 }
16 }, function(error) {
17- $scope.error = $scope.convertPythonDictToErrorMsg(error);
18+ $scope.error = $scope.devicePythonDictToErrorMsg(error);
19 });
20 };
21
22
23=== modified file 'src/maasserver/static/js/angular/controllers/domain_details.js'
24--- src/maasserver/static/js/angular/controllers/domain_details.js 2016-03-28 13:54:47 +0000
25+++ src/maasserver/static/js/angular/controllers/domain_details.js 2016-04-20 05:12:39 +0000
26@@ -66,29 +66,10 @@
27 $scope.confirmingDelete = false;
28 };
29
30- // Convert the Python dict error message to displayed message.
31- // We know it's probably a form ValidationError dictionary, so just use
32- // it as such, and recover if that doesn't parse as JSON.
33- $scope.convertPythonDictToErrorMsg = function(pythonError) {
34- var dictionary;
35- try {
36- dictionary = JSON.parse(pythonError);
37- } catch(e) {
38- if (e instanceof SyntaxError) {
39- return pythonError;
40- } else {
41- throw e;
42- }
43- }
44- var result = '', msg = '';
45- var key;
46- angular.forEach(dictionary, function(value, key) {
47- result += key + ": ";
48- angular.forEach(dictionary[key], function(value) {
49- result += value + " ";
50- });
51- });
52- return result;
53+ // Called when an error message from the Python world needs to be
54+ // rendered in the UI.
55+ $scope.convertPythonDictToErrorMsg = function(error) {
56+ return ManagerHelperService.parseLikelyValidationError(error);
57 };
58
59 // Called when the confirm delete domain button is pressed.
60
61=== modified file 'src/maasserver/static/js/angular/controllers/fabric_details.js'
62--- src/maasserver/static/js/angular/controllers/fabric_details.js 2016-03-25 13:52:27 +0000
63+++ src/maasserver/static/js/angular/controllers/fabric_details.js 2016-04-20 05:12:39 +0000
64@@ -118,29 +118,10 @@
65 $scope.confirmingDelete = false;
66 };
67
68- // Convert the Python dict error message to displayed message.
69- // We know it's probably a form ValidationError dictionary, so just use
70- // it as such, and recover if that doesn't parse as JSON.
71- $scope.convertPythonDictToErrorMsg = function(pythonError) {
72- var dictionary;
73- try {
74- dictionary = JSON.parse(pythonError);
75- } catch(e) {
76- if(e instanceof SyntaxError) {
77- return pythonError;
78- } else {
79- throw e;
80- }
81- }
82- var result = '', msg = '';
83- var key;
84- angular.forEach(dictionary, function(value, key) {
85- result += key + ": ";
86- angular.forEach(dictionary[key], function(value) {
87- result += value + " ";
88- });
89- });
90- return result;
91+ // Called when an error message from the Python world needs to be
92+ // rendered in the UI.
93+ $scope.convertPythonDictToErrorMsg = function(error) {
94+ return ManagerHelperService.parseLikelyValidationError(error);
95 };
96
97 // Called when the confirm delete fabric button is pressed.
98
99=== modified file 'src/maasserver/static/js/angular/controllers/space_details.js'
100--- src/maasserver/static/js/angular/controllers/space_details.js 2016-03-25 13:52:27 +0000
101+++ src/maasserver/static/js/angular/controllers/space_details.js 2016-04-20 05:12:39 +0000
102@@ -95,29 +95,10 @@
103 $scope.confirmingDelete = false;
104 };
105
106- // Convert the Python dict error message to displayed message.
107- // We know it's probably a form ValidationError dictionary, so just use
108- // it as such, and recover if that doesn't parse as JSON.
109- $scope.convertPythonDictToErrorMsg = function(pythonError) {
110- var dictionary;
111- try {
112- dictionary = JSON.parse(pythonError);
113- } catch(e) {
114- if(e instanceof SyntaxError) {
115- return pythonError;
116- } else {
117- throw e;
118- }
119- }
120- var result = '', msg = '';
121- var key;
122- angular.forEach(dictionary, function(value, key) {
123- result += key + ": ";
124- angular.forEach(dictionary[key], function(value) {
125- result += value + " ";
126- });
127- });
128- return result;
129+ // Called when an error message from the Python world needs to be
130+ // rendered in the UI.
131+ $scope.convertPythonDictToErrorMsg = function(error) {
132+ return ManagerHelperService.parseLikelyValidationError(error);
133 };
134
135 // Called when the confirm delete space button is pressed.
136
137=== modified file 'src/maasserver/static/js/angular/controllers/subnet_details.js'
138--- src/maasserver/static/js/angular/controllers/subnet_details.js 2016-03-28 13:54:47 +0000
139+++ src/maasserver/static/js/angular/controllers/subnet_details.js 2016-04-20 05:12:39 +0000
140@@ -7,11 +7,11 @@
141 angular.module('MAAS').controller('SubnetDetailsController', [
142 '$scope', '$rootScope', '$routeParams', '$filter', '$location',
143 'SubnetsManager', 'IPRangesManager', 'SpacesManager', 'VLANsManager',
144- 'FabricsManager', 'ManagerHelperService', 'ErrorService',
145+ 'UsersManager', 'FabricsManager', 'ManagerHelperService', 'ErrorService',
146 function(
147 $scope, $rootScope, $routeParams, $filter, $location, SubnetsManager,
148- IPRangesManager, SpacesManager, VLANsManager, FabricsManager,
149- ManagerHelperService, ErrorService) {
150+ IPRangesManager, SpacesManager, VLANsManager, UsersManager,
151+ FabricsManager, ManagerHelperService, ErrorService) {
152
153 // Set title and page.
154 $rootScope.title = "Loading...";
155@@ -63,6 +63,38 @@
156 return FabricsManager.getItemFromList(fabric_id).name;
157 };
158
159+ // Return true if the authenticated user is super user.
160+ $scope.isSuperUser = function() {
161+ return UsersManager.isSuperUser();
162+ };
163+
164+ // Called when the delete subnet button is pressed.
165+ $scope.deleteButton = function() {
166+ $scope.error = null;
167+ $scope.confirmingDelete = true;
168+ };
169+
170+ // Called when the cancel delete subnet button is pressed.
171+ $scope.cancelDeleteButton = function() {
172+ $scope.confirmingDelete = false;
173+ };
174+
175+ // Called when an error message from the Python world needs to be
176+ // rendered in the UI.
177+ $scope.convertPythonDictToErrorMsg = function(error) {
178+ return ManagerHelperService.parseLikelyValidationError(error);
179+ };
180+
181+ // Called when the confirm delete subnet button is pressed.
182+ $scope.deleteConfirmButton = function() {
183+ SubnetsManager.deleteSubnet($scope.subnet).then(function() {
184+ $scope.confirmingDelete = false;
185+ $location.path("/networks");
186+ }, function(error) {
187+ $scope.error = $scope.convertPythonDictToErrorMsg(error);
188+ });
189+ };
190+
191 // Called when the subnet has been loaded.
192 function subnetLoaded(subnet) {
193 $scope.subnet = subnet;
194@@ -78,7 +110,7 @@
195 // Load all the required managers.
196 ManagerHelperService.loadManagers([
197 SubnetsManager, IPRangesManager, SpacesManager, VLANsManager,
198- FabricsManager
199+ UsersManager, FabricsManager
200 ]).then(function() {
201 // Possibly redirected from another controller that already had
202 // this subnet set to active. Only call setActiveItem if not
203
204=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_add_device.js'
205--- src/maasserver/static/js/angular/controllers/tests/test_add_device.js 2016-03-28 13:54:47 +0000
206+++ src/maasserver/static/js/angular/controllers/tests/test_add_device.js 2016-04-20 05:12:39 +0000
207@@ -763,14 +763,14 @@
208 });
209 });
210
211- describe("convertPythonDictToErrorMsg", function() {
212+ describe("devicePythonDictToErrorMsg", function() {
213 it("converts hostname error for display",
214 function() {
215 var controller = makeController();
216 var errorMsg = makeName("error");
217 var error = "{'hostname': ['Node " + errorMsg + "']}";
218 var expected = "Device " + errorMsg + " ";
219- expect($scope.convertPythonDictToErrorMsg(
220+ expect($scope.devicePythonDictToErrorMsg(
221 error)).toBe(expected);
222 });
223
224@@ -780,7 +780,7 @@
225 var errorMsg = makeName("error");
226 var error = "{'mac_addresses': ['" + errorMsg + "']}";
227 var expected = errorMsg + " ";
228- expect($scope.convertPythonDictToErrorMsg(
229+ expect($scope.devicePythonDictToErrorMsg(
230 error)).toBe(expected);
231 });
232
233@@ -792,7 +792,7 @@
234 var error = "{'" + errorSegment1 +
235 "': ['" + errorSegment2 + "']}";
236 var expected = errorSegment1 + errorSegment2;
237- expect($scope.convertPythonDictToErrorMsg(
238+ expect($scope.devicePythonDictToErrorMsg(
239 error)).toBe(expected);
240 });
241 });
242
243=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_add_domain.js'
244--- src/maasserver/static/js/angular/controllers/tests/test_add_domain.js 2016-03-28 13:54:47 +0000
245+++ src/maasserver/static/js/angular/controllers/tests/test_add_domain.js 2016-04-20 05:12:39 +0000
246@@ -270,39 +270,4 @@
247 expect($scope.error).toBe(errorMsg);
248 });
249 });
250-
251- describe("convertPythonDictToErrorMsg", function() {
252- it("converts name error for display",
253- function() {
254- var controller = makeController();
255- var errorMsg = makeName("error");
256- var error = '{"name": ["' + errorMsg + '"]}';
257- var expected = errorMsg;
258- expect($scope.convertPythonDictToErrorMsg(
259- error)).toBe(expected);
260- });
261-
262- it("concatenates array elements",
263- function() {
264- var controller = makeController();
265- var errorSegment1 = makeName("key");
266- var errorSegment2 = makeName("error");
267- var errorSegment3 = makeName("error");
268- var error = '{"' + errorSegment1 +
269- '": ["' + errorSegment2 + '", "' +
270- errorSegment3 + '"]}';
271- var expected = errorSegment2 + " " + errorSegment3;
272- expect($scope.convertPythonDictToErrorMsg(
273- error)).toBe(expected);
274- });
275-
276- it("converts non-dictionary correctly",
277- function() {
278- var controller = makeController();
279- var errorSegment1 = makeName("error");
280- var expected = errorSegment1;
281- expect($scope.convertPythonDictToErrorMsg(
282- errorSegment1)).toBe(expected);
283- });
284- });
285 });
286
287=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js'
288--- src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js 2016-03-28 13:54:47 +0000
289+++ src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js 2016-04-20 05:12:39 +0000
290@@ -221,4 +221,43 @@
291 expect($scope.subnet.dns_servers).toEqual(["127.0.0.1", "127.0.0.2"]);
292 expect($scope.all_dns_servers).toBe("127.0.0.1 127.0.0.2");
293 });
294+
295+ describe("deleteButton", function() {
296+
297+ it("confirms delete", function() {
298+ var controller = makeControllerResolveSetActiveItem();
299+ $scope.deleteButton();
300+ expect($scope.confirmingDelete).toBe(true);
301+ });
302+
303+ it("clears error", function() {
304+ var controller = makeControllerResolveSetActiveItem();
305+ $scope.error = makeName("error");
306+ $scope.deleteButton();
307+ expect($scope.error).toBeNull();
308+ });
309+ });
310+
311+ describe("cancelDeleteButton", function() {
312+
313+ it("cancels delete", function() {
314+ var controller = makeControllerResolveSetActiveItem();
315+ $scope.deleteButton();
316+ $scope.cancelDeleteButton();
317+ expect($scope.confirmingDelete).toBe(false);
318+ });
319+ });
320+
321+ describe("deleteSubnet", function() {
322+
323+ it("calls deleteSubnet", function() {
324+ var controller = makeController();
325+ var deleteSubnet = spyOn(SubnetsManager, "deleteSubnet");
326+ var defer = $q.defer();
327+ deleteSubnet.and.returnValue(defer.promise);
328+ $scope.deleteConfirmButton();
329+ expect(deleteSubnet).toHaveBeenCalled();
330+ });
331+ });
332+
333 });
334
335=== modified file 'src/maasserver/static/js/angular/factories/subnets.js'
336--- src/maasserver/static/js/angular/factories/subnets.js 2016-03-28 13:54:47 +0000
337+++ src/maasserver/static/js/angular/factories/subnets.js 2016-04-20 05:12:39 +0000
338@@ -75,5 +75,10 @@
339 return RegionConnection.callMethod("subnet.create", subnet);
340 };
341
342+ // Delete the subnet.
343+ SubnetsManager.prototype.deleteSubnet = function(subnet) {
344+ return RegionConnection.callMethod("subnet.delete", subnet);
345+ };
346+
347 return new SubnetsManager();
348 }]);
349
350=== modified file 'src/maasserver/static/js/angular/factories/tests/test_spaces.js'
351--- src/maasserver/static/js/angular/factories/tests/test_spaces.js 2016-03-28 13:54:47 +0000
352+++ src/maasserver/static/js/angular/factories/tests/test_spaces.js 2016-04-20 05:12:39 +0000
353@@ -68,4 +68,17 @@
354 );
355 });
356 });
357+
358+ describe("delete", function() {
359+
360+ it("calls the region with expected parameters", function() {
361+ var obj = {};
362+ var result = {};
363+ spyOn(RegionConnection, "callMethod").and.returnValue(result);
364+ expect(SpacesManager.deleteSpace(obj)).toBe(result);
365+ expect(RegionConnection.callMethod).toHaveBeenCalledWith(
366+ "space.delete", obj
367+ );
368+ });
369+ });
370 });
371
372=== modified file 'src/maasserver/static/js/angular/factories/tests/test_subnets.js'
373--- src/maasserver/static/js/angular/factories/tests/test_subnets.js 2016-03-28 13:54:47 +0000
374+++ src/maasserver/static/js/angular/factories/tests/test_subnets.js 2016-04-20 05:12:39 +0000
375@@ -73,4 +73,17 @@
376 );
377 });
378 });
379+
380+ describe("delete", function() {
381+
382+ it("calls the region with expected parameters", function() {
383+ var obj = {};
384+ var result = {};
385+ spyOn(RegionConnection, "callMethod").and.returnValue(result);
386+ expect(SubnetsManager.deleteSubnet(obj)).toBe(result);
387+ expect(RegionConnection.callMethod).toHaveBeenCalledWith(
388+ "subnet.delete", obj
389+ );
390+ });
391+ });
392 });
393
394=== modified file 'src/maasserver/static/js/angular/factories/tests/test_vlans.js'
395--- src/maasserver/static/js/angular/factories/tests/test_vlans.js 2016-03-28 13:54:47 +0000
396+++ src/maasserver/static/js/angular/factories/tests/test_vlans.js 2016-04-20 05:12:39 +0000
397@@ -102,4 +102,17 @@
398 );
399 });
400 });
401+
402+ describe("delete", function() {
403+
404+ it("calls the region with expected parameters", function() {
405+ var obj = { id: "whatever" };
406+ var result = {};
407+ spyOn(RegionConnection, "callMethod").and.returnValue(result);
408+ expect(VLANsManager.deleteVLAN(obj)).toBe(result);
409+ expect(RegionConnection.callMethod).toHaveBeenCalledWith(
410+ "vlan.delete", obj, true
411+ );
412+ });
413+ });
414 });
415
416=== modified file 'src/maasserver/static/partials/subnet-details.html'
417--- src/maasserver/static/partials/subnet-details.html 2016-03-28 13:54:47 +0000
418+++ src/maasserver/static/partials/subnet-details.html 2016-04-20 05:12:39 +0000
419@@ -1,83 +1,42 @@
420-<div data-ng-hide="loaded">
421- <header class="page-header">
422- <div class="inner-wrapper">
423- <h1 class="page-header__title twelve-col">Loading...</h1>
424- </div>
425- </header>
426-</div>
427+<header class="page-header margin-bottom" data-maas-sticky-header>
428+ <div class="inner-wrapper">
429+ <h1 class="page-header__title eight-col">{$ subnet.cidr $}</h1>
430+ <div data-ng-hide="loaded">
431+ <div class="inner-wrapper">
432+ <h1 class="page-header__title twelve-col">Loading...</h1>
433+ </div>
434+ </div>
435+ <div class="page-header__actions ng-hide" data-ng-show="isSuperUser() && !isDefaultSubnet() && !loading">
436+ <div class="right no-margin-bottom last-col">
437+ <button class="cta-ubuntu"
438+ data-ng-click="deleteButton()"
439+ data-ng-hide="confirmingDelete">Delete Subnet</button>
440+ </div>
441+ </div>
442+ <div class="page-header__dropdown ng-hide" data-ng-show="confirmingDelete">
443+ <div class="page-header__feedback ng-hide" data-ng-show="!error">
444+ <p class="page-header__feedback-message error">
445+ Are you sure you want to delete this subnet and release all
446+ IP addresses assigned within its range?
447+ </p>
448+ <div class="right">
449+ <a href="" class="margin-right" data-ng-click="cancelDeleteButton()">Cancel</a>
450+ <button class="cta-ubuntu" data-ng-click="deleteConfirmButton()">Go</button>
451+ </div>
452+ </div>
453+ <div class="page-header__feedback ng-hide" data-ng-show="error">
454+ <p class="page-header__feedback-message error">
455+ {$ error $}
456+ </p>
457+ <div class="right">
458+ <a href="" class="margin-right" data-ng-click="cancelDeleteButton()">Cancel</a>
459+ <button class="cta-ubuntu" data-ng-click="deleteConfirmButton()">Retry</button>
460+ </div>
461+ </div>
462+ </div>
463+ </div>
464+</header>
465 <div data-ng-if="loaded">
466- <header class="page-header margin-bottom" data-maas-sticky-header>
467- <div class="inner-wrapper">
468- <h1 class="page-header__title eight-col">{$ subnet.cidr $}</h1>
469- <div class="page-header__actions">
470- <div class="page-header__cta">
471- <div class="right cta-group ng-hide">
472- <a class="cta-group__link ng-binding" data-ng-click="shown=!shown">Take action</a>
473- <ul class="cta-group__dropdown" role="menu" data-ng-show="shown">
474- <!-- <li class="cta-group__item ng-scope">
475- <a>Fabric</a>
476- </li>
477- <li class="cta-group__item ng-scope">
478- <a>VLAN</a>
479- </li>
480- <li class="cta-group__item ng-scope">
481- <a>Space</a>
482- </li>
483- <li class="cta-group__item ng-scope">
484- <a>Subnet</a>
485- </li> -->
486- </ul>
487- </div>
488- </div>
489- <div class="page-header__cta ng-hide">
490- <span class="page-header__cta-feedback">
491- 2 Selected
492- </span>
493- <div class="right cta-group">
494- <a class="cta-group__link ng-binding" data-ng-click="shown=!shown">Take action</a>
495- <ul class="cta-group__dropdown ng-hide" role="menu" data-ng-show="shown">
496- <li class="cta-group__item ng-scope">
497- <a>Move subnet</a>
498- </li>
499- <li class="cta-group__item ng-scope">
500- <a>Remove subnet</a>
501- </li>
502- </ul>
503- </div>
504- </div>
505- </div>
506- <div class="page-header__dropdown border ng-hide">
507- <form class="form-inline tweleve-col padding-top--ten padding-bottom--ten">
508- <div class="form__group">
509- <label for="move-subnet">Move this subnet to</label>
510- <select name="move-subnet" id="move-subnet">
511- <option value="1">Space 1</option>
512- <option value="2">Space 2</option>
513- <option value="3">Space 3</option>
514- </select>
515- <div class="right">
516- <button class="cta-ubuntu text-button">Cancel</button>
517- <button class="cta-ubuntu">Move subnet</button>
518- </div>
519- </div>
520- </form>
521- <div class="page-header__feedback">
522- <p class="page-header__feedback-message warning">Are you sure you want to remove this subnet release all IP addresses assigned within its range?</p>
523- <div class="right">
524- <button class="cta-ubuntu text-button">Cancel</button>
525- <button class="cta-ubuntu">Remove subnet</button>
526- </div>
527- </div>
528- <div class="page-header__feedback">
529- <p class="page-header__feedback-message warning">Are you sure you want to remove this subnet?</p>
530- <div class="right">
531- <button class="cta-ubuntu text-button">Cancel</button>
532- <button class="cta-ubuntu">Remove subnet</button>
533- </div>
534- </div>
535- </div>
536- </div>
537- </header>
538 <section class="row">
539 <div class="inner-wrapper">
540 <div class="tweleve-col">
541@@ -290,11 +249,6 @@
542 -->
543 </main>
544 </div>
545- <!-- XXX mpontillo re-enable buttons when adding edit mode -->
546- <div class="tweleve-col padding-bottom ng-hide">
547- <a class="link-cta-ubuntu secondary margin-right">Reserve range</a>
548- <a class="link-cta-ubuntu secondary">Reserve dynamic range</a>
549- </div>
550 </div>
551 <div class="tweleve-col">
552 <div class="six-col">
553
554=== modified file 'src/maasserver/websockets/handlers/subnet.py'
555--- src/maasserver/websockets/handlers/subnet.py 2016-04-14 15:25:36 +0000
556+++ src/maasserver/websockets/handlers/subnet.py 2016-04-20 05:12:39 +0000
557@@ -7,6 +7,7 @@
558 "SubnetHandler",
559 ]
560
561+from maasserver.enum import NODE_PERMISSION
562 from maasserver.models.subnet import Subnet
563 from maasserver.websockets.handlers.timestampedmodel import (
564 TimestampedModelHandler,
565@@ -28,6 +29,7 @@
566 pk = 'id'
567 allowed_methods = [
568 'create',
569+ 'delete',
570 'get',
571 'list',
572 'set_active'
573@@ -46,3 +48,10 @@
574 data["ip_addresses"] = subnet.render_json_for_related_ips(
575 with_username=True, with_node_summary=True)
576 return data
577+
578+ def delete(self, parameters):
579+ """Delete this Subnet."""
580+ subnet = self.get_object(parameters)
581+ assert self.user.has_perm(
582+ NODE_PERMISSION.ADMIN, subnet), "Permission denied."
583+ subnet.delete()
584
585=== modified file 'src/maasserver/websockets/handlers/tests/test_space.py'
586--- src/maasserver/websockets/handlers/tests/test_space.py 2016-03-28 13:54:47 +0000
587+++ src/maasserver/websockets/handlers/tests/test_space.py 2016-04-20 05:12:39 +0000
588@@ -8,8 +8,11 @@
589 from maasserver.models.space import Space
590 from maasserver.testing.factory import factory
591 from maasserver.testing.testcase import MAASServerTestCase
592+from maasserver.utils.orm import reload_object
593 from maasserver.websockets.handlers.space import SpaceHandler
594 from maasserver.websockets.handlers.timestampedmodel import dehydrate_datetime
595+from testtools import ExpectedException
596+from testtools.matchers import Equals
597
598
599 class TestSpaceHandler(MAASServerTestCase):
600@@ -51,3 +54,36 @@
601 self.assertItemsEqual(
602 expected_spaces,
603 handler.list({}))
604+
605+
606+class TestSpaceHandlerDelete(MAASServerTestCase):
607+
608+ def test__delete_as_admin_success(self):
609+ user = factory.make_admin()
610+ handler = SpaceHandler(user, {})
611+ space = factory.make_Space()
612+ handler.delete({
613+ "id": space.id,
614+ })
615+ space = reload_object(space)
616+ self.assertThat(space, Equals(None))
617+
618+ def test__delete_as_non_admin_asserts(self):
619+ user = factory.make_User()
620+ handler = SpaceHandler(user, {})
621+ space = factory.make_Space()
622+ with ExpectedException(AssertionError, "Permission denied."):
623+ handler.delete({
624+ "id": space.id,
625+ })
626+
627+ def test__reloads_user(self):
628+ user = factory.make_admin()
629+ handler = SpaceHandler(user, {})
630+ space = factory.make_Space()
631+ user.is_superuser = False
632+ user.save()
633+ with ExpectedException(AssertionError, "Permission denied."):
634+ handler.delete({
635+ "id": space.id,
636+ })
637
638=== modified file 'src/maasserver/websockets/handlers/tests/test_subnet.py'
639--- src/maasserver/websockets/handlers/tests/test_subnet.py 2016-04-14 15:25:36 +0000
640+++ src/maasserver/websockets/handlers/tests/test_subnet.py 2016-04-20 05:12:39 +0000
641@@ -8,10 +8,12 @@
642 from maasserver.models.subnet import Subnet
643 from maasserver.testing.factory import factory
644 from maasserver.testing.testcase import MAASServerTestCase
645+from maasserver.utils.orm import reload_object
646 from maasserver.websockets.handlers.subnet import SubnetHandler
647 from maasserver.websockets.handlers.timestampedmodel import dehydrate_datetime
648 from netaddr import IPNetwork
649 from provisioningserver.utils.network import IPRangeStatistics
650+from testtools import ExpectedException
651 from testtools.matchers import Equals
652
653
654@@ -60,3 +62,36 @@
655 self.assertItemsEqual(
656 expected_subnets,
657 handler.list({}))
658+
659+
660+class TestSubnetHandlerDelete(MAASServerTestCase):
661+
662+ def test__delete_as_admin_success(self):
663+ user = factory.make_admin()
664+ handler = SubnetHandler(user, {})
665+ subnet = factory.make_Subnet()
666+ handler.delete({
667+ "id": subnet.id,
668+ })
669+ subnet = reload_object(subnet)
670+ self.assertThat(subnet, Equals(None))
671+
672+ def test__delete_as_non_admin_asserts(self):
673+ user = factory.make_User()
674+ handler = SubnetHandler(user, {})
675+ subnet = factory.make_Subnet()
676+ with ExpectedException(AssertionError, "Permission denied."):
677+ handler.delete({
678+ "id": subnet.id,
679+ })
680+
681+ def test__reloads_user(self):
682+ user = factory.make_admin()
683+ handler = SubnetHandler(user, {})
684+ subnet = factory.make_Subnet()
685+ user.is_superuser = False
686+ user.save()
687+ with ExpectedException(AssertionError, "Permission denied."):
688+ handler.delete({
689+ "id": subnet.id,
690+ })