Allow subnet creation from admin networks panel

- Changed create network modal to a wizard which includes
the tabs for subnet creation.
- Removed templates that are no longer needed.
- Fixed an issue on wizard that prevents the error message
to be cleared and displayed properly.

Change-Id: Ib2fb9c2e94810cd614b915bb515f2204a4df3be6
Closes-bug: #1618637
This commit is contained in:
Ying Zuo 2016-10-21 11:20:07 -07:00
parent f716d559ad
commit 3c3e70905d
7 changed files with 187 additions and 45 deletions

View File

@ -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(
'<span class="help-block">' +
error + '</span>');
});

View File

@ -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')

View File

@ -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 %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Create a new network for any project as you need."%}</p>
<p>{% 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."%}</p>
<p>{% trans "In addition, you can create an external network or a shared network by checking the corresponding checkbox."%}</p>
</div>
{% endblock %}

View File

@ -1,7 +0,0 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Network" %}{% endblock %}
{% block main %}
{% include "admin/networks/_create.html" %}
{% endblock %}

View File

@ -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,
'<input type="hidden" name="network_type" id="id_network_type" />',
@ -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'),

View File

@ -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):

View File

@ -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