← Back to team overview

launchpad-reviewers team mailing list archive

[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."""