launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06685
[Merge] lp:~rvb/maas/maas-arch-ui into lp:maas
Raphaël Badin has proposed merging lp:~rvb/maas/maas-arch-ui into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~rvb/maas/maas-arch-ui/+merge/97366
This branch adds the architecture field in the "Add node" panel. It also changes NodeForm so that now the architecture field is a required field (hence the updates to the API calls). Note that I've also changed snippets.html to use Django's form system to pre-render the field that will be used on the JS side (I've got a follow-up branch that generalizes this approach to the rest of the snippets in that file); also, the test for that is pretty minimal (it only makes sure that the snippet looks right) because that form is fully tested in test_forms.py.
--
https://code.launchpad.net/~rvb/maas/maas-arch-ui/+merge/97366
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/maas-arch-ui into lp:maas.
=== modified file 'src/maasserver/context_processors.py'
--- src/maasserver/context_processors.py 2012-03-07 16:26:04 +0000
+++ src/maasserver/context_processors.py 2012-03-14 10:49:17 +0000
@@ -14,6 +14,7 @@
]
from django.conf import settings
+from maasserver.forms import NodeForm
from maasserver.models import Config
@@ -28,6 +29,7 @@
def global_options(context):
return {
+ 'node_form': NodeForm(),
'global_options': {
'site_name': Config.objects.get_config('maas_name'),
}
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2012-03-12 07:25:31 +0000
+++ src/maasserver/forms.py 2012-03-14 10:49:17 +0000
@@ -56,13 +56,9 @@
after_commissioning_action = forms.TypedChoiceField(
choices=NODE_AFTER_COMMISSIONING_ACTION_CHOICES, required=False,
empty_value=NODE_AFTER_COMMISSIONING_ACTION.DEFAULT)
- # The architecture field is not visible yet, but requires a choice.
- # Faking it by setting 'i386' as the representation for "none
- # selected." Once this field becomes meaningful, it may simply have
- # to become mandatory.
- architecture = forms.TypedChoiceField(
- choices=ARCHITECTURE_CHOICES, required=False,
- empty_value=ARCHITECTURE.i386,
+ architecture = forms.ChoiceField(
+ choices=ARCHITECTURE_CHOICES, required=True,
+ initial=ARCHITECTURE.i386,
error_messages={'invalid_choice': INVALID_ARCHITECTURE_MESSAGE})
class Meta:
=== modified file 'src/maasserver/static/js/node_add.js'
--- src/maasserver/static/js/node_add.js 2012-03-07 09:09:23 +0000
+++ src/maasserver/static/js/node_add.js 2012-03-14 10:49:17 +0000
@@ -121,6 +121,7 @@
.append(operation)
.append(Y.Node.create(this.add_macaddress))
.append(macaddress_add_link)
+ .append(Y.Node.create(this.add_architecture))
.append(Y.Node.create(this.add_node));
return addnodeform;
},
@@ -170,6 +171,7 @@
initializer: function(cfg) {
// Load form snippets.
this.add_macaddress = Y.one('#add-macaddress').getContent();
+ this.add_architecture = Y.one('#add-architecture').getContent();
this.add_node = Y.one('#add-node').getContent();
// Create panel's content.
this.set('bodyContent', this.createForm());
=== modified file 'src/maasserver/static/js/tests/test_node_add.html'
--- src/maasserver/static/js/tests/test_node_add.html 2012-03-07 16:26:04 +0000
+++ src/maasserver/static/js/tests/test_node_add.html 2012-03-14 10:49:17 +0000
@@ -28,6 +28,17 @@
<script type="text/x-template" id="add-macaddress">
<p></p>
</script>
+ <script type="text/x-template" id="add-architecture">
+ <p>
+ <label for="id_architecture">Architecture
+ </label>
+ <select id="id_architecture" name="architecture">
+ <option value="amd64">amd64</option>
+ <option value="i386">i386</option>
+ </select>
+ <input type="text" maxlength="30" name="hostname" id="id_hostname" />
+ </p>
+ </script>
<script type="text/x-template" id="add-node">
<p>
<label for="id_hostname">Hostname
=== modified file 'src/maasserver/static/js/tests/test_node_add.js'
--- src/maasserver/static/js/tests/test_node_add.js 2012-02-24 17:37:01 +0000
+++ src/maasserver/static/js/tests/test_node_add.js 2012-03-14 10:49:17 +0000
@@ -53,6 +53,35 @@
suite.add(new Y.maas.testing.TestCase({
name: 'test-add-node-widget-add-node',
+ testFormContainsArchitectureChoice: function() {
+ // The generated form contains an 'architecture' field.
+ module.showAddNodeWidget();
+ var panel = module._add_node_singleton;
+ var arch = panel.get('srcNode').one('form').one('#id_architecture');
+ Y.Assert.isNotNull(arch);
+ var arch_options = arch.all('option');
+ Y.Assert.areEqual(2, arch_options.size());
+ },
+
+ testAddNodeAPICallSubmitsForm: function() {
+ // The call to the API triggered by clicking on 'Add a node'
+ // submits (via an API call) the panel's form.
+ module.showAddNodeWidget();
+ var panel = module._add_node_singleton;
+ var mockXhr = new Y.Base();
+ var fired = false;
+ var form = panel.get('srcNode').one('form');
+ mockXhr.send = function(uri, cfg) {
+ fired = true;
+ Y.Assert.areEqual(form, cfg.form);
+ };
+ this.mockIO(mockXhr, module);
+ panel.get('srcNode').one('#id_hostname').set('value', 'host');
+ var button = panel.get('srcNode').one('.yui3-button');
+ button.simulate('click');
+ Y.Assert.isTrue(fired);
+ },
+
testAddNodeAPICall: function() {
var mockXhr = Y.Mock();
Y.Mock.expect(mockXhr, {
=== modified file 'src/maasserver/templates/maasserver/snippets.html'
--- src/maasserver/templates/maasserver/snippets.html 2012-02-20 12:08:13 +0000
+++ src/maasserver/templates/maasserver/snippets.html 2012-03-14 10:49:17 +0000
@@ -6,6 +6,12 @@
<div class="field-help">e.g AA:BB:CC:DD:EE:FF</div>
</p>
</script>
+<script type="text/x-template" id="add-architecture">
+ <p>
+ <label for="id_architecture">Architecture</label>
+ {{ node_form.architecture }}
+ </p>
+</script>
<script type="text/x-template" id="add-node">
<p>
<label for="id_hostname">Hostname
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2012-03-13 05:34:38 +0000
+++ src/maasserver/tests/test_api.py 2012-03-14 10:49:17 +0000
@@ -19,7 +19,7 @@
from django.conf import settings
from maasserver.models import (
- ARCHITECTURE,
+ ARCHITECTURE_CHOICES,
Config,
MACAddress,
Node,
@@ -59,12 +59,13 @@
def test_POST_new_creates_node(self):
# The API allows a Node to be created.
+ architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
response = self.client.post(
self.get_uri('nodes/'),
{
'op': 'new',
'hostname': 'diane',
- 'architecture': 'amd64',
+ 'architecture': architecture,
'after_commissioning_action': '2',
'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
})
@@ -76,16 +77,18 @@
self.assertNotEqual(0, len(parsed_result.get('system_id')))
[diane] = Node.objects.filter(hostname='diane')
self.assertEqual(2, diane.after_commissioning_action)
- self.assertEqual(ARCHITECTURE.amd64, diane.architecture)
+ self.assertEqual(architecture, diane.architecture)
def test_POST_new_associates_mac_addresses(self):
# The API allows a Node to be created and associated with MAC
# Addresses.
+ architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
self.client.post(
self.get_uri('nodes/'),
{
'op': 'new',
'hostname': 'diane',
+ 'architecture': architecture,
'after_commissioning_action': '2',
'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
})
@@ -95,11 +98,13 @@
[mac.mac_address for mac in diane.macaddress_set.all()])
def test_POST_returns_limited_fields(self):
+ architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
response = self.client.post(
self.get_uri('nodes/'),
{
'op': 'new',
'hostname': 'diane',
+ 'architecture': architecture,
'after_commissioning_action': '2',
'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
})
@@ -490,11 +495,13 @@
def test_POST_new_creates_node(self):
# The API allows a Node to be created, even as a logged-in user.
+ architecture = factory.getRandomChoice(ARCHITECTURE_CHOICES)
response = self.client.post(
self.get_uri('nodes/'),
{
'op': 'new',
'hostname': 'diane',
+ 'architecture': architecture,
'after_commissioning_action': '2',
'mac_addresses': ['aa:bb:cc:dd:ee:ff', '22:bb:cc:dd:ee:ff'],
})
=== modified file 'src/maasserver/tests/test_views.py'
--- src/maasserver/tests/test_views.py 2012-03-13 05:34:38 +0000
+++ src/maasserver/tests/test_views.py 2012-03-14 10:49:17 +0000
@@ -66,6 +66,29 @@
doc.cssselect('h2')])
+class TestSnippets(LoggedInTestCase):
+
+ def assertSnippetExistsAndContains(self, content, snippet_selector,
+ contains_selector):
+ """Assert that the provided html 'content' contains a snippet as
+ selected by 'snippet_selector' which in turn contains an element
+ selected by 'contains_selector'.
+ """
+ doc = fromstring(content)
+ arch_snippets = doc.cssselect(snippet_selector)
+ # The snippet exists.
+ self.assertEqual(1, len(arch_snippets))
+ # It contains the required element.
+ selects = fromstring(
+ arch_snippets[0].text).cssselect(contains_selector)
+ self.assertEqual(1, len(selects))
+
+ def test_architecture_snippet(self):
+ response = self.client.get('/')
+ self.assertSnippetExistsAndContains(
+ response.content, '#add-architecture', 'select#id_architecture')
+
+
class TestComboLoaderView(TestCase):
"""Test combo loader view."""