Merge ~newell-jensen/maas:fix-1686678 into maas:master
- Git
- lp:~newell-jensen/maas
- fix-1686678
- Merge into master
Proposed by
Newell Jensen
Status: | Merged |
---|---|
Approved by: | Newell Jensen |
Approved revision: | 194c62cf7ff3fbce84540d9fafa6ea04a2d75fcc |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~newell-jensen/maas:fix-1686678 |
Merge into: | maas:master |
Diff against target: |
408 lines (+130/-19) 8 files modified
src/maasserver/forms/__init__.py (+6/-1) src/maasserver/forms/tests/test_controller.py (+13/-0) src/maasserver/static/js/angular/controllers/node_details.js (+25/-2) src/maasserver/static/js/angular/controllers/tests/test_node_details.js (+64/-4) src/maasserver/static/partials/node-details.html (+6/-4) src/maasserver/websockets/handlers/controller.py (+10/-2) src/maasserver/websockets/handlers/tests/test_controller.py (+4/-4) src/maasserver/websockets/protocol.py (+2/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+326634@code.launchpad.net |
Commit message
Allows the domain of controllers to be changed in the UI.
Description of the change
To post a comment you must log in.
~newell-jensen/maas:fix-1686678
updated
- 194c62c... by Newell Jensen
-
Fix html alignment issues.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/forms/__init__.py b/src/maasserver/forms/__init__.py |
2 | index 2dd682c..5e752c1 100644 |
3 | --- a/src/maasserver/forms/__init__.py |
4 | +++ b/src/maasserver/forms/__init__.py |
5 | @@ -875,19 +875,24 @@ class ControllerForm(MAASModelForm, WithPowerTypeMixin): |
6 | class Meta: |
7 | model = Controller |
8 | |
9 | - fields = ['zone'] |
10 | + fields = ['zone', 'domain'] |
11 | |
12 | zone = forms.ModelChoiceField( |
13 | label="Physical zone", required=False, |
14 | initial=Zone.objects.get_default_zone, |
15 | queryset=Zone.objects.all(), to_field_name='name') |
16 | |
17 | + domain = forms.ModelChoiceField( |
18 | + required=False, initial=Domain.objects.get_default_domain, |
19 | + queryset=Domain.objects.all(), to_field_name='name') |
20 | + |
21 | def __init__(self, data=None, instance=None, request=None, **kwargs): |
22 | super(ControllerForm, self).__init__( |
23 | data=data, instance=instance, **kwargs) |
24 | WithPowerTypeMixin.set_up_power_fields(self, data, instance) |
25 | if instance is not None: |
26 | self.initial['zone'] = instance.zone.name |
27 | + self.initial['domain'] = instance.domain.name |
28 | |
29 | def clean(self): |
30 | cleaned_data = super(ControllerForm, self).clean() |
31 | diff --git a/src/maasserver/forms/tests/test_controller.py b/src/maasserver/forms/tests/test_controller.py |
32 | index b7ca4b0..6481447 100644 |
33 | --- a/src/maasserver/forms/tests/test_controller.py |
34 | +++ b/src/maasserver/forms/tests/test_controller.py |
35 | @@ -18,6 +18,7 @@ class TestControllerForm(MAASServerTestCase): |
36 | self.assertItemsEqual( |
37 | [ |
38 | 'zone', |
39 | + 'domain', |
40 | 'power_type', |
41 | 'power_parameters', |
42 | ], |
43 | @@ -70,3 +71,15 @@ class TestControllerForm(MAASServerTestCase): |
44 | instance=rack) |
45 | rack = form.save() |
46 | self.assertEqual(zone.name, rack.zone.name) |
47 | + |
48 | + def test__sets_domain(self): |
49 | + rack = factory.make_RackController() |
50 | + domain = factory.make_Domain() |
51 | + form = ControllerForm( |
52 | + data={ |
53 | + 'domain': domain.name, |
54 | + 'power_parameters_skip_check': 'true', |
55 | + }, |
56 | + instance=rack) |
57 | + rack = form.save() |
58 | + self.assertEqual(domain.name, rack.domain.name) |
59 | diff --git a/src/maasserver/static/js/angular/controllers/node_details.js b/src/maasserver/static/js/angular/controllers/node_details.js |
60 | index e712a14..85869f4 100644 |
61 | --- a/src/maasserver/static/js/angular/controllers/node_details.js |
62 | +++ b/src/maasserver/static/js/angular/controllers/node_details.js |
63 | @@ -60,6 +60,7 @@ angular.module('MAAS').controller('NodeDetailsController', [ |
64 | // Node header section. |
65 | $scope.header = { |
66 | editing: false, |
67 | + editing_domain: false, |
68 | hostname: { |
69 | value: "" |
70 | }, |
71 | @@ -127,7 +128,7 @@ angular.module('MAAS').controller('NodeDetailsController', [ |
72 | function updateHeader() { |
73 | // Don't update the value if in editing mode. As this would |
74 | // overwrite the users changes. |
75 | - if($scope.header.editing) { |
76 | + if($scope.header.editing || $scope.header.editing_domain) { |
77 | return; |
78 | } |
79 | $scope.header.hostname.value = $scope.node.fqdn; |
80 | @@ -757,6 +758,25 @@ angular.module('MAAS').controller('NodeDetailsController', [ |
81 | !$scope.isController); |
82 | }; |
83 | |
84 | + // Called to edit the domain name. |
85 | + $scope.editHeaderDomain = function() { |
86 | + if($scope.canEdit()) { |
87 | + return; |
88 | + } |
89 | + |
90 | + // Do nothing if already editing because we don't want to reset |
91 | + // the current value. |
92 | + if($scope.header.editing_domain) { |
93 | + return; |
94 | + } |
95 | + $scope.header.editing = false; |
96 | + $scope.header.editing_domain = true; |
97 | + |
98 | + // Set the value to the hostname, as hostname and domain are edited |
99 | + // using different fields. |
100 | + $scope.header.hostname.value = $scope.node.hostname; |
101 | + }; |
102 | + |
103 | // Called to edit the node name. |
104 | $scope.editHeader = function() { |
105 | if(!$scope.canEdit()) { |
106 | @@ -769,6 +789,7 @@ angular.module('MAAS').controller('NodeDetailsController', [ |
107 | return; |
108 | } |
109 | $scope.header.editing = true; |
110 | + $scope.header.editing_domain = false; |
111 | |
112 | // Set the value to the hostname, as hostname and domain are edited |
113 | // using different fields. |
114 | @@ -778,7 +799,7 @@ angular.module('MAAS').controller('NodeDetailsController', [ |
115 | // Return true when the hostname or domain in the header is invalid. |
116 | $scope.editHeaderInvalid = function() { |
117 | // Not invalid unless editing. |
118 | - if(!$scope.header.editing) { |
119 | + if(!$scope.header.editing && !$scope.header.editing_domain) { |
120 | return false; |
121 | } |
122 | |
123 | @@ -793,6 +814,7 @@ angular.module('MAAS').controller('NodeDetailsController', [ |
124 | // Called to cancel editing of the node hostname and domain. |
125 | $scope.cancelEditHeader = function() { |
126 | $scope.header.editing = false; |
127 | + $scope.header.editing_domain = false; |
128 | updateHeader(); |
129 | }; |
130 | |
131 | @@ -803,6 +825,7 @@ angular.module('MAAS').controller('NodeDetailsController', [ |
132 | return; |
133 | } |
134 | $scope.header.editing = false; |
135 | + $scope.header.editing_domain = false; |
136 | |
137 | // Copy the node and make the changes. |
138 | var node = angular.copy($scope.node); |
139 | diff --git a/src/maasserver/static/js/angular/controllers/tests/test_node_details.js b/src/maasserver/static/js/angular/controllers/tests/test_node_details.js |
140 | index 96207dd..d782d63 100644 |
141 | --- a/src/maasserver/static/js/angular/controllers/tests/test_node_details.js |
142 | +++ b/src/maasserver/static/js/angular/controllers/tests/test_node_details.js |
143 | @@ -1486,23 +1486,74 @@ describe("NodeDetailsController", function() { |
144 | }); |
145 | }); |
146 | |
147 | + describe("editHeaderDomain", function() { |
148 | + |
149 | + it("doesnt set editing false and editing_domain true if cannot edit", |
150 | + function() { |
151 | + var controller = makeController(); |
152 | + spyOn($scope, "canEdit").and.returnValue(true); |
153 | + $scope.header.editing = true; |
154 | + $scope.header.editing_domain = false; |
155 | + $scope.editHeaderDomain(); |
156 | + expect($scope.header.editing).toBe(true); |
157 | + expect($scope.header.editing_domain).toBe(false); |
158 | + }); |
159 | + |
160 | + it("sets editing to false and editing_domain to true if able", |
161 | + function() { |
162 | + var controller = makeController(); |
163 | + $scope.node = node; |
164 | + spyOn($scope, "canEdit").and.returnValue(false); |
165 | + $scope.header.editing = true; |
166 | + $scope.header.editing_domain = false; |
167 | + $scope.editHeaderDomain(); |
168 | + expect($scope.header.editing).toBe(false); |
169 | + expect($scope.header.editing_domain).toBe(true); |
170 | + }); |
171 | + |
172 | + it("sets header.hostname.value to node hostname", function() { |
173 | + var controller = makeController(); |
174 | + $scope.node = node; |
175 | + spyOn($scope, "canEdit").and.returnValue(false); |
176 | + $scope.editHeaderDomain(); |
177 | + expect($scope.header.hostname.value).toBe(node.hostname); |
178 | + }); |
179 | + |
180 | + it("doesnt reset header.hostname.value on multiple calls", function() { |
181 | + var controller = makeController(); |
182 | + $scope.node = node; |
183 | + spyOn($scope, "canEdit").and.returnValue(false); |
184 | + $scope.editHeaderDomain(); |
185 | + var updatedName = makeName("name"); |
186 | + $scope.header.hostname.value = updatedName; |
187 | + $scope.editHeaderDomain(); |
188 | + expect($scope.header.hostname.value).toBe(updatedName); |
189 | + }); |
190 | + }); |
191 | + |
192 | describe("editHeader", function() { |
193 | |
194 | - it("doesnt set editing to true if cannot edit", function() { |
195 | + it("doesnt set editing true and editing_domain false if cannot edit", |
196 | + function() { |
197 | var controller = makeController(); |
198 | spyOn($scope, "canEdit").and.returnValue(false); |
199 | $scope.header.editing = false; |
200 | + $scope.header.editing_domain = true; |
201 | $scope.editHeader(); |
202 | expect($scope.header.editing).toBe(false); |
203 | + expect($scope.header.editing_domain).toBe(true); |
204 | }); |
205 | |
206 | - it("sets editing to true if able", function() { |
207 | + it("sets editing to true and editing_domain to false if able", |
208 | + function() { |
209 | var controller = makeController(); |
210 | $scope.node = node; |
211 | spyOn($scope, "canEdit").and.returnValue(true); |
212 | $scope.header.editing = false; |
213 | + $scope.header.editing_domain = true; |
214 | $scope.editHeader(); |
215 | expect($scope.header.editing).toBe(true); |
216 | + expect($scope.header.editing_domain).toBe(false); |
217 | }); |
218 | |
219 | it("sets header.hostname.value to node hostname", function() { |
220 | @@ -1527,9 +1578,10 @@ describe("NodeDetailsController", function() { |
221 | |
222 | describe("editHeaderInvalid", function() { |
223 | |
224 | - it("returns false if not editing", function() { |
225 | + it("returns false if not editing and not editing_domain", function() { |
226 | var controller = makeController(); |
227 | $scope.header.editing = false; |
228 | + $scope.header.editing_domain = false; |
229 | $scope.header.hostname.value = "abc_invalid.local"; |
230 | expect($scope.editHeaderInvalid()).toBe(false); |
231 | }); |
232 | @@ -1537,6 +1589,7 @@ describe("NodeDetailsController", function() { |
233 | it("returns true for bad values", function() { |
234 | var controller = makeController(); |
235 | $scope.header.editing = true; |
236 | + $scope.header.editing_domain = false; |
237 | var values = [ |
238 | { |
239 | input: "aB0-z", |
240 | @@ -1564,12 +1617,15 @@ describe("NodeDetailsController", function() { |
241 | |
242 | describe("cancelEditHeader", function() { |
243 | |
244 | - it("sets editing to false for nameHeader section", function() { |
245 | + it("sets editing and editing_domain to false for nameHeader section", |
246 | + function() { |
247 | var controller = makeController(); |
248 | $scope.node = node; |
249 | $scope.header.editing = true; |
250 | + $scope.header.editing_domain = true; |
251 | $scope.cancelEditHeader(); |
252 | expect($scope.header.editing).toBe(false); |
253 | + expect($scope.header.editing_domain).toBe(false); |
254 | }); |
255 | |
256 | it("sets header.hostname.value back to fqdn", function() { |
257 | @@ -1590,8 +1646,10 @@ describe("NodeDetailsController", function() { |
258 | spyOn($scope, "editHeaderInvalid").and.returnValue(true); |
259 | var sentinel = {}; |
260 | $scope.header.editing = sentinel; |
261 | + $scope.header.editing_domain = sentinel; |
262 | $scope.saveEditHeader(); |
263 | expect($scope.header.editing).toBe(sentinel); |
264 | + expect($scope.header.editing_domain).toBe(sentinel); |
265 | }); |
266 | |
267 | it("sets editing to false", function() { |
268 | @@ -1602,10 +1660,12 @@ describe("NodeDetailsController", function() { |
269 | |
270 | $scope.node = node; |
271 | $scope.header.editing = true; |
272 | + $scope.header.editing_domain = true; |
273 | $scope.header.hostname.value = makeName("name"); |
274 | $scope.saveEditHeader(); |
275 | |
276 | expect($scope.header.editing).toBe(false); |
277 | + expect($scope.header.editing_domain).toBe(false); |
278 | }); |
279 | |
280 | it("calls updateItem with copy of node", function() { |
281 | diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html |
282 | index 00d0b11..bff8c1a 100755 |
283 | --- a/src/maasserver/static/partials/node-details.html |
284 | +++ b/src/maasserver/static/partials/node-details.html |
285 | @@ -11,21 +11,23 @@ |
286 | <h1 class="page-header__title"> |
287 | <span contenteditable="true" |
288 | data-ng-class="{ editable: canEdit(), 'has-error': editHeaderInvalid(), editmode: header.editing }" |
289 | + data-ng-show="canEdit()" |
290 | data-ng-model="header.hostname.value" |
291 | data-ng-disabled="!canEdit()" |
292 | data-maas-editing="editHeader()"></span> |
293 | - <span class="page-header__title-dot ng-hide" data-ng-show="header.editing">.</span> |
294 | + <span ng-click="editHeaderDomain()" data-ng-show="!canEdit()">{$ header.hostname.value $}</span> |
295 | + <span class="page-header__title-dot ng-hide" data-ng-show="header.editing || header.editing_domain">.</span> |
296 | <select class="page-header__title-domain ng-hide" |
297 | - data-ng-show="header.editing" |
298 | + data-ng-show="header.editing || header.editing_domain" |
299 | data-ng-model="header.domain.selected" |
300 | data-ng-options="domain as domain.name for domain in header.domain.options"> |
301 | </select> |
302 | </h1> |
303 | <button class="button--base button--inline u-margin--bottom-small ng-hide" |
304 | - data-ng-show="header.editing" |
305 | + data-ng-show="header.editing || header.editing_domain" |
306 | data-ng-click="cancelEditHeader()">Cancel</button> |
307 | <button class="button--positive button--inline u-margin--bottom-small ng-hide" |
308 | - data-ng-show="header.editing" |
309 | + data-ng-show="header.editing || header.editing_domain" |
310 | data-ng-disabled="editHeaderInvalid()" |
311 | data-ng-click="saveEditHeader()">Save</button> |
312 | |
313 | diff --git a/src/maasserver/websockets/handlers/controller.py b/src/maasserver/websockets/handlers/controller.py |
314 | index 0aa8ff3..ca85333 100644 |
315 | --- a/src/maasserver/websockets/handlers/controller.py |
316 | +++ b/src/maasserver/websockets/handlers/controller.py |
317 | @@ -8,11 +8,12 @@ __all__ = [ |
318 | ] |
319 | |
320 | from maasserver.enum import NODE_PERMISSION |
321 | -from maasserver.forms import AdminMachineWithMACAddressesForm |
322 | +from maasserver.forms import ControllerForm |
323 | from maasserver.models.node import ( |
324 | Controller, |
325 | RackController, |
326 | ) |
327 | +from maasserver.websockets.base import HandlerError |
328 | from maasserver.websockets.handlers.machine import MachineHandler |
329 | from maasserver.websockets.handlers.node import node_prefetch |
330 | |
331 | @@ -40,7 +41,7 @@ class ControllerHandler(MachineHandler): |
332 | 'link_subnet', |
333 | 'unlink_subnet', |
334 | ] |
335 | - form = AdminMachineWithMACAddressesForm |
336 | + form = ControllerForm |
337 | exclude = [ |
338 | "status_expires", |
339 | "parent", |
340 | @@ -74,6 +75,13 @@ class ControllerHandler(MachineHandler): |
341 | "controller", |
342 | ] |
343 | |
344 | + def get_form_class(self, action): |
345 | + """Return the form class used for `action`.""" |
346 | + if action in ("create", "update"): |
347 | + return ControllerForm |
348 | + else: |
349 | + raise HandlerError("Unknown action: %s" % action) |
350 | + |
351 | def get_queryset(self): |
352 | """Return `QuerySet` for controllers only viewable by `user`.""" |
353 | return Controller.controllers.get_nodes( |
354 | diff --git a/src/maasserver/websockets/handlers/tests/test_controller.py b/src/maasserver/websockets/handlers/tests/test_controller.py |
355 | index 1a23df1..111ed57 100644 |
356 | --- a/src/maasserver/websockets/handlers/tests/test_controller.py |
357 | +++ b/src/maasserver/websockets/handlers/tests/test_controller.py |
358 | @@ -1,4 +1,4 @@ |
359 | -# Copyright 2016 Canonical Ltd. This software is licensed under the |
360 | +# Copyright 2016-2017 Canonical Ltd. This software is licensed under the |
361 | # GNU Affero General Public License version 3 (see the file LICENSE). |
362 | |
363 | """Tests for `maasserver.websockets.handlers.controller`""" |
364 | @@ -6,7 +6,7 @@ |
365 | __all__ = [] |
366 | |
367 | from maasserver.enum import NODE_TYPE |
368 | -from maasserver.forms import AdminMachineWithMACAddressesForm |
369 | +from maasserver.forms import ControllerForm |
370 | from maasserver.testing.factory import factory |
371 | from maasserver.testing.testcase import MAASServerTestCase |
372 | from maasserver.websockets.base import dehydrate_datetime |
373 | @@ -68,14 +68,14 @@ class TestControllerHandler(MAASServerTestCase): |
374 | user = factory.make_admin() |
375 | handler = ControllerHandler(user, {}) |
376 | self.assertEqual( |
377 | - AdminMachineWithMACAddressesForm, |
378 | + ControllerForm, |
379 | handler.get_form_class("create")) |
380 | |
381 | def test_get_form_class_for_update(self): |
382 | user = factory.make_admin() |
383 | handler = ControllerHandler(user, {}) |
384 | self.assertEqual( |
385 | - AdminMachineWithMACAddressesForm, |
386 | + ControllerForm, |
387 | handler.get_form_class("update")) |
388 | |
389 | def test_check_images(self): |
390 | diff --git a/src/maasserver/websockets/protocol.py b/src/maasserver/websockets/protocol.py |
391 | index af5c195..f143fd7 100644 |
392 | --- a/src/maasserver/websockets/protocol.py |
393 | +++ b/src/maasserver/websockets/protocol.py |
394 | @@ -1,4 +1,4 @@ |
395 | -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the |
396 | +# Copyright 2015-2017 Canonical Ltd. This software is licensed under the |
397 | # GNU Affero General Public License version 3 (see the file LICENSE). |
398 | |
399 | """The MAAS WebSockets protocol.""" |
400 | @@ -218,7 +218,7 @@ class WebSocketProtocol(Protocol): |
401 | def dataReceived(self, data): |
402 | """Received message from client and queue up the message.""" |
403 | try: |
404 | - message = json.loads(data.decode("ascii")) |
405 | + message = json.loads(data.decode('utf-8')) |
406 | except ValueError: |
407 | # Only accept JSON data over the protocol. Close the connect |
408 | # with invalid data. |
Looks good.
You have a few minor alignment problems with the HTML that affect read ability, but otherwise it looks good.