launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #08554
[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