← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/maas/ui-power-parameters into lp:maas

 

Raphaël Badin has proposed merging lp:~rvb/maas/ui-power-parameters into lp:maas with lp:~rvb/maas/api-power-parameters as a prerequisite.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rvb/maas/ui-power-parameters/+merge/109621

This branch add the fields power_type and power_parameters to the "add node" UI (if the user is an admin).

= Notes =

- I've changed DictCharField to return the empty string instead of '<fieldset></fieldset>' when the number of sub-fields is zero. I've done that because, for a reason I cannot fathom, http://paste.ubuntu.com/1035380/ does not validate.  I suppose <fieldset> must contain a tag or something like that.  Anyway, returning the empty string in that case is better anyway.

- I've added power_parameters.js and enums.js in js-conf.html (i.e. globally).

- no need for the "architecture" snippet (in snippet.html) to be a snippet on its own, I've included it in the general snippet "add-node".
-- 
https://code.launchpad.net/~rvb/maas/ui-power-parameters/+merge/109621
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/ui-power-parameters into lp:maas.
=== modified file 'src/maasserver/config_forms.py'
--- src/maasserver/config_forms.py	2012-06-07 15:57:27 +0000
+++ src/maasserver/config_forms.py	2012-06-11 13:08:19 +0000
@@ -279,6 +279,9 @@
             widgets = self.widgets
         if not isinstance(value, list):
             value = self.decompress(value)
+        if len(widgets) == 0:
+            return mark_safe(self.format_output(''))
+
         output = ['<fieldset>']
         final_attrs = self.build_attrs(attrs)
         id_ = final_attrs.get('id', None)

=== modified file 'src/maasserver/context_processors.py'
--- src/maasserver/context_processors.py	2012-06-06 16:01:55 +0000
+++ src/maasserver/context_processors.py	2012-06-11 13:08:19 +0000
@@ -16,7 +16,7 @@
 
 from django.conf import settings
 from maasserver.components import get_persistent_errors
-from maasserver.forms import NodeForm
+from maasserver.forms import get_node_edit_form
 from maasserver.models import Config
 from maasserver.power_parameters import POWER_TYPE_PARAMETERS
 from provisioningserver.enum import POWER_TYPE
@@ -34,7 +34,7 @@
 def global_options(context):
     return {
         'persistent_errors': get_persistent_errors(),
-        'node_form': NodeForm(),
+        'node_form': get_node_edit_form(context.user)(),
         'POWER_TYPE_PARAMETERS_FIELDS':
             [(power_type, field.widget.render('power_parameters', []))
                 for power_type, field in POWER_TYPE_PARAMETERS.items()

=== modified file 'src/maasserver/static/js/node_add.js'
--- src/maasserver/static/js/node_add.js	2012-06-11 07:34:55 +0000
+++ src/maasserver/static/js/node_add.js	2012-06-11 13:08:19 +0000
@@ -25,6 +25,10 @@
 
 AddNodeWidget.NAME = 'node-add-widget';
 
+module.POWER_TYPE_ENUM = Y.maas.enums.POWER_TYPE;
+
+module.POWER_PARAM_TEMPLATE_PREFIX = '#power-param-form-';
+
 AddNodeWidget.ATTRS = {
 
    /**
@@ -141,7 +145,6 @@
             .append(global_error)
             .append(operation)
             .append(Y.Node.create(this.add_node))
-            .append(Y.Node.create(this.add_architecture))
             .append(Y.Node.create(this.add_macaddress))
             .append(macaddress_add_link)
             .append(buttons);
@@ -207,16 +210,36 @@
     renderUI: function() {
         // 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.
         var heading = Y.Node.create('<h2 />')
             .set('text', "Add node");
         this.get('srcNode').append(heading).append(this.createForm());
+        this.setupPowerParameterField();
         this.initializeNodes();
     },
 
     /**
+     * If the 'power_type' field is present, setup the linked
+     * 'power_parameter' field.
+     *
+     * @method setupPowerParameterField
+     */
+    setupPowerParameterField: function() {
+        if (Y.Lang.isValue(Y.one('#id_power_type'))) {
+            // If the 'power_type' field is present, setup the linked
+            // field 'power_parameters'.
+            var widget = new Y.maas.power_parameters.LinkedContentWidget({
+                srcNode: '.power_parameters',
+                driverEnum: module.POWER_TYPE_ENUM,
+                templatePrefix: module.POWER_PARAM_TEMPLATE_PREFIX
+                });
+            widget.bindTo(Y.one('#id_power_type'), 'change');
+            widget.render();
+        }
+    },
+
+    /**
      * Show the widget
      *
      * @method showWidget
@@ -379,5 +402,5 @@
 };
 
 }, '0.1', {'requires': ['io', 'node', 'widget', 'event', 'event-custom',
-                        'maas.morph']}
+                        'maas.morph', 'maas.enums', 'maas.power_parameters']}
 );

=== modified file 'src/maasserver/static/js/tests/test_node_add.html'
--- src/maasserver/static/js/tests/test_node_add.html	2012-03-29 04:21:34 +0000
+++ src/maasserver/static/js/tests/test_node_add.html	2012-06-11 13:08:19 +0000
@@ -9,6 +9,8 @@
     <script type="text/javascript" src="../testing/testrunner.js"></script>
     <script type="text/javascript" src="../testing/testing.js"></script>
     <script type="text/javascript" src="../morph.js"></script>
+    <script type="text/javascript" src="../power_parameters.js"></script>
+    <script type="text/javascript" src="../enums.js"></script>
     <!-- The module under test -->
     <script type="text/javascript" src="../node_add.js"></script>
     <!-- The test suite -->
@@ -22,30 +24,47 @@
         nodes_handler: '/api/nodes/'
       }
     };
+    var ENUM = {
+        "DEFAULT": "",
+        "value1": "value1"
+    };
     // -->
     </script>
   </head>
   <body>
+  <script type="text/x-template" id="power-param-form-">
+  </script>
+  <script type="text/x-template" id="power-param-form-value1">
+  </script>
   <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
-      <span class="form-optional">(optional)</span>
-    </label>
-    <input type="text" maxlength="30" name="hostname" id="id_hostname" />
+      <label for="id_hostname">Hostname
+        <span class="form-optional">(optional)</span>
+      </label>
+      <input type="text" maxlength="30" name="hostname" id="id_hostname" />
+    </p>
+    <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-admin">
+    <p>
+      <label for="id_power_type">Power type</label>
+      <select name="power_type" id="id_power_type">
+        <option value="" selected="selected">---------</option>
+        <option value="value1">value1</option>
+      </select>
+    </p>
+    <p class="power_parameters">
+      <label for="id_power_parameters">Power parameters</label>
     </p>
   </script>
   <div id="target_node"></div>

=== modified file 'src/maasserver/static/js/tests/test_node_add.js'
--- src/maasserver/static/js/tests/test_node_add.js	2012-04-02 05:21:40 +0000
+++ src/maasserver/static/js/tests/test_node_add.js	2012-06-11 13:08:19 +0000
@@ -211,6 +211,70 @@
 
 }));
 
+suite.add(new Y.maas.testing.TestCase({
+    name: 'test-add-node-widget-add-node-admin',
+
+    setUp: function() {
+        this.setUpAdminTemplate();
+        this.setUpPowerTypeEnum();
+    },
+
+    setUpPowerTypeEnum: function() {
+        // Override module.POWER_TYPE_ENUM for the sake of testing.
+        this.POWER_TYPE_ENUM = module.POWER_TYPE_ENUM;
+        module.POWER_TYPE_ENUM = ENUM;
+    },
+
+    setUpAdminTemplate: function() {
+        // Append the snippet that will be seen by an admin user to the
+        // general template.
+        this.add_node_template = Y.one('#add-node').getContent();
+        this.add_node_admin_snippet = Y.one('#add-node-admin').getContent();
+        Y.one('#add-node').set(
+            'innerHTML',
+            this.add_node_template + this.add_node_admin_snippet);
+    },
+
+    tearDown: function() {
+        this.tearDownAdminTemplate();
+        this.tearDownPowerTypeEnum();
+    },
+
+    tearDownAdminTemplate: function() {
+        Y.one('#add-node').set('innerHTML', this.add_node_template);
+    },
+
+    tearDownPowerTypeEnum: function() {
+        module.POWER_TYPE_ENUM = this.POWER_TYPE_ENUM;
+    },
+
+    testFormContainsPowerType: function() {
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
+        Y.Assert.isNotNull(find_widget().one('#id_power_type'));
+    },
+
+    testPowerParameterHidden: function() {
+        // The power_parameter field starts hidden.
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
+        Y.Assert.isTrue(
+            find_widget().one('.power_parameters').hasClass('hidden'));
+    },
+
+    testPowerParameterDynamicallyLinked: function() {
+        // When the option of the 'select' tag changes to a non empty
+        // string, the 'power_parameters' field is shown.
+        module.showAddNodeWidget({targetNode: '#target_node', animate: false});
+        var select = Y.one('#id_power_type');
+        select.set('value', 'value1');
+        select.simulate('change');
+        Y.Assert.isFalse(
+            find_widget().one('.power_parameters').hasClass('hidden'));
+    }
+
+
+}));
+
+
 namespace.suite = suite;
 
 }, '0.1', {'requires': [

=== modified file 'src/maasserver/templates/maasserver/js-conf.html'
--- src/maasserver/templates/maasserver/js-conf.html	2012-03-16 09:02:13 +0000
+++ src/maasserver/templates/maasserver/js-conf.html	2012-06-11 13:08:19 +0000
@@ -30,3 +30,5 @@
 <script type="text/javascript" src="{{ STATIC_URL }}js/utils.js"></script>
 <script type="text/javascript" src="{{ STATIC_URL }}js/node_views.js"></script>
 <script type="text/javascript" src="{{ STATIC_URL }}js/longpoll.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}js/enums.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}js/power_parameters.js"></script>

=== modified file 'src/maasserver/templates/maasserver/node_edit.html'
--- src/maasserver/templates/maasserver/node_edit.html	2012-06-08 10:05:57 +0000
+++ src/maasserver/templates/maasserver/node_edit.html	2012-06-11 13:08:19 +0000
@@ -8,8 +8,6 @@
 {% endblock %}
 
 {% block head %}
-  <script type="text/javascript" src="{{ STATIC_URL }}js/power_parameters.js"></script>
-  <script type="text/javascript" src="{{ STATIC_URL }}js/enums.js"></script>
   <script type="text/javascript">
   <!--
   YUI().use(

=== modified file 'src/maasserver/templates/maasserver/snippets.html'
--- src/maasserver/templates/maasserver/snippets.html	2012-06-06 16:01:55 +0000
+++ src/maasserver/templates/maasserver/snippets.html	2012-06-11 13:08:19 +0000
@@ -6,12 +6,6 @@
     <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
@@ -25,6 +19,20 @@
     <label for="id_after_commissioning_action">After commissioning</label>
     {{ node_form.after_commissioning_action }}
   </p>
+  <p>
+    <label for="id_architecture">Architecture</label>
+    {{ node_form.architecture }}
+  </p>
+  {% if node_form.power_type %}
+    <p>
+      <label for="id_power_type">Power type</label>
+      {{ node_form.power_type }}
+    </p>
+    <p class="power_parameters">
+      <label for="id_power_parameters">Power parameters</label>
+      {{ node_form.power_parameters }}
+    </p>
+  {% endif %}
 </script>
 {% for power_type, fieldset in POWER_TYPE_PARAMETERS_FIELDS %}
   <script type="text/x-template" id="power-param-form-{{ power_type}}">

=== modified file 'src/maasserver/tests/test_config_forms.py'
--- src/maasserver/tests/test_config_forms.py	2012-06-07 14:34:27 +0000
+++ src/maasserver/tests/test_config_forms.py	2012-06-11 13:08:19 +0000
@@ -216,6 +216,11 @@
                 ),
             widget.render(name, values))
 
+    def test_empty_DictCharWidget_renders_as_empty_string(self):
+        widget = DictCharWidget(
+            [widgets.CheckboxInput], [], [], skip_check=True)
+        self.assertEqual('', widget.render(factory.getRandomString(), ''))
+
     def test_DictCharWidget_value_from_datadict_values_from_data(self):
         # 'value_from_datadict' extracts the values corresponding to the
         # field as a dictionary.

=== modified file 'src/maasserver/tests/test_views.py'
--- src/maasserver/tests/test_views.py	2012-04-30 16:26:38 +0000
+++ src/maasserver/tests/test_views.py	2012-06-11 13:08:19 +0000
@@ -31,9 +31,9 @@
     LoggedInTestCase,
     TestCase,
     )
+from maasserver.utils import map_enum
 from maasserver.views import HelpfulDeleteView
 from maasserver.views.nodes import NodeEdit
-from maasserver.utils import map_enum
 from maastesting.matchers import ContainsAll
 from provisioningserver.enum import PSERV_FAULT
 
@@ -70,24 +70,47 @@
 
 class TestSnippets(LoggedInTestCase):
 
-    def assertTemplateExistsAndContains(self, content, template_selector,
-                                        contains_selector):
-        """Assert that the provided html 'content' contains a snippet as
-        selected by 'template_selector' which in turn contains an element
-        selected by 'contains_selector'.
-        """
+    def _assertTemplateExistsAndContains(self, content, template_selector,
+                                         contains_selector, reverse=False):
         doc = fromstring(content)
         snippets = doc.cssselect(template_selector)
         # The snippet exists.
-        self.assertEqual(1, len(snippets))
+        self.assertEqual(
+            1, len(snippets),
+            "The snippet '%s' does not exist." % template_selector)
         # It contains the required element.
         selects = fromstring(snippets[0].text).cssselect(contains_selector)
-        self.assertEqual(1, len(selects))
+        if reverse:
+            self.assertEqual(
+                0, len(selects),
+                "The element '%s' does exist." % contains_selector)
+        else:
+            self.assertEqual(
+                1, len(selects),
+                "The element '%s' does not exist." % contains_selector,)
+
+    def assertTemplateExistsAndContains(self, content, template_selector,
+                                        contains_selector, reverse=False):
+        """Assert that the provided html 'content' contains a snippet as
+        selected by 'template_selector' which in turn contains an element
+        selected by 'contains_selector'.
+        """
+        self._assertTemplateExistsAndContains(
+            content, template_selector, contains_selector)
+
+    def assertTemplateExistsAndDoesNotContain(self, content, template_selector,
+                                              contains_selector):
+        """Assert that the provided html 'content' contains a snippet as
+        selected by 'template_selector' which does not contains an element
+        selected by 'contains_selector'.
+        """
+        self._assertTemplateExistsAndContains(
+            content, template_selector, contains_selector, reverse=True)
 
     def test_architecture_snippet(self):
         response = self.client.get('/')
         self.assertTemplateExistsAndContains(
-            response.content, '#add-architecture', 'select#id_architecture')
+            response.content, '#add-node', 'select#id_architecture')
 
     def test_hostname(self):
         response = self.client.get('/')
@@ -100,6 +123,19 @@
             response.content, '#add-node',
             'select#id_after_commissioning_action')
 
+    def test_power_type_does_not_exist_if_not_admin(self):
+        response = self.client.get('/')
+        self.assertTemplateExistsAndDoesNotContain(
+            response.content, '#add-node',
+            'select#id_power_type')
+
+    def test_power_type_exists_if_admin(self):
+        self.become_admin()
+        response = self.client.get('/')
+        self.assertTemplateExistsAndContains(
+            response.content, '#add-node',
+            'select#id_power_type')
+
 
 class FakeDeletableModel:
     """A fake model class, with a delete method."""