diff --git a/horizon/static/horizon/js/horizon.modals.js b/horizon/static/horizon/js/horizon.modals.js index 6ad7674030..b756a18766 100644 --- a/horizon/static/horizon/js/horizon.modals.js +++ b/horizon/static/horizon/js/horizon.modals.js @@ -107,7 +107,7 @@ horizon.modals.init_wizard = function () { $form.find('.form-group.has-error').each(function () { var $group = $(this); $group.removeClass('has-error'); - $group.find('span.help-block.alert').remove(); + $group.find('span.help-block').remove(); }); // Temporarilly remove "disabled" attribute to get the values serialized @@ -153,7 +153,7 @@ horizon.modals.init_wizard = function () { $field = $fieldset.find('[name="' + field + '"]'); $field.closest('.form-group').addClass('has-error'); $.each(errors, function (index, error) { - $field.after( + $field.closest(".form-control").after( '' + error + ''); }); diff --git a/openstack_dashboard/dashboards/admin/networks/forms.py b/openstack_dashboard/dashboards/admin/networks/forms.py index e57575ef3d..ee9d9e8b61 100644 --- a/openstack_dashboard/dashboards/admin/networks/forms.py +++ b/openstack_dashboard/dashboards/admin/networks/forms.py @@ -133,6 +133,20 @@ class CreateNetwork(forms.SelfHandlingForm): initial=False, required=False) external = forms.BooleanField(label=_("External Network"), initial=False, required=False) + with_subnet = forms.BooleanField(label=_("Create Subnet"), + widget=forms.CheckboxInput(attrs={ + 'class': 'switchable', + 'data-slug': 'with_subnet', + 'data-hide-tab': 'create_network__' + 'createsubnetinfo' + 'action,' + 'create_network__' + 'createsubnetdetail' + 'action', + 'data-hide-on-checked': 'false' + }), + initial=True, + required=False) @classmethod def _instantiate(cls, request, *args, **kwargs): @@ -263,7 +277,6 @@ class CreateNetwork(forms.SelfHandlingForm): network = api.neutron.network_create(request, **params) msg = _('Network %s was successfully created.') % data['name'] LOG.debug(msg) - messages.success(request, msg) return network except Exception: redirect = reverse('horizon:admin:networks:index') diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/_create.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/_create.html deleted file mode 100644 index 17c52be3f1..0000000000 --- a/openstack_dashboard/dashboards/admin/networks/templates/networks/_create.html +++ /dev/null @@ -1,22 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} - -{% block form_id %}create_network_form{% endblock %} -{% block form_action %}{% url 'horizon:admin:networks:create' %}{% endblock %} - -{% block modal_id %}create_network_modal{% endblock %} -{% block modal-header %}{% trans "Create Network" %}{% endblock %} - -{% block modal-body %} -
-
- {% include "horizon/common/_form_fields.html" %} -
-
-
-

{% trans "Description:" %}

-

{% trans "Create a new network for any project as you need."%}

-

{% trans "Provider specified network can be created. You can specify a physical network type (like Flat, VLAN, GRE, and VXLAN) and its segmentation_id or physical network name for a new virtual network."%}

-

{% trans "In addition, you can create an external network or a shared network by checking the corresponding checkbox."%}

-
-{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/networks/templates/networks/create.html b/openstack_dashboard/dashboards/admin/networks/templates/networks/create.html deleted file mode 100644 index 79d02fb63d..0000000000 --- a/openstack_dashboard/dashboards/admin/networks/templates/networks/create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{% trans "Create Network" %}{% endblock %} - -{% block main %} - {% include "admin/networks/_create.html" %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/networks/tests.py b/openstack_dashboard/dashboards/admin/networks/tests.py index eb220f696d..7f2297919e 100644 --- a/openstack_dashboard/dashboards/admin/networks/tests.py +++ b/openstack_dashboard/dashboards/admin/networks/tests.py @@ -21,6 +21,7 @@ from django.utils.http import urlunquote from mox3.mox import IsA # noqa from openstack_dashboard import api +from openstack_dashboard.dashboards.project.networks import tests from openstack_dashboard.test import helpers as test INDEX_TEMPLATE = 'horizon/common/_data_table_view.html' @@ -381,7 +382,7 @@ class NetworkTests(test.BaseAdminViewTests): url = reverse('horizon:admin:networks:create') res = self.client.get(url) - self.assertTemplateUsed(res, 'admin/networks/create.html') + self.assertTemplateUsed(res, 'horizon/common/_workflow_base.html') @test.update_settings( OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'}) @@ -390,7 +391,8 @@ class NetworkTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('network_create', 'profile_list', - 'is_extension_supported',), + 'is_extension_supported', + 'subnetpool_list'), api.keystone: ('tenant_list',)}) def test_network_create_post(self, test_with_profile=False): @@ -405,7 +407,8 @@ class NetworkTests(test.BaseAdminViewTests): 'admin_state_up': network.admin_state_up, 'router:external': True, 'shared': True, - 'provider:network_type': 'local'} + 'provider:network_type': 'local', + 'with_subnet': False} if test_with_profile: net_profiles = self.net_profiles.list() net_profile_id = self.net_profiles.first().id @@ -414,8 +417,14 @@ class NetworkTests(test.BaseAdminViewTests): params['net_profile_id'] = net_profile_id api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ MultipleTimes().AndReturn(True) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'subnet_allocation').\ + MultipleTimes().AndReturn(True) + api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ + AndReturn(self.subnetpools.list()) api.neutron.network_create(IsA(http.HttpRequest), **params)\ .AndReturn(network) + self.mox.ReplayAll() form_data = {'tenant_id': tenant_id, @@ -432,6 +441,62 @@ class NetworkTests(test.BaseAdminViewTests): self.assertNoFormErrors(res) self.assertRedirectsNoFollow(res, INDEX_URL) + @test.create_stubs({api.neutron: ('network_create', + 'subnet_create', + 'profile_list', + 'is_extension_supported', + 'subnetpool_list'), + api.keystone: ('tenant_list',)}) + def test_network_create_post_with_subnet(self, + test_with_profile=False): + tenants = self.tenants.list() + tenant_id = self.tenants.first().id + network = self.networks.first() + subnet = self.subnets.first() + params = {'name': network.name, + 'tenant_id': tenant_id, + 'admin_state_up': network.admin_state_up, + 'router:external': True, + 'shared': True, + 'provider:network_type': 'local', + 'with_subnet': True} + + api.keystone.tenant_list(IsA(http.HttpRequest))\ + .AndReturn([tenants, False]) + + if test_with_profile: + net_profiles = self.net_profiles.list() + net_profile_id = self.net_profiles.first().id + api.neutron.profile_list(IsA(http.HttpRequest), + 'network').AndReturn(net_profiles) + params['net_profile_id'] = net_profile_id + api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ + MultipleTimes().AndReturn(True) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'subnet_allocation').\ + MultipleTimes().AndReturn(True) + api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ + AndReturn(self.subnetpools.list()) + api.neutron.network_create(IsA(http.HttpRequest), **params)\ + .AndReturn(network) + self.mox.ReplayAll() + + form_data = {'tenant_id': tenant_id, + 'name': network.name, + 'admin_state': network.admin_state_up, + 'external': True, + 'shared': True, + 'network_type': 'local', + 'with_subnet': True} + if test_with_profile: + form_data['net_profile_id'] = net_profile_id + form_data.update(tests.form_data_subnet(subnet, allocation_pools=[])) + url = reverse('horizon:admin:networks:create') + res = self.client.post(url, form_data) + + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, INDEX_URL) + @test.update_settings( OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'}) def test_network_create_post_with_profile(self): @@ -439,7 +504,8 @@ class NetworkTests(test.BaseAdminViewTests): @test.create_stubs({api.neutron: ('network_create', 'profile_list', - 'is_extension_supported',), + 'is_extension_supported', + 'subnetpool_list'), api.keystone: ('tenant_list',)}) def test_network_create_post_network_exception(self, test_with_profile=False): @@ -454,7 +520,8 @@ class NetworkTests(test.BaseAdminViewTests): 'admin_state_up': network.admin_state_up, 'router:external': True, 'shared': False, - 'provider:network_type': 'local'} + 'provider:network_type': 'local', + 'with_subnet': False} if test_with_profile: net_profiles = self.net_profiles.list() net_profile_id = self.net_profiles.first().id @@ -463,6 +530,11 @@ class NetworkTests(test.BaseAdminViewTests): params['net_profile_id'] = net_profile_id api.neutron.is_extension_supported(IsA(http.HttpRequest), 'provider').\ MultipleTimes().AndReturn(True) + api.neutron.is_extension_supported(IsA(http.HttpRequest), + 'subnet_allocation').\ + MultipleTimes().AndReturn(True) + api.neutron.subnetpool_list(IsA(http.HttpRequest)).\ + AndReturn(self.subnetpools.list()) api.neutron.network_create(IsA(http.HttpRequest), **params).AndRaise(self.exceptions.neutron) self.mox.ReplayAll() @@ -593,7 +665,7 @@ class NetworkTests(test.BaseAdminViewTests): url = reverse('horizon:admin:networks:create') res = self.client.get(url) - self.assertTemplateUsed(res, 'admin/networks/create.html') + self.assertTemplateUsed(res, 'horizon/common/_workflow_base.html') self.assertContains( res, '', @@ -615,7 +687,7 @@ class NetworkTests(test.BaseAdminViewTests): url = reverse('horizon:admin:networks:create') res = self.client.get(url) - self.assertTemplateUsed(res, 'admin/networks/create.html') + self.assertTemplateUsed(res, 'horizon/common/_workflow_base.html') network_type = res.context['form'].fields['network_type'] self.assertListEqual(list(network_type.choices), [('local', 'Local'), ('flat', 'Flat'), diff --git a/openstack_dashboard/dashboards/admin/networks/views.py b/openstack_dashboard/dashboards/admin/networks/views.py index babbd5dacc..15898d8448 100644 --- a/openstack_dashboard/dashboards/admin/networks/views.py +++ b/openstack_dashboard/dashboards/admin/networks/views.py @@ -19,7 +19,6 @@ from django.core.urlresolvers import reverse_lazy from django.utils.translation import ugettext_lazy as _ from horizon import exceptions -from horizon import forms from horizon import tables from horizon import tabs from horizon.utils import memoized @@ -39,6 +38,9 @@ from openstack_dashboard.dashboards.admin.networks.subnets \ import tables as subnets_tables from openstack_dashboard.dashboards.admin.networks \ import tables as networks_tables +from openstack_dashboard.dashboards.admin.networks import workflows +from openstack_dashboard.dashboards.project.networks import views \ + as project_view class IndexView(tables.DataTableView): @@ -122,11 +124,8 @@ class IndexView(tables.DataTableView): return filters -class CreateView(forms.ModalFormView): - form_class = project_forms.CreateNetwork - template_name = 'admin/networks/create.html' - success_url = reverse_lazy('horizon:admin:networks:index') - page_title = _("Create Network") +class CreateView(project_view.CreateView): + workflow_class = workflows.CreateNetwork class UpdateView(user_views.UpdateView): diff --git a/openstack_dashboard/dashboards/admin/networks/workflows.py b/openstack_dashboard/dashboards/admin/networks/workflows.py new file mode 100644 index 0000000000..77a05a3aab --- /dev/null +++ b/openstack_dashboard/dashboards/admin/networks/workflows.py @@ -0,0 +1,87 @@ +# Copyright 2012 NEC Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from django.core.urlresolvers import reverse + +from openstack_dashboard.dashboards.admin.networks import forms \ + as networks_forms +from openstack_dashboard.dashboards.project.networks \ + import workflows as network_workflows + + +class CreateNetworkInfoAction(network_workflows.CreateNetworkInfoAction): + + def __init__(self, request, context, *args, **kwargs): + self.create_network_form = context.get('create_network_form') + self.base_fields = self.create_network_form.base_fields + + super(CreateNetworkInfoAction, self).__init__( + request, context, *args, **kwargs) + self.fields = self.create_network_form.fields + + def clean(self): + self.create_network_form.cleaned_data = super( + CreateNetworkInfoAction, self).clean() + self.create_network_form._changed_data = self.changed_data + self.create_network_form._errors = self.errors + return self.create_network_form.clean() + + class Meta(object): + name = network_workflows.CreateNetworkInfoAction.name + help_text = network_workflows.CreateNetworkInfoAction.help_text + + +class CreateNetworkInfo(network_workflows.CreateNetworkInfo): + action_class = CreateNetworkInfoAction + contributes = ("net_name", "admin_state", "net_profile_id", "with_subnet") + + def __init__(self, workflow): + self.contributes = tuple(workflow.create_network_form.fields.keys()) + super(CreateNetworkInfo, self).__init__(workflow) + + def prepare_action_context(self, request, context): + context = super(CreateNetworkInfo, self).prepare_action_context( + request, context) + context['create_network_form'] = self.workflow.create_network_form + return context + + +class CreateNetwork(network_workflows.CreateNetwork): + default_steps = (CreateNetworkInfo, + network_workflows.CreateSubnetInfo, + network_workflows.CreateSubnetDetail) + + def __init__(self, request=None, context_seed=None, entry_point=None, + *args, **kwargs): + self.create_network_form = networks_forms.CreateNetwork( + request, *args, **kwargs) + super(CreateNetwork, self).__init__( + request=request, + context_seed=context_seed, + entry_point=entry_point, + *args, **kwargs) + + def get_success_url(self): + return reverse("horizon:admin:networks:index") + + def get_failure_url(self): + return reverse("horizon:admin:networks:index") + + def _create_network(self, request, data): + network = self.create_network_form.handle(request, data) + # Replicate logic from parent CreateNetwork._create_network + if network: + self.context['net_id'] = network.id + self.context['net_name'] = network.name + return network