← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~rvb/maas/multifield-ui2 into lp:maas

 

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

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~rvb/maas/multifield-ui2/+merge/108980

This branch adds the power_parameter field to the UI on the node edit page.  Most of the code is located in the JS module that is designed to dynamically change the power_parameter fieldset when the value of power_type changes.

Note that I've removed the usage of node.get_effective_power.  I realized that it would be clumsy to have a node's power_type set to DEFAULT (this means that the default value will be used) and open up the possibility to set a node's power_parameter.  It would be clumsy because it would mean that a node could have completely incompatible power parameters if the default config value where to change.  When power_type is set to default, the widget for power_parameters disappears.
-- 
https://code.launchpad.net/~rvb/maas/multifield-ui2/+merge/108980
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/multifield-ui2 into lp:maas.
=== modified file 'src/maasserver/context_processors.py'
--- src/maasserver/context_processors.py	2012-04-16 10:00:51 +0000
+++ src/maasserver/context_processors.py	2012-06-06 16:39:20 +0000
@@ -18,6 +18,8 @@
 from maasserver.components import get_persistent_errors
 from maasserver.forms import NodeForm
 from maasserver.models import Config
+from maasserver.power_parameters import POWER_TYPE_PARAMETERS
+from provisioningserver.enum import POWER_TYPE
 
 
 def yui(context):
@@ -33,6 +35,10 @@
     return {
         'persistent_errors': get_persistent_errors(),
         'node_form': NodeForm(),
+        'POWER_TYPE_PARAMETERS_FIELDS':
+            [(power_type, field.widget.render('power_parameters', []))
+                for power_type, field in POWER_TYPE_PARAMETERS.items()
+                if power_type is not POWER_TYPE.DEFAULT],
         'global_options': {
             'site_name': Config.objects.get_config('maas_name'),
         }

=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py	2012-06-06 16:39:19 +0000
+++ src/maasserver/forms.py	2012-06-06 16:39:20 +0000
@@ -176,20 +176,23 @@
            'power_parameters',
            )
 
-    def __init__(self, data, instance):
-        super(APIAdminNodeEditForm, self).__init__(data, instance=instance)
+    def __init__(self, data=None, files=None, instance=None, initial=None):
+        super(APIAdminNodeEditForm, self).__init__(
+            data=data, files=files, instance=instance, initial=initial)
         self.set_up_power_parameters_field(data, instance)
 
     def set_up_power_parameters_field(self, data, node):
-        power_type = data.get('power_type', None)
+        if data is None:
+            data = {}
+        power_type = data.get('power_type', self.initial.get('power_type'))
         if power_type is None or power_type not in dict(POWER_TYPE_CHOICES):
-            power_type = node.get_effective_power_type()
+            power_type = node.power_type
         self.fields['power_parameters'] = POWER_TYPE_PARAMETERS[power_type]
 
 
 def get_node_edit_form(user, api=False):
     if user.is_superuser:
-        return APIAdminNodeEditForm if api else UIAdminNodeEditForm
+        return APIAdminNodeEditForm
     else:
         return UINodeEditForm
 

=== modified file 'src/maasserver/static/css/forms.css'
--- src/maasserver/static/css/forms.css	2012-05-02 09:21:57 +0000
+++ src/maasserver/static/css/forms.css	2012-06-06 16:39:20 +0000
@@ -51,6 +51,14 @@
 input[type="text"]:focus {
     border-color: #000;
     }
+fieldset {
+    padding-left: 10;
+    padding-right: 10px;
+    border: 1px solid #999;
+    -moz-border-radius: 2px;
+    -webkit-border-radius: 2px;
+    border-radius: 2px;
+    }
 li.error textarea,
 li.error input[type="password"],
 li.error input[type="text"] {

=== added file 'src/maasserver/static/js/power_parameters.js'
--- src/maasserver/static/js/power_parameters.js	1970-01-01 00:00:00 +0000
+++ src/maasserver/static/js/power_parameters.js	2012-06-06 16:39:20 +0000
@@ -0,0 +1,126 @@
+/* Copyright 2012 Canonical Ltd.  This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ *
+ * Power parameters utilities.
+ *
+ * @module Y.maas.power_parameter
+ */
+
+YUI.add('maas.power_parameters', function(Y) {
+
+Y.log('loading maas.power_parameters');
+var module = Y.namespace('maas.power_parameters');
+
+// Only used to mockup io in tests.
+module._io = new Y.IO();
+
+var DynamicWidget;
+
+/**
+ * A widget class used to have the content of a node dependent of the selected
+ * value of a <select> tag.
+ *
+ */
+DynamicWidget = function() {
+    DynamicWidget.superclass.constructor.apply(this, arguments);
+};
+
+DynamicWidget.NAME = 'dynamic-widget';
+
+Y.extend(DynamicWidget, Y.Widget, {
+
+   /**
+    * Initialize the widget.
+    * - cfg.srcNode is the  node which will be updated when the selected
+    *   value of the 'driver node' will change.
+    * - cfg.driverNode is the node containing a 'select' element.  When
+    *   the selected element will change, the srcNode HTML will be
+    *   updated.
+    * - cfg.driverEnum is an dictionary which contains all the possible values
+    *   of the driverNode's select element.
+    * - cfg.templatePrefix is the prefix string which will be used to fetch
+    *   all the templates for each possible value of driverEnum.
+    *
+    * @method initializer
+    */
+    initializer: function(cfg) {
+        this.driverNode = cfg.driverNode;
+        this.driverEnum = cfg.driverEnum;
+        this.templatePrefix = cfg.templatePrefix;
+        this.initTemplates();
+        this.setVisibility();
+    },
+
+   /**
+    * Create a dictionary containing the templates for all the possible
+    * values from 'this.driverEnum'.
+    *
+    * @method initTemplates
+    */
+    initTemplates: function() {
+        this.templates = {};
+        var driverValue;
+        for (driverValue in this.driverEnum) {
+            if (this.driverEnum.hasOwnProperty(driverValue)) {
+                var type = this.driverEnum[driverValue];
+                var template = Y.one(
+                    this.templatePrefix + type).getContent();
+                this.templates[type] = template;
+            }
+        }
+    },
+
+   /**
+    * Hide 'srcNode' if the value of the 'driverNode' is the empty string
+    * and show it otherwise.
+    *
+    * @method setVisibility
+    */
+    setVisibility: function() {
+        var driverValue = Y.one(this.driverNode).one('select').get('value');
+        if (driverValue === '') {
+            this.get('srcNode').addClass('hidden');
+        }
+        else {
+            this.get('srcNode').removeClass('hidden');
+        }
+    },
+
+   /**
+    * React to a new value of the driver node: update the HTML of
+    * 'srcNode'.
+    *
+    * @method switchTo
+    */
+    switchTo: function(newDriverValue) {
+        // Remove old fieldset if any.
+        var srcNode = this.get('srcNode');
+        srcNode.all('fieldset').remove();
+        // Insert the template fragment corresponding to the new value
+        // of the driver in 'srcNode'.
+        var old_innerHTML = srcNode.get('innerHTML');
+        srcNode.set(
+            'innerHTML', old_innerHTML + this.templates[newDriverValue]);
+        this.setVisibility();
+    },
+
+   /**
+    * Bind the widget's events:
+    *  - hook up the driver's 'change' event to 'this.switchTo(newValue)'.
+    *
+    * @method bindUI
+    */
+    bindUI: function() {
+        var self = this;
+        Y.one(this.driverNode).one('select').on('change', function(e) {
+            var newDriverValue = e.currentTarget.get('value');
+            self.switchTo(newDriverValue);
+        });
+    }
+
+});
+
+module.DynamicWidget = DynamicWidget;
+
+}, '0.1', {'requires': ['widget', 'io', 'maas.enums']}
+);

=== added file 'src/maasserver/static/js/tests/test_power_parameters.html'
--- src/maasserver/static/js/tests/test_power_parameters.html	1970-01-01 00:00:00 +0000
+++ src/maasserver/static/js/tests/test_power_parameters.html	2012-06-06 16:39:20 +0000
@@ -0,0 +1,50 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";>
+<html xmlns="http://www.w3.org/1999/xhtml"; xml:lang="en" lang="en">
+  <head>
+    <title>Test maas.power_parameters</title>
+
+    <!-- YUI and test setup -->
+    <script type="text/javascript" src="../testing/yui_test_conf.js"></script>
+    <script type="text/javascript" src="../../jslibs/yui/tests/build/yui/yui.js"></script>
+    <script type="text/javascript" src="../testing/testrunner.js"></script>
+    <script type="text/javascript" src="../testing/testing.js"></script>
+    <!-- The module under test -->
+    <script type="text/javascript" src="../power_parameters.js"></script>
+    <!-- The test suite -->
+    <script type="text/javascript" src="test_power_parameters.js"></script>
+    <script type="text/javascript">
+    <!--
+    var ENUM = {
+        "DEFAULT": "",
+        "VALUE1": "value1",
+        "VALUE2": "value2"
+    };
+    // -->
+    </script>
+  </head>
+  <body>
+  <span id="suite">maas.power_parameters.tests</span>
+  <script type="text/x-template" id="select_node">
+  <span class="power_type">
+    <select>
+      <option value="" selected="selected">---------</option>
+      <option value="value1">Value1</option>
+      <option value="value2">Value2</option>
+    </select>
+  </span>
+  </script>
+  <script type="text/x-template" id="target_node">
+    <span class="power_parameters"></span>
+  </script>
+  <script type="text/x-template" id="prefix-">
+    Empty default value.
+  </script>
+  <script type="text/x-template" id="prefix-value1">
+    Value1
+  </script>
+  <script type="text/x-template" id="prefix-value2">
+    Value2.
+  </script>
+  <div id="placeholder"></div>
+  </body>
+</html>

=== added file 'src/maasserver/static/js/tests/test_power_parameters.js'
--- src/maasserver/static/js/tests/test_power_parameters.js	1970-01-01 00:00:00 +0000
+++ src/maasserver/static/js/tests/test_power_parameters.js	2012-06-06 16:39:20 +0000
@@ -0,0 +1,88 @@
+/* Copyright 2012 Canonical Ltd.  This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ */
+
+YUI({ useBrowserConsole: true }).add(
+    'maas.power_parameters.tests', function(Y) {
+
+Y.log('loading maas.power_parameters.tests');
+var namespace = Y.namespace('maas.power_parameters.tests');
+
+var module = Y.maas.power_parameters;
+var suite = new Y.Test.Suite("maas.power_parameters Tests");
+
+var select_node_template = Y.one('#select_node').getContent();
+var target_node_template = Y.one('#target_node').getContent();
+
+suite.add(new Y.maas.testing.TestCase({
+    name: 'test-power_parameters',
+
+    setUp: function () {
+        Y.one('#placeholder').empty().append(
+            Y.Node.create(select_node_template).append(
+                Y.Node.create(target_node_template)));
+    },
+
+    testInitializerSetsUpVariables: function() {
+        var widget = new Y.maas.power_parameters.DynamicWidget({
+            srcNode: '.power_parameters',
+            driverNode: '.power_type',
+            driverEnum: ENUM,
+            templatePrefix: '#prefix-'
+            });
+        Y.Assert.areEqual('.power_type', widget.driverNode);
+        Y.Assert.areEqual(ENUM, widget.driverEnum);
+        Y.Assert.areEqual('#prefix-', widget.templatePrefix);
+    },
+
+    testInitializerInitializesTemplates: function() {
+        var widget = new Y.maas.power_parameters.DynamicWidget({
+            srcNode: '.power_parameters',
+            driverNode: '.power_type',
+            driverEnum: ENUM,
+            templatePrefix: '#prefix-'
+            });
+        var key;
+        for (key in ENUM) {
+            if (ENUM.hasOwnProperty(key)) {
+                var value = ENUM[key];
+                var template = Y.one('#prefix-' + value).getContent();
+                Y.Assert.areEqual(template, widget.templates[value]);
+            }
+        }
+    },
+
+    testInitializerSetsVisibility: function() {
+        var widget = new Y.maas.power_parameters.DynamicWidget({
+            srcNode: '.power_parameters',
+            driverNode: '.power_type',
+            driverEnum: ENUM,
+            templatePrefix: '#prefix-'
+            });
+        Y.Assert.isTrue(Y.one('.power_parameters').hasClass('hidden'));
+    },
+
+    testswitchToUpdatesSrcNode: function() {
+        var widget = new Y.maas.power_parameters.DynamicWidget({
+            srcNode: '.power_parameters',
+            driverNode: '.power_type',
+            driverEnum: ENUM,
+            templatePrefix: '#prefix-'
+            });
+        var newValue = 'value1';
+        Y.one('.power_type').one('select').set('value', newValue);
+        widget.switchTo(newValue);
+        Y.Assert.isFalse(Y.one('.power_parameters').hasClass('hidden'));
+        var template = Y.one('#prefix-' + newValue).getContent();
+        Y.Assert.areEqual(
+            template, Y.one('.power_parameters').get('innerHTML'));
+    }
+
+
+}));
+
+namespace.suite = suite;
+
+}, '0.1', {'requires': [
+    'node-event-simulate', 'test', 'maas.testing', 'maas.power_parameters']}
+);

=== modified file 'src/maasserver/templates/maasserver/node_edit.html'
--- src/maasserver/templates/maasserver/node_edit.html	2012-04-27 08:11:29 +0000
+++ src/maasserver/templates/maasserver/node_edit.html	2012-06-06 16:39:20 +0000
@@ -4,6 +4,33 @@
 {% block title %}Edit node{% endblock %}
 {% block page-title %}Edit node{% endblock %}
 
+{% block html_includes %}{% include "maasserver/snippets.html" %}
+{% 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(
+    'maas.enums', 'maas.power_parameters',
+    function (Y) {
+    Y.on('load', function() {
+      // Create DynamicWidget widget so that the power_parameter field
+      // will be updated each time the value of the power_type field changes.
+      var widget = new Y.maas.power_parameters.DynamicWidget({
+          srcNode: '.power_parameters',
+          driverNode: '.power_type',
+          driverEnum: Y.maas.enums.POWER_TYPE,
+          templatePrefix: '#power-param-form-'
+          });
+      widget.render();
+    });
+  });
+  // -->
+  </script>
+{% endblock %}
+
 {% block content %}
   <form action="." method="post" class="block auto-width">
     <ul>

=== modified file 'src/maasserver/templates/maasserver/snippets.html'
--- src/maasserver/templates/maasserver/snippets.html	2012-04-23 07:56:57 +0000
+++ src/maasserver/templates/maasserver/snippets.html	2012-06-06 16:39:20 +0000
@@ -26,3 +26,11 @@
     {{ node_form.after_commissioning_action }}
   </p>
 </script>
+{% for power_type, fieldset in POWER_TYPE_PARAMETERS_FIELDS %}
+  <script type="text/x-template" id="power-param-form-{{ power_type}}">
+    {{ fieldset }}
+  </script>
+{% endfor %}
+<script type="text/x-template" id="power-param-form-">
+</script>
+

=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py	2012-06-06 16:39:19 +0000
+++ src/maasserver/tests/test_forms.py	2012-06-06 16:39:20 +0000
@@ -51,9 +51,7 @@
     )
 from maasserver.testing.factory import factory
 from maasserver.testing.testcase import TestCase
-from provisioningserver.enum import (
-    POWER_TYPE_CHOICES,
-    )
+from provisioningserver.enum import POWER_TYPE_CHOICES
 from testtools.testcase import ExpectedException
 
 
@@ -291,10 +289,6 @@
             (node.hostname, node.after_commissioning_action, node.power_type,
                 node.power_parameters))
 
-    def test_get_node_edit_form_returns_UIAdminNodeEditForm_if_admin(self):
-        admin = factory.make_admin()
-        self.assertEqual(UIAdminNodeEditForm, get_node_edit_form(admin))
-
     def test_get_node_edit_form_returns_UINodeEditForm_if_non_admin(self):
         user = factory.make_user()
         self.assertEqual(UINodeEditForm, get_node_edit_form(user))


Follow ups