diff options
author | Jenkins <jenkins@review.openstack.org> | 2014-10-14 08:16:06 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2014-10-14 08:16:06 +0000 |
commit | 03e1425aa87357543b11391ed19eb5b6c0763b30 (patch) | |
tree | f83e312b342ad2366412605594e0cda0727db781 | |
parent | ef08ef4e12b2961edebda80dfa10c3b5adef285c (diff) | |
parent | db3d7cd35c69ef7ebe901e1f5e5417e010bb71bd (diff) | |
download | tuskar-ui-03e1425aa87357543b11391ed19eb5b6c0763b30.tar.gz |
Merge "New node registration and upload dialogs"
16 files changed, 239 insertions, 275 deletions
diff --git a/tuskar_ui/forms.py b/tuskar_ui/forms.py index 6a17b037..acdd4ce3 100644 --- a/tuskar_ui/forms.py +++ b/tuskar_ui/forms.py @@ -109,7 +109,7 @@ class SelfHandlingFormset(forms.formsets.BaseFormSet): def handle(self, request, data): success = True for form in self: - form_success = form.handle(request, data) + form_success = form.handle(request, form.cleaned_data) if not form_success: success = False else: diff --git a/tuskar_ui/infrastructure/nodes/forms.py b/tuskar_ui/infrastructure/nodes/forms.py index c529a0cd..b2bfd244 100644 --- a/tuskar_ui/infrastructure/nodes/forms.py +++ b/tuskar_ui/infrastructure/nodes/forms.py @@ -11,7 +11,6 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - import csv import django.forms @@ -24,6 +23,7 @@ import tuskar_ui.forms CPU_ARCH_CHOICES = [ + ('', _("unspecified")), ('amd64', _("amd64")), ('x86', _("x86")), ('x86_64', _("x86_64")), @@ -36,9 +36,7 @@ DRIVER_CHOICES = [ def get_driver_info_dict(data): driver = data['driver'] - driver_dict = { - 'driver': driver - } + driver_dict = {'driver': driver} if driver == 'ipmi': driver_dict.update( # TODO(rdopieralski) If ipmi_address is no longer required, @@ -56,18 +54,22 @@ def get_driver_info_dict(data): return driver_dict -def auto_discover_node(request, kwargs): - node = api.node.Node.create( - request, - **kwargs +def create_node(request, data): + kwargs = get_driver_info_dict(data) + kwargs.update( + cpu_arch=data.get('cpu_arch'), + cpus=data.get('cpus'), + memory_mb=data.get('memory_mb'), + local_gb=data.get('local_gb'), + mac_addresses=data['mac_addresses'].split(), ) - api.node.Node.set_maintenance(request, - node.uuid, - True) - api.node.Node.discover(request, [node.uuid]) + node = api.node.Node.create(request, **kwargs) + if data.get('do_autodiscovery', False): + api.node.Node.set_maintenance(request, node.uuid, True) + api.node.Node.discover(request, [node.uuid]) -class BaseNodeForm(django.forms.Form): +class NodeForm(django.forms.Form): id = django.forms.IntegerField( label="", required=False, @@ -139,156 +141,129 @@ class BaseNodeForm(django.forms.Form): 'rows': 2, }), ) - - def get_name(self): - try: - name = (self.fields['ipmi_address'].value() or - self.fields['ssh_address'].value()) - except AttributeError: - # when the field is not bound - name = _("Undefined node") - return name - - def create_node(self, request, data): - kwargs = get_driver_info_dict(data) - kwargs.update( - cpu_arch=data.get('cpu_arch'), - cpus=data.get('cpus'), - memory_mb=data.get('memory_mb'), - local_gb=data.get('local_gb'), - mac_addresses=data['mac_addresses'].split(), - ) - api.node.Node.create(request, **kwargs) - - def handle(self, request, data): - success = True - data = self.cleaned_data - try: - self.create_node(request, data) - except Exception: - success = False - exceptions.handle(request, _('Unable to register node.')) - # TODO(tzumainn) If there is a failure between steps, do we - # have to unregister nodes, delete ports, etc? - return success - - -class RegisterNodeForm(BaseNodeForm): + do_autodiscovery = django.forms.BooleanField( + label=_("Discover missing attributes"), + required=False, + ) mac_addresses = tuskar_ui.forms.MultiMACField( label=_("NIC MAC Addresses"), + required=False, widget=django.forms.Textarea(attrs={ - 'class': 'form-control', + 'placeholder': _('unspecified'), 'rows': '2', }), ) cpu_arch = django.forms.ChoiceField( label=_("Architecture"), - required=True, + required=False, choices=CPU_ARCH_CHOICES, widget=django.forms.Select( - attrs={'class': 'form-control'}), + attrs={'placeholder': _('unspecified')}), ) cpus = django.forms.IntegerField( label=_("CPUs"), - required=True, - min_value=1, - initial=1, + required=False, + min_value=0, widget=tuskar_ui.forms.NumberInput( - attrs={'class': 'form-control'}), + attrs={'placeholder': _('unspecified')}), ) memory_mb = django.forms.IntegerField( label=_("Memory"), - required=True, - min_value=1, - initial=1, + required=False, + min_value=0, widget=tuskar_ui.forms.NumberInput( - attrs={'class': 'form-control'}), + attrs={'placeholder': _('unspecified')}), ) local_gb = django.forms.IntegerField( label=_("Local Disk"), - required=True, - min_value=1, - initial=1, + required=False, + min_value=0, widget=tuskar_ui.forms.NumberInput( - attrs={'class': 'form-control'}), + attrs={'placeholder': _('unspecified')}), ) + def get_name(self): + try: + name = (self.fields['ipmi_address'].value() or + self.fields['ssh_address'].value()) + except AttributeError: + # when the field is not bound + name = _("Undefined node") + return name + + def handle(self, request, data): + success = True + try: + create_node(request, data) + except Exception: + success = False + exceptions.handle(request, _('Unable to register node.')) + # TODO(tzumainn) If there is a failure between steps, do we + # have to unregister nodes, delete ports, etc? + return success -class AutoDiscoverNodeForm(BaseNodeForm): - mac_addresses = tuskar_ui.forms.MultiMACField( - label=_("NIC MAC Addresses"), - required=False, - widget=django.forms.Textarea(attrs={ - 'class': 'form-control switched', - 'data-switch-on': 'driver', - 'data-driver-pxe_ssh': _("PXE + SSH"), - 'rows': 2, - }), - ) + def clean_ipmi_username(self): + return self.cleaned_data.get('ipmi_username') or None - def create_node(self, request, data): - kwargs = get_driver_info_dict(data) - kwargs.update( - mac_addresses=data['mac_addresses'].split(), - ) - auto_discover_node(request, kwargs) + def clean_ipmi_password(self): + return self.cleaned_data.get('ipmi_password') or None + def clean(self): + cleaned_data = super(NodeForm, self).clean() + if not cleaned_data.get('do_autodiscovery', False): + for field_name in [ + 'mac_addresses', + 'cpu_arch', + 'cpus', + 'memory_mb', + 'local_gb', + ]: + if not cleaned_data.get(field_name): + self._errors[field_name] = self.error_class([( + u"This field is required " + u"when autodiscovery is disabled." + )]) + return cleaned_data -class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset): +class BaseNodeFormset(tuskar_ui.forms.SelfHandlingFormset): def clean(self): for form in self: if not form.cleaned_data: raise django.forms.ValidationError( _("Please provide node data for all nodes.")) - if not form.cleaned_data.get('ipmi_username'): - form.cleaned_data['ipmi_username'] = None - if not form.cleaned_data.get('ipmi_password'): - form.cleaned_data['ipmi_password'] = None -RegisterNodeFormset = django.forms.formsets.formset_factory( - RegisterNodeForm, extra=1, - formset=BaseNodeFormset) - - -AutoDiscoverNodeFormset = django.forms.formsets.formset_factory( - AutoDiscoverNodeForm, extra=1, - formset=BaseNodeFormset) - - -class AutoDiscoverCSVNodeForm(forms.SelfHandlingForm): - csv_file = forms.FileField(label=_("CSV File"), - required=False) +class UploadNodeForm(forms.SelfHandlingForm): + csv_file = forms.FileField(label=_("CSV File"), required=True) def handle(self, request, data): - success = True - all_node_data = csv.reader(data['csv_file']) - for node_data in all_node_data: - driver = node_data[0] - kwargs = { - 'driver': driver - } + return True + + def get_data(self): + data = [] + for row in csv.reader(self.cleaned_data['csv_file']): + driver = row[0].strip() if driver == 'pxe_ssh': - kwargs.update( - ssh_address=node_data[1], - ssh_username=node_data[2], - ssh_key_contents=node_data[3], - mac_addresses=node_data[4].split() + node = dict( + ssh_address=row[1], + ssh_username=row[2], + ssh_key_contents=row[3], + mac_addresses=row[4], + driver=driver, + do_autodiscovery=True, ) - else: - kwargs.update( - ipmi_address=node_data[1], - ipmi_username=node_data[2], - ipmi_password=node_data[3], + elif driver == 'ipmi': + node = dict( + ipmi_address=row[1], + ipmi_username=row[2], + ipmi_password=row[3], + driver=driver, + do_autodiscovery=True, ) + data.append(node) + return data - try: - auto_discover_node(request, kwargs) - except Exception: - success = False - exceptions.handle(request, _('Unable to register node.')) - # TODO(tzumainn) If there is a failure between steps, do we - # have to unregister nodes, delete ports, etc? - return success +RegisterNodeFormset = django.forms.formsets.formset_factory( + NodeForm, extra=1, formset=BaseNodeFormset) diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover.html deleted file mode 100644 index 1e06ba3b..00000000 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover.html +++ /dev/null @@ -1,20 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}autodiscover_nodes_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:nodes:auto-discover' %}{% endblock %} -{% block modal_id %}autodiscover_nodes_modal{% endblock %} -{% block modal-header %}{% trans "Auto-Discover Nodes" %}{% endblock %} - -{% block modal-body %} -{% include "formset_table/menu_formset.html" with formset=form form_template="infrastructure/nodes/_auto_discover_nodes_formset_form.html" %} -{% endblock %} - -{% block modal-footer %} - <input class="btn btn-primary pull-right" type="submit" - value="{% trans "Register Nodes" %}" /> - <a href="{% url 'horizon:infrastructure:nodes:index' %}" - class="btn secondary cancel close">{% trans "Cancel" %}</a> -{% endblock %} - diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html index 9e4c4978..b6d7f14b 100644 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html +++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_csv.html @@ -3,9 +3,10 @@ {% load url from future %} {% block form_id %}autodiscover_csv_nodes_form{% endblock %} -{% block form_action %}{% url 'horizon:infrastructure:nodes:auto-discover-csv' %}{% endblock %} +{% block form_action %}{% url 'horizon:infrastructure:nodes:register' %}{% endblock %} + {% block modal_id %}autodiscover_csv_nodes_modal{% endblock %} -{% block modal-header %}{% trans "Auto-Discover Nodes (Upload CSV)" %}{% endblock %} +{% block modal-header %}{% trans "Upload Nodes" %}{% endblock %} {% block form_attrs %}enctype="multipart/form-data"{% endblock %} {% block modal-body %} @@ -13,9 +14,10 @@ {% endblock %} {% block modal-footer %} - <input class="btn btn-primary pull-right" type="submit" - value="{% trans "Register Nodes" %}" /> + <button class="btn btn-primary pull-right" type="submit"> + <i class="fa fa-upload"></i> + {% trans "Upload" %} + </button> <a href="{% url 'horizon:infrastructure:nodes:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a> {% endblock %} - diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_nodes_formset_form.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_nodes_formset_form.html deleted file mode 100644 index c8ef4bbe..00000000 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/_auto_discover_nodes_formset_form.html +++ /dev/null @@ -1,31 +0,0 @@ -<div class="well tab-pane{% if active %} active{% endif %}" - id="tab-{{ form.prefix }}"> - <div class="form form-inline"><fieldset> - <div class="row"> - <h4>Node Detail</h4> - </div> - <div class="row"> - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.driver required=True %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_username %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_password %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_address %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_username %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_key_contents %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses %} - </div> - </fieldset></div> -</div> - -<script type="text/javascript"> -(window.$ || window.addHorizonLoadEvent)(function () { - var form_prefix = '{{ form.prefix|escapejs }}'; - var $form = $('#tab-' + form_prefix); - var $nav_link = $('a[href="#' + $form.attr('id') + '"]'); - var undefined_name = '{{ form.get_name|escapejs }}'; - - $form.find('input[name$="-ipmi_address"]').change(function () { - $nav_link.html($(this).val() || undefined_name); - }); -}); -</script> diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html index 4802dc04..1bb851e0 100644 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html +++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_nodes_formset_form.html @@ -1,32 +1,31 @@ -<div class="well tab-pane{% if active %} active{% endif %}" +{% load i18n %} +{% load form_helpers %} + +<div class="container-fluid tab-pane{% if active %} active{% endif %}" id="tab-{{ form.prefix }}"> - <div class="form form-inline"><fieldset> - <div class="row"> - <h4>Node Detail</h4> - </div> + <div class="form form-inline"><fieldset class="well"> + {% include 'horizon/common/_form_errors.html' with form=form %} <div class="row"> - <h5>Power Management</h5> - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.driver required=True %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_username %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_password %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_address %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_username %} - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_key_contents %} - </div> - <div class="row"> - <h5>Networking</h5> - {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses required=True %} + <h4>{% trans "Node Detail" %}</h4> </div> - <div class="row"> - <div class="col-xs-4"> - <h5>Hardware</h5> + <h5 class="row">{% trans "Power Management" %}</h5> + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.driver required=True %} + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_address %} + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_username %} + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ipmi_password %} + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_address %} + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_username %} + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.ssh_key_contents %} + <div class="panel panel-default"> + <div class="panel-heading"> + <label class="checkbox"> + {{ form.do_autodiscovery|add_bootstrap_class }} + {{ form.do_autodiscovery.label }} + </label> </div> - <label class="col-xs-6 checkbox checkbox-inline"> - {{ form.introspect_hardware }}<small> {{ form.introspect_hardware.label }}</small> - </label> - </div> - <div class="row" id="register-hardware-fields"> + <h5 class="row">{% trans "Networking" %}</h5> + {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.mac_addresses required=True %} + <h5 class="row">{% trans "Hardware" %}</h5> {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpu_arch required=True %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.cpus extra_text=_('units') required=True %} {% include 'infrastructure/nodes/_nodes_formset_field.html' with field=form.memory_mb extra_text=_('MB') required=True %} @@ -45,5 +44,11 @@ $form.find('input[name$="-ipmi_address"]').change(function () { $nav_link.html($(this).val() || undefined_name); }); + + $form.find('input[name$="-do_autodiscovery"]').change(function () { + var $this = $(this); + $this.closest('.panel').find( + '.form-group .row').toggleClass('required', !($this.attr('checked'))); + }); }); </script> diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html b/tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html new file mode 100644 index 00000000..7638cb49 --- /dev/null +++ b/tuskar_ui/infrastructure/nodes/templates/nodes/_upload.html @@ -0,0 +1,24 @@ +{% extends "horizon/common/_modal_form.html" %} +{% load i18n %} +{% load url from future %} + +{% block form_id %}upload_nodes_form{% endblock %} +{% block form_action %}{% url 'horizon:infrastructure:nodes:register' %}?upload{% endblock %} + +{% block modal_id %}upload_nodes_modal{% endblock %} +{% block modal-header %}{% trans "Upload Nodes" %}{% endblock %} +{% block form_attrs %}enctype="multipart/form-data"{% endblock %} + +{% block modal-body %} + {% include "horizon/common/_form_fields.html" %} +{% endblock %} + +{% block modal-footer %} + <button class="btn btn-primary pull-right" type="submit"> + <i class="fa fa-upload"></i> + {% trans "Upload" %} + </button> + <a href="{% url 'horizon:infrastructure:nodes:index' %}" + class="btn secondary cancel close">{% trans "Cancel" %}</a> +{% endblock %} + diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover.html b/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover.html deleted file mode 100644 index e52ebaae..00000000 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "infrastructure/base.html" %} -{% load i18n %} -{% block title %}{% trans "Auto-Discover Nodes" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Auto-Discover Nodes") %} -{% endblock %} - -{% block main %} - {% include "infrastructure/nodes/_auto_discover.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover_csv.html b/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover_csv.html deleted file mode 100644 index bde32008..00000000 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/auto_discover_csv.html +++ /dev/null @@ -1,11 +0,0 @@ -{% extends "infrastructure/base.html" %} -{% load i18n %} -{% block title %}{% trans "Auto-Discover Nodes (Upload CSV)" %}{% endblock %} - -{% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Auto-Discover Nodes (Upload CSV)") %} -{% endblock %} - -{% block main %} - {% include "infrastructure/nodes/_auto_discover_csv.html" %} -{% endblock %} diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/index.html b/tuskar_ui/infrastructure/nodes/templates/nodes/index.html index 3519a9b7..362c876a 100644 --- a/tuskar_ui/infrastructure/nodes/templates/nodes/index.html +++ b/tuskar_ui/infrastructure/nodes/templates/nodes/index.html @@ -11,18 +11,16 @@ <div class="row"> <div class="col-xs-12"> <div class="actions pull-right"> - <a href="{% url 'horizon:infrastructure:nodes:register' %}" class="btn btn-primary ajax-modal"> + <a href="{% url 'horizon:infrastructure:nodes:register' %}" + class="btn btn-primary ajax-modal"> <span class="fa fa-plus"></span> {% trans 'Register Nodes' %} </a> {% if ironic_enabled %} - <a href="{% url 'horizon:infrastructure:nodes:auto-discover' %}" class="btn btn-primary ajax-modal"> - <span class="fa fa-search-plus"></span> - {% trans 'Auto-Discover Nodes' %} - </a> - <a href="{% url 'horizon:infrastructure:nodes:auto-discover-csv' %}" class="btn btn-primary ajax-modal"> - <span class="fa fa-search-plus"></span> - {% trans 'Auto-Discover Nodes (Upload CSV)' %} + <a href="{% url 'horizon:infrastructure:nodes:auto-discover-csv' %}" + class="btn btn-primary ajax-modal"> + <span class="fa fa-upload"></span> + {% trans 'Upload Nodes' %} </a> {% endif %} </div> diff --git a/tuskar_ui/infrastructure/nodes/templates/nodes/upload.html b/tuskar_ui/infrastructure/nodes/templates/nodes/upload.html new file mode 100644 index 00000000..a95bcede --- /dev/null +++ b/tuskar_ui/infrastructure/nodes/templates/nodes/upload.html @@ -0,0 +1,11 @@ +{% extends "infrastructure/base.html" %} +{% load i18n %} +{% block title %}{% trans "Upload Nodes" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Upload Nodes") %} +{% endblock %} + +{% block main %} + {% include "infrastructure/nodes/_upload.html" %} +{% endblock %} diff --git a/tuskar_ui/infrastructure/nodes/tests.py b/tuskar_ui/infrastructure/nodes/tests.py index ea5473b3..5725f9bb 100644 --- a/tuskar_ui/infrastructure/nodes/tests.py +++ b/tuskar_ui/infrastructure/nodes/tests.py @@ -17,7 +17,7 @@ import json from django.core import urlresolvers from horizon import exceptions as horizon_exceptions -from mock import patch, call # noqa +from mock import patch, call, ANY # noqa from openstack_dashboard.test import helpers from openstack_dashboard.test.test_data import utils @@ -157,11 +157,11 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase): 'create.return_value': node, }) as Node: res = self.client.post(REGISTER_URL, data) + self.assertNoFormErrors(res) self.assertRedirectsNoFollow(res, INDEX_URL) - request = Node.create.call_args_list[0][0][0] # This is a hack. self.assertListEqual(Node.create.call_args_list, [ call( - request, + ANY, ipmi_address=u'127.0.0.1', cpu_arch='x86', cpus=1, @@ -173,7 +173,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase): driver='ipmi', ), call( - request, + ANY, ipmi_address=u'127.0.0.2', cpu_arch='x86', cpus=4, @@ -216,10 +216,9 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase): }) as Node: res = self.client.post(REGISTER_URL, data) self.assertEqual(res.status_code, 200) - request = Node.create.call_args_list[0][0][0] # This is a hack. self.assertListEqual(Node.create.call_args_list, [ call( - request, + ANY, ipmi_address=u'127.0.0.1', cpu_arch='x86', cpus=1, @@ -231,7 +230,7 @@ class NodesTests(test.BaseAdminViewTests, helpers.APITestCase): driver='ipmi', ), call( - request, + ANY, ipmi_address=u'127.0.0.2', cpu_arch='x86', cpus=4, diff --git a/tuskar_ui/infrastructure/nodes/urls.py b/tuskar_ui/infrastructure/nodes/urls.py index d1dc0935..c26dd16e 100644 --- a/tuskar_ui/infrastructure/nodes/urls.py +++ b/tuskar_ui/infrastructure/nodes/urls.py @@ -22,9 +22,7 @@ urlpatterns = urls.patterns( urls.url(r'^$', views.IndexView.as_view(), name='index'), urls.url(r'^register/$', views.RegisterView.as_view(), name='register'), - urls.url(r'^auto-discover/$', views.AutoDiscoverView.as_view(), - name='auto-discover'), - urls.url(r'^auto-discover-csv/$', views.AutoDiscoverCSVView.as_view(), + urls.url(r'^auto-discover-csv/$', views.UploadView.as_view(), name='auto-discover-csv'), urls.url(r'^nodes_performance/$', views.PerformanceView.as_view(), name='nodes_performance'), diff --git a/tuskar_ui/infrastructure/nodes/views.py b/tuskar_ui/infrastructure/nodes/views.py index 6f80da87..0db92b2e 100644 --- a/tuskar_ui/infrastructure/nodes/views.py +++ b/tuskar_ui/infrastructure/nodes/views.py @@ -14,9 +14,11 @@ import json from django.core.urlresolvers import reverse_lazy -from django import http +import django.forms +import django.http from django.utils.translation import ugettext_lazy as _ from django.views.generic import base +from horizon import exceptions from horizon import forms as horizon_forms from horizon import tabs as horizon_tabs from horizon.utils import memoized @@ -50,32 +52,35 @@ class RegisterView(horizon_forms.ModalFormView): return [] def get_form(self, form_class): - return form_class(self.request.POST or None, - initial=self.get_data(), - prefix=self.form_prefix) - - -class AutoDiscoverView(horizon_forms.ModalFormView): - form_class = forms.AutoDiscoverNodeFormset - form_prefix = 'auto_discover_nodes' - template_name = 'infrastructure/nodes/auto_discover.html' + initial = [] + if self.request.FILES: + csv_form = forms.UploadNodeForm(self.request, + files=self.request.FILES) + if csv_form.is_valid(): + initial = csv_form.get_data() + formset = forms.RegisterNodeFormset( + None, + initial=initial, + prefix=self.form_prefix, + ) + formset.extra = 0 + return formset + return forms.RegisterNodeFormset( + self.request.POST or None, + initial=initial, + prefix=self.form_prefix, + ) + + +class UploadView(horizon_forms.ModalFormView): + form_class = forms.UploadNodeForm + template_name = 'infrastructure/nodes/upload.html' success_url = reverse_lazy( 'horizon:infrastructure:nodes:index') - def get_data(self): - return [] - - def get_form(self, form_class): - return form_class(self.request.POST or None, - initial=self.get_data(), - prefix=self.form_prefix) - - -class AutoDiscoverCSVView(horizon_forms.ModalFormView): - form_class = forms.AutoDiscoverCSVNodeForm - template_name = 'infrastructure/nodes/auto_discover_csv.html' - success_url = reverse_lazy( - 'horizon:infrastructure:nodes:index') + def post(self, request, *args, **kwargs): + # This form's POST is handled in RegisterView. + raise exceptions.NotFound() class DetailView(horizon_tabs.TabView): @@ -138,5 +143,5 @@ class PerformanceView(base.TemplateView): date_from=date_from, date_to=date_to, stats_attr=stats_attr, barchart=barchart) - return http.HttpResponse(json.dumps(json_output), - content_type='application/json') + return django.http.HttpResponse( + json.dumps(json_output), content_type='application/json') diff --git a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js index d3d8bc89..ef9c3b4c 100644 --- a/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js +++ b/tuskar_ui/infrastructure/static/infrastructure/js/tuskar.menu_formset.js @@ -32,7 +32,7 @@ tuskar.menu_formset = (function () { function add_delete_link($nav_item) { var $form = $content.find($nav_item.find('a').attr('href')); - $nav_item.prepend('<span class="btn-small pull-right delete-icon"><i class="fa fa-trash"></i></span>'); + $nav_item.prepend('<span class="btn-small pull-right delete-icon"><i class="fa fa-times"></i></span>'); $nav_item.find('span.delete-icon:first').click(function () { var count; $form.remove(); @@ -53,13 +53,24 @@ tuskar.menu_formset = (function () { $nav.append('<li><a href="#' + id + '" data-toggle="tab">Undefined node</a></li>'); $new_nav = $nav.find('li > a:last'); add_delete_link($new_nav.parent()); - $new_nav.click(function () { $(this).tab('show'); }); + $new_nav.click(function () { + $(this).tab('show'); + $('select.switchable').trigger('change'); + }); $new_nav.tab('show'); + $('select.switchable').trigger('change'); } // Connect all signals. $('a.add-node-link').click(add_node); - $nav.find('li').each(function () { add_delete_link($(this)); }); + $nav.find('li').each(function () { + add_delete_link($(this)); + }); + $nav.find('li a').click(function () { + window.setTimeout(function () { + $('select.switchable').trigger('change'); + }, 0); + }); // Activate the first field that has errors. $content.find('.control-group.error').each(function () { @@ -68,6 +79,7 @@ tuskar.menu_formset = (function () { activated = true; } }); + }; return module; diff --git a/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss b/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss index 88861707..63f47ec9 100644 --- a/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss +++ b/tuskar_ui/infrastructure/static/infrastructure/scss/_individual_pages.scss @@ -78,10 +78,18 @@ $link-color: #428bca; font-weight: normal; } + .panel { + margin: 8px -8px 8px -8px; + padding: 8px; + .panel-heading { + margin: -8px -8px 0 -8px; + } + } + fieldset .form-group { width: 100%; input, textarea, select { - width: 150px; + width: 100%; margin-left: 5px; } } |