Add volume group-specs-list support for admin panel
This commit allow admin to list/show cinder volume group-spec using horizon dashboard and user can perform the following table action : 1. group-spec-create 2. group-spec-edit 3. group-spec-delete Partially-Implements blueprint cinder-generic-volume-groups Change-Id: I7a24e21bbf86595bc7e1251d29caed7f7ff5dec8
This commit is contained in:
parent
8bc497a5e3
commit
4b523122b0
@ -152,6 +152,14 @@ class VolTypeExtraSpec(object):
|
|||||||
self.value = val
|
self.value = val
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpec(object):
|
||||||
|
def __init__(self, group_type_id, key, val):
|
||||||
|
self.group_type_id = group_type_id
|
||||||
|
self.id = key
|
||||||
|
self.key = key
|
||||||
|
self.value = val
|
||||||
|
|
||||||
|
|
||||||
class QosSpec(object):
|
class QosSpec(object):
|
||||||
def __init__(self, id, key, val):
|
def __init__(self, id, key, val):
|
||||||
self.id = id
|
self.id = id
|
||||||
@ -1154,16 +1162,28 @@ def group_type_delete(request, group_type_id):
|
|||||||
client.group_types.delete(group_type_id)
|
client.group_types.delete(group_type_id)
|
||||||
|
|
||||||
|
|
||||||
|
@profiler.trace
|
||||||
|
def group_type_spec_list(request, group_type_id, raw=False):
|
||||||
|
group_type = group_type_get(request, group_type_id)
|
||||||
|
specs = group_type._apiresource.get_keys()
|
||||||
|
if raw:
|
||||||
|
return specs
|
||||||
|
return [GroupTypeSpec(group_type_id, key, value) for
|
||||||
|
key, value in specs.items()]
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
@profiler.trace
|
||||||
def group_type_spec_set(request, group_type_id, metadata):
|
def group_type_spec_set(request, group_type_id, metadata):
|
||||||
client = _cinderclient_with_generic_groups(request)
|
group_type = group_type_get(request, group_type_id)
|
||||||
client.group_types.set_keys(metadata)
|
if not metadata:
|
||||||
|
return None
|
||||||
|
return group_type._apiresource.set_keys(metadata)
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
@profiler.trace
|
||||||
def group_type_spec_unset(request, group_type_id, keys):
|
def group_type_spec_unset(request, group_type_id, keys):
|
||||||
client = _cinderclient_with_generic_groups(request)
|
group_type = group_type_get(request, group_type_id)
|
||||||
client.group_types.unset_keys(keys)
|
return group_type._apiresource.unset_keys(keys)
|
||||||
|
|
||||||
|
|
||||||
@profiler.trace
|
@profiler.trace
|
||||||
|
@ -0,0 +1,81 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import messages
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
|
||||||
|
KEY_NAME_REGEX = re.compile(r"^[a-zA-Z0-9_.:-]+$", re.UNICODE)
|
||||||
|
KEY_ERROR_MESSAGES = {
|
||||||
|
'invalid': _('Key names can only contain alphanumeric characters, '
|
||||||
|
'underscores, periods, colons and hyphens')}
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSpec(forms.SelfHandlingForm):
|
||||||
|
key = forms.RegexField(max_length=255, label=_("Key"),
|
||||||
|
regex=KEY_NAME_REGEX,
|
||||||
|
error_messages=KEY_ERROR_MESSAGES)
|
||||||
|
value = forms.CharField(max_length=255, label=_("Value"))
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
group_type_id = self.initial['group_type_id']
|
||||||
|
error_msg = _('key with name "%s" already exists.Use Edit to '
|
||||||
|
'update the value, else create key with different '
|
||||||
|
'name.') % data['key']
|
||||||
|
try:
|
||||||
|
specs_list = api.cinder.group_type_spec_list(self.request,
|
||||||
|
group_type_id)
|
||||||
|
for spec in specs_list:
|
||||||
|
if spec.key.lower() == data['key'].lower():
|
||||||
|
raise forms.ValidationError(error_msg)
|
||||||
|
api.cinder.group_type_spec_set(request,
|
||||||
|
group_type_id,
|
||||||
|
{data['key']: data['value']})
|
||||||
|
|
||||||
|
msg = _('Created group type spec "%s".') % data['key']
|
||||||
|
messages.success(request, msg)
|
||||||
|
return True
|
||||||
|
except forms.ValidationError:
|
||||||
|
messages.error(request, error_msg)
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse("horizon:admin:group_types:index")
|
||||||
|
exceptions.handle(request,
|
||||||
|
_("Unable to create group type spec."),
|
||||||
|
redirect=redirect)
|
||||||
|
|
||||||
|
|
||||||
|
class EditSpec(forms.SelfHandlingForm):
|
||||||
|
value = forms.CharField(max_length=255, label=_("Value"))
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
key = self.initial['key']
|
||||||
|
group_type_id = self.initial['group_type_id']
|
||||||
|
try:
|
||||||
|
api.cinder.group_type_spec_set(request,
|
||||||
|
group_type_id,
|
||||||
|
{key: data['value']})
|
||||||
|
msg = _('Saved group spec "%s".') % key
|
||||||
|
messages.success(request, msg)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
redirect = reverse("horizon:admin:group_types:index")
|
||||||
|
exceptions.handle(request,
|
||||||
|
_("Unable to edit group type spec."),
|
||||||
|
redirect=redirect)
|
@ -0,0 +1,87 @@
|
|||||||
|
# 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.urls import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.utils.translation import ungettext_lazy
|
||||||
|
from six.moves.urllib import parse
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpecDelete(tables.DeleteAction):
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_present(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Delete Spec",
|
||||||
|
u"Delete Specs",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def action_past(count):
|
||||||
|
return ungettext_lazy(
|
||||||
|
u"Delete Spec",
|
||||||
|
u"Delete Specs",
|
||||||
|
count
|
||||||
|
)
|
||||||
|
|
||||||
|
def delete(self, request, obj_id):
|
||||||
|
key = parse.unquote(obj_id)
|
||||||
|
api.cinder.group_type_spec_unset(request,
|
||||||
|
self.table.kwargs['type_id'],
|
||||||
|
[key])
|
||||||
|
|
||||||
|
def get_success_url(self, request):
|
||||||
|
return reverse('horizon:admin:group_types:index')
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpecCreate(tables.LinkAction):
|
||||||
|
name = "create"
|
||||||
|
verbose_name = _("Create Spec")
|
||||||
|
url = "horizon:admin:group_types:specs:create"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "plus"
|
||||||
|
|
||||||
|
def get_link_url(self, group_type_spec=None):
|
||||||
|
return reverse(self.url, args=[self.table.kwargs['type_id']])
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpecEdit(tables.LinkAction):
|
||||||
|
name = "edit"
|
||||||
|
verbose_name = _("Edit Spec")
|
||||||
|
url = "horizon:admin:group_types:specs:edit"
|
||||||
|
classes = ("btn-edit", "ajax-modal")
|
||||||
|
|
||||||
|
def get_link_url(self, group_type_spec):
|
||||||
|
return reverse(self.url, args=[self.table.kwargs['type_id'],
|
||||||
|
group_type_spec.key])
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpecsTable(tables.DataTable):
|
||||||
|
key = tables.Column('key', verbose_name=_('Key'))
|
||||||
|
value = tables.Column('value', verbose_name=_('Value'))
|
||||||
|
|
||||||
|
class Meta(object):
|
||||||
|
name = "specs"
|
||||||
|
verbose_name = _("Group Type Specs")
|
||||||
|
table_actions = (GroupTypeSpecCreate, GroupTypeSpecDelete)
|
||||||
|
row_actions = (GroupTypeSpecEdit, GroupTypeSpecDelete)
|
||||||
|
|
||||||
|
def get_object_id(self, datum):
|
||||||
|
return parse.quote(datum.key)
|
||||||
|
|
||||||
|
def get_object_display(self, datum):
|
||||||
|
return datum.key
|
141
openstack_dashboard/dashboards/admin/group_types/specs/tests.py
Normal file
141
openstack_dashboard/dashboards/admin/group_types/specs/tests.py
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
# 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.urls import reverse
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
from openstack_dashboard.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpecTests(test.BaseAdminViewTests):
|
||||||
|
|
||||||
|
@test.create_mocks({api.cinder: ('group_type_spec_list',
|
||||||
|
'group_type_get')})
|
||||||
|
def test_list_specs_when_none_exists(self):
|
||||||
|
group_type = self.cinder_group_types.first()
|
||||||
|
specs = [api.cinder.GroupTypeSpec(group_type.id, 'k1', 'v1')]
|
||||||
|
|
||||||
|
self.mock_group_type_get.return_value = group_type
|
||||||
|
self.mock_group_type_spec_list.return_value = specs
|
||||||
|
url = reverse('horizon:admin:group_types:specs:index',
|
||||||
|
args=[group_type.id])
|
||||||
|
resp = self.client.get(url)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertTemplateUsed(resp,
|
||||||
|
"admin/group_types/specs/index.html")
|
||||||
|
self.mock_group_type_get.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id)
|
||||||
|
self.mock_group_type_spec_list.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id)
|
||||||
|
|
||||||
|
@test.create_mocks({api.cinder: ('group_type_spec_list',
|
||||||
|
'group_type_get')})
|
||||||
|
def test_specs_view_with_exception(self):
|
||||||
|
group_type = self.cinder_group_types.first()
|
||||||
|
|
||||||
|
self.mock_group_type_get.return_value = group_type
|
||||||
|
self.mock_group_type_spec_list.side_effect = self.exceptions.cinder
|
||||||
|
url = reverse('horizon:admin:group_types:specs:index',
|
||||||
|
args=[group_type.id])
|
||||||
|
resp = self.client.get(url)
|
||||||
|
self.assertEqual(len(resp.context['specs_table'].data), 0)
|
||||||
|
self.assertMessageCount(resp, error=1)
|
||||||
|
self.mock_group_type_get.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id)
|
||||||
|
self.mock_group_type_spec_list.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id)
|
||||||
|
|
||||||
|
@test.create_mocks({api.cinder: ('group_type_spec_list',
|
||||||
|
'group_type_spec_set', )})
|
||||||
|
def test_spec_create_post(self):
|
||||||
|
group_type = self.cinder_group_types.first()
|
||||||
|
create_url = reverse(
|
||||||
|
'horizon:admin:group_types:specs:create',
|
||||||
|
args=[group_type.id])
|
||||||
|
index_url = reverse(
|
||||||
|
'horizon:admin:group_types:index')
|
||||||
|
|
||||||
|
data = {'key': u'k1',
|
||||||
|
'value': u'v1'}
|
||||||
|
|
||||||
|
self.mock_group_type_spec_set.return_value = None
|
||||||
|
resp = self.client.post(create_url, data)
|
||||||
|
self.assertNoFormErrors(resp)
|
||||||
|
self.assertMessageCount(success=1)
|
||||||
|
self.assertRedirectsNoFollow(resp, index_url)
|
||||||
|
self.mock_group_type_spec_set.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(),
|
||||||
|
group_type.id,
|
||||||
|
{data['key']: data['value']})
|
||||||
|
|
||||||
|
@test.create_mocks({api.cinder: ('group_type_get', )})
|
||||||
|
def test_spec_create_get(self):
|
||||||
|
group_type = self.cinder_group_types.first()
|
||||||
|
create_url = reverse(
|
||||||
|
'horizon:admin:group_types:specs:create',
|
||||||
|
args=[group_type.id])
|
||||||
|
|
||||||
|
self.mock_group_type_get.return_value = group_type
|
||||||
|
resp = self.client.get(create_url)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertTemplateUsed(
|
||||||
|
resp, 'admin/group_types/specs/create.html')
|
||||||
|
self.mock_group_type_get.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id)
|
||||||
|
|
||||||
|
@test.create_mocks({api.cinder: ('group_type_spec_list',
|
||||||
|
'group_type_spec_set',)})
|
||||||
|
def test_spec_edit(self):
|
||||||
|
group_type = self.cinder_group_types.first()
|
||||||
|
key = 'foo'
|
||||||
|
edit_url = reverse('horizon:admin:group_types:specs:edit',
|
||||||
|
args=[group_type.id, key])
|
||||||
|
index_url = reverse('horizon:admin:group_types:index')
|
||||||
|
|
||||||
|
data = {'value': u'v1'}
|
||||||
|
specs = {key: data['value']}
|
||||||
|
|
||||||
|
self.mock_group_type_spec_list.return_value = specs
|
||||||
|
self.mock_group_type_spec_set.return_value = None
|
||||||
|
|
||||||
|
resp = self.client.post(edit_url, data)
|
||||||
|
self.assertNoFormErrors(resp)
|
||||||
|
self.assertMessageCount(success=1)
|
||||||
|
self.assertRedirectsNoFollow(resp, index_url)
|
||||||
|
|
||||||
|
self.mock_group_type_spec_list.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id, raw=True)
|
||||||
|
self.mock_group_type_spec_set.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id, specs)
|
||||||
|
|
||||||
|
@test.create_mocks({api.cinder: ('group_type_spec_list',
|
||||||
|
'group_type_spec_unset')})
|
||||||
|
def test_spec_delete(self):
|
||||||
|
group_type = self.cinder_group_types.first()
|
||||||
|
specs = [api.cinder.GroupTypeSpec(group_type.id, 'k1', 'v1')]
|
||||||
|
formData = {'action': 'specs__delete__k1'}
|
||||||
|
index_url = reverse('horizon:admin:group_types:specs:index',
|
||||||
|
args=[group_type.id])
|
||||||
|
|
||||||
|
self.mock_group_type_spec_list.return_value = specs
|
||||||
|
self.mock_group_type_spec_unset.return_value = group_type
|
||||||
|
|
||||||
|
res = self.client.post(index_url, formData)
|
||||||
|
|
||||||
|
redirect = reverse('horizon:admin:group_types:index')
|
||||||
|
self.assertNoFormErrors(res)
|
||||||
|
self.assertRedirectsNoFollow(res, redirect)
|
||||||
|
|
||||||
|
self.mock_group_type_spec_list.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id)
|
||||||
|
self.mock_group_type_spec_unset.assert_called_once_with(
|
||||||
|
test.IsHttpRequest(), group_type.id, ['k1'])
|
@ -0,0 +1,22 @@
|
|||||||
|
# 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.conf.urls import url
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.admin.group_types.specs \
|
||||||
|
import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||||
|
url(r'^create/$', views.CreateView.as_view(), name='create'),
|
||||||
|
url(r'^(?P<key>[^/]+)/edit/$', views.EditView.as_view(), name='edit'),
|
||||||
|
]
|
125
openstack_dashboard/dashboards/admin/group_types/specs/views.py
Normal file
125
openstack_dashboard/dashboards/admin/group_types/specs/views.py
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
# 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.urls import reverse
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
from openstack_dashboard.dashboards.admin.group_types.specs \
|
||||||
|
import forms as admin_forms
|
||||||
|
from openstack_dashboard.dashboards.admin.group_types.specs \
|
||||||
|
import tables as admin_tables
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpecMixin(object):
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(GroupTypeSpecMixin, self).get_context_data(**kwargs)
|
||||||
|
try:
|
||||||
|
context['group_type'] = api.cinder.group_type_get(
|
||||||
|
self.request, self.kwargs['type_id'])
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_("Unable to retrieve group type details."))
|
||||||
|
if 'key' in self.kwargs:
|
||||||
|
context['key'] = self.kwargs['key']
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(GroupTypeSpecMixin, forms.ModalFormMixin, tables.DataTableView):
|
||||||
|
table_class = admin_tables.GroupTypeSpecsTable
|
||||||
|
template_name = 'admin/group_types/specs/index.html'
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
try:
|
||||||
|
group_type_id = self.kwargs['type_id']
|
||||||
|
specs_list = api.cinder.group_type_spec_list(self.request,
|
||||||
|
group_type_id)
|
||||||
|
specs_list.sort(key=lambda es: (es.key,))
|
||||||
|
except Exception:
|
||||||
|
specs_list = []
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to retrieve group type spec list.'))
|
||||||
|
return specs_list
|
||||||
|
|
||||||
|
|
||||||
|
class CreateView(GroupTypeSpecMixin, forms.ModalFormView):
|
||||||
|
form_class = admin_forms.CreateSpec
|
||||||
|
form_id = "group_type_spec_create_form"
|
||||||
|
modal_header = _("Create Group Type Spec")
|
||||||
|
modal_id = "group_type_spec_create_modal"
|
||||||
|
submit_label = _("Create")
|
||||||
|
submit_url = "horizon:admin:group_types:specs:create"
|
||||||
|
template_name = 'admin/group_types/specs/create.html'
|
||||||
|
success_url = 'horizon:admin:group_types:index'
|
||||||
|
cancel_url = reverse_lazy('horizon:admin:group_types:index')
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
return {'group_type_id': self.kwargs['type_id']}
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(self.success_url)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CreateView, self).get_context_data(**kwargs)
|
||||||
|
args = (self.kwargs['type_id'],)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class EditView(GroupTypeSpecMixin, forms.ModalFormView):
|
||||||
|
form_class = admin_forms.EditSpec
|
||||||
|
form_id = "group_type_spec_edit_form"
|
||||||
|
modal_header = _('Edit Group Type Spec Value: %s')
|
||||||
|
modal_id = "group_type_spec_edit_modal"
|
||||||
|
submit_label = _("Save")
|
||||||
|
submit_url = "horizon:admin:group_types:specs:edit"
|
||||||
|
template_name = 'admin/group_types/specs/edit.html'
|
||||||
|
success_url = 'horizon:admin:group_types:index'
|
||||||
|
cancel_url = reverse_lazy('horizon:admin:group_types:index')
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return reverse(self.success_url)
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
group_type_id = self.kwargs['type_id']
|
||||||
|
key = self.kwargs['key']
|
||||||
|
try:
|
||||||
|
group_specs = api.cinder.group_type_spec_list(self.request,
|
||||||
|
group_type_id,
|
||||||
|
raw=True)
|
||||||
|
except Exception:
|
||||||
|
group_specs = {}
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to retrieve group type spec '
|
||||||
|
'details.'))
|
||||||
|
return {'group_type_id': group_type_id,
|
||||||
|
'key': key,
|
||||||
|
'value': group_specs.get(key, '')}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(EditView, self).get_context_data(**kwargs)
|
||||||
|
args = (self.kwargs['type_id'], self.kwargs['key'],)
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=args)
|
||||||
|
context['modal_header'] = self.modal_header % self.kwargs['key']
|
||||||
|
return context
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
context = super(EditView, self).get_context_data()
|
||||||
|
context = self._populate_context(context)
|
||||||
|
context['form'] = form
|
||||||
|
context['modal_header'] = self.modal_header % self.kwargs['key']
|
||||||
|
return self.render_to_response(context)
|
@ -41,6 +41,15 @@ class EditGroupType(tables.LinkAction):
|
|||||||
policy_rules = (("volume", "group:group_types_manage"),)
|
policy_rules = (("volume", "group:group_types_manage"),)
|
||||||
|
|
||||||
|
|
||||||
|
class GroupTypeSpecs(tables.LinkAction):
|
||||||
|
name = "specs"
|
||||||
|
verbose_name = _("View Specs")
|
||||||
|
url = "horizon:admin:group_types:specs:index"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "pencil"
|
||||||
|
policy_rules = (("volume", "group:group_types_manage"),)
|
||||||
|
|
||||||
|
|
||||||
class GroupTypesFilterAction(tables.FilterAction):
|
class GroupTypesFilterAction(tables.FilterAction):
|
||||||
|
|
||||||
def filter(self, table, group_types, filter_string):
|
def filter(self, table, group_types, filter_string):
|
||||||
@ -118,6 +127,7 @@ class GroupTypesTable(tables.DataTable):
|
|||||||
DeleteGroupType,
|
DeleteGroupType,
|
||||||
)
|
)
|
||||||
row_actions = (
|
row_actions = (
|
||||||
|
GroupTypeSpecs,
|
||||||
EditGroupType,
|
EditGroupType,
|
||||||
DeleteGroupType
|
DeleteGroupType
|
||||||
)
|
)
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>{% trans 'Create a new "group spec" key-value pair for a group type.' %}</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,7 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>{% blocktrans with key=key %}Update the "group spec" value for "{{ key }}"{% endblocktrans %}</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,13 @@
|
|||||||
|
{% extends "horizon/common/_modal.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_id %}group_type_specs_modal{% endblock %}
|
||||||
|
{% block modal-header %}{% trans "Group Type Specs" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
{{ table.render }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<a href="{% url 'horizon:admin:group_types:index' %}" class="btn btn-default cancel">{% trans "Close" %}</a>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Create Group Type Spec" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
<h2>
|
||||||
|
{% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }} {% endblocktrans %}
|
||||||
|
</h2>
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "admin/group_types/specs/_create.html" %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Edit Group Type Spec" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
<h2>
|
||||||
|
{% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }} {% endblocktrans %}
|
||||||
|
</h2>
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "admin/group_types/specs/_edit.html" %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Group Type Specs" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
<h2>{% blocktrans with group_type_name=group_type.name %}Group Type: {{ group_type_name }}{% endblocktrans %}</h2>
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "admin/group_types/specs/_index.html" %}
|
||||||
|
{% endblock %}
|
@ -10,8 +10,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
from django.conf.urls import include
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.admin.group_types.specs \
|
||||||
|
import urls as specs_urls
|
||||||
from openstack_dashboard.dashboards.admin.group_types \
|
from openstack_dashboard.dashboards.admin.group_types \
|
||||||
import views
|
import views
|
||||||
|
|
||||||
@ -23,4 +26,6 @@ urlpatterns = [
|
|||||||
url(r'^(?P<type_id>[^/]+)/update_type/$',
|
url(r'^(?P<type_id>[^/]+)/update_type/$',
|
||||||
views.EditGroupTypeView.as_view(),
|
views.EditGroupTypeView.as_view(),
|
||||||
name='update_type'),
|
name='update_type'),
|
||||||
|
url(r'^(?P<type_id>[^/]+)/specs/',
|
||||||
|
include((specs_urls, 'specs'))),
|
||||||
]
|
]
|
||||||
|
@ -3,4 +3,7 @@ features:
|
|||||||
- |
|
- |
|
||||||
[:blueprint:`cinder-generic-volume-groups`]
|
[:blueprint:`cinder-generic-volume-groups`]
|
||||||
Cinder generic groups is now supported for admin panel.
|
Cinder generic groups is now supported for admin panel.
|
||||||
Admin is now able to view all groups for differenet users.
|
Admin is now able to view all groups and group snapshots
|
||||||
|
for differenet users.
|
||||||
|
Also group-type and group-type-spec support added to admin panel.
|
||||||
|
Admin is able to create group-type and group-type-spec now.
|
||||||
|
Loading…
Reference in New Issue
Block a user