Flavor Extra Specs support.
Special thanks: * Preserves extra specs on flavor edit (Tihomir Trifonov) * Displays flavor name on extra specs pages (Vinay Bannai) * Extras specs table close (Don Dugger & Gabriel). * Final cleanup (Gabriel Hurley) Change-Id: I6acb1176e5c0ca6987abc758fc45335870c55d57
This commit is contained in:
parent
c61c5e28cc
commit
82c19aee05
10
horizon/templates/horizon/common/_modal.html
Normal file
10
horizon/templates/horizon/common/_modal.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<div id="{% block modal_id %}{% endblock %}" class="{% block modal_class %}{% if hide %}modal hide{% else %}static_page{% endif %}{% endblock %}">
|
||||||
|
<div class="modal-header">
|
||||||
|
{% if hide %}<a href="#" class="close" data-dismiss="modal">×</a>{% endif %}
|
||||||
|
<h3>{% block modal-header %}{% endblock %}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body clearfix">
|
||||||
|
{% block modal-body %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">{% block modal-footer %}{% endblock %}</div>
|
||||||
|
</div>
|
@ -167,6 +167,14 @@ class SecurityGroupRule(APIResourceWrapper):
|
|||||||
return _('ALLOW %(from)s:%(to)s from %(cidr)s') % vals
|
return _('ALLOW %(from)s:%(to)s from %(cidr)s') % vals
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorExtraSpec(object):
|
||||||
|
def __init__(self, flavor_id, key, val):
|
||||||
|
self.flavor_id = flavor_id
|
||||||
|
self.id = key
|
||||||
|
self.key = key
|
||||||
|
self.value = val
|
||||||
|
|
||||||
|
|
||||||
def novaclient(request):
|
def novaclient(request):
|
||||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||||
LOG.debug('novaclient connection created using token "%s" and url "%s"' %
|
LOG.debug('novaclient connection created using token "%s" and url "%s"' %
|
||||||
@ -206,6 +214,28 @@ def flavor_list(request):
|
|||||||
return novaclient(request).flavors.list()
|
return novaclient(request).flavors.list()
|
||||||
|
|
||||||
|
|
||||||
|
def flavor_get_extras(request, flavor_id, raw=False):
|
||||||
|
"""Get flavor extra specs."""
|
||||||
|
flavor = novaclient(request).flavors.get(flavor_id)
|
||||||
|
extras = flavor.get_keys()
|
||||||
|
if raw:
|
||||||
|
return extras
|
||||||
|
return [FlavorExtraSpec(flavor_id, key, value) for
|
||||||
|
key, value in extras.items()]
|
||||||
|
|
||||||
|
|
||||||
|
def flavor_extra_delete(request, flavor_id, keys):
|
||||||
|
"""Unset the flavor extra spec keys."""
|
||||||
|
flavor = novaclient(request).flavors.get(flavor_id)
|
||||||
|
return flavor.unset_keys(keys)
|
||||||
|
|
||||||
|
|
||||||
|
def flavor_extra_set(request, flavor_id, metadata):
|
||||||
|
"""Set the flavor extra spec keys."""
|
||||||
|
flavor = novaclient(request).flavors.get(flavor_id)
|
||||||
|
return flavor.set_keys(metadata)
|
||||||
|
|
||||||
|
|
||||||
def tenant_floating_ip_list(request):
|
def tenant_floating_ip_list(request):
|
||||||
"""Fetches a list of all floating ips."""
|
"""Fetches a list of all floating ips."""
|
||||||
return novaclient(request).floating_ips.list()
|
return novaclient(request).floating_ips.list()
|
||||||
|
66
openstack_dashboard/dashboards/admin/flavors/extras/forms.py
Normal file
66
openstack_dashboard/dashboards/admin/flavors/extras/forms.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Intel, Inc.
|
||||||
|
#
|
||||||
|
# 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 logging
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
from horizon import messages
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class CreateExtraSpec(forms.SelfHandlingForm):
|
||||||
|
key = forms.CharField(max_length="25", label=_("Key"))
|
||||||
|
value = forms.CharField(max_length="25", label=_("Value"))
|
||||||
|
flavor_id = forms.IntegerField(widget=forms.widgets.HiddenInput)
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
try:
|
||||||
|
api.nova.flavor_extra_set(request,
|
||||||
|
data['flavor_id'],
|
||||||
|
{data['key']: data['value']})
|
||||||
|
msg = _('Created extra spec "%s".') % data['key']
|
||||||
|
messages.success(request, msg)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
exceptions.handle(request,
|
||||||
|
_("Unable to create flavor extra spec."))
|
||||||
|
|
||||||
|
|
||||||
|
class EditExtraSpec(forms.SelfHandlingForm):
|
||||||
|
key = forms.CharField(max_length="25", label=_("Key"))
|
||||||
|
value = forms.CharField(max_length="25", label=_("Value"))
|
||||||
|
flavor_id = forms.IntegerField(widget=forms.widgets.HiddenInput)
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
flavor_id = data['flavor_id']
|
||||||
|
try:
|
||||||
|
api.nova.flavor_extra_set(request,
|
||||||
|
flavor_id,
|
||||||
|
{data['key']: data['value']})
|
||||||
|
msg = _('Saved extra spec "%s".') % data['key']
|
||||||
|
messages.success(request, msg)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
exceptions.handle(request, _("Unable to edit extra spec."))
|
@ -0,0 +1,75 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright (c) 2012 Intel, Inc.
|
||||||
|
#
|
||||||
|
# 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 logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraSpecDelete(tables.DeleteAction):
|
||||||
|
data_type_singular = _("ExtraSpec")
|
||||||
|
data_type_plural = _("ExtraSpecs")
|
||||||
|
|
||||||
|
def delete(self, request, obj_ids):
|
||||||
|
flavor = api.nova.flavor_get(request, self.table.kwargs['id'])
|
||||||
|
flavor.unset_keys([obj_ids])
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraSpecCreate(tables.LinkAction):
|
||||||
|
name = "create"
|
||||||
|
verbose_name = _("Create")
|
||||||
|
url = "horizon:admin:flavors:extras:create"
|
||||||
|
classes = ("btn-create", "ajax-modal")
|
||||||
|
|
||||||
|
def get_link_url(self, extra_spec=None):
|
||||||
|
return reverse(self.url, args=[self.table.kwargs['id']])
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraSpecEdit(tables.LinkAction):
|
||||||
|
name = "edit"
|
||||||
|
verbose_name = _("Edit")
|
||||||
|
url = "horizon:admin:flavors:extras:edit"
|
||||||
|
classes = ("btn-edit", "ajax-modal")
|
||||||
|
|
||||||
|
def get_link_url(self, extra_spec):
|
||||||
|
return reverse(self.url, args=[self.table.kwargs['id'],
|
||||||
|
extra_spec.key])
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraSpecsTable(tables.DataTable):
|
||||||
|
key = tables.Column('key', verbose_name=_('Key'))
|
||||||
|
value = tables.Column('value', verbose_name=_('Value'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
name = "extras"
|
||||||
|
verbose_name = _("Extra Specs")
|
||||||
|
table_actions = (ExtraSpecCreate, ExtraSpecDelete)
|
||||||
|
row_actions = (ExtraSpecEdit, ExtraSpecDelete)
|
||||||
|
|
||||||
|
def get_object_id(self, datum):
|
||||||
|
return datum.key
|
||||||
|
|
||||||
|
def get_object_display(self, datum):
|
||||||
|
return datum.key
|
65
openstack_dashboard/dashboards/admin/flavors/extras/tests.py
Normal file
65
openstack_dashboard/dashboards/admin/flavors/extras/tests.py
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
from django import http
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
|
||||||
|
from mox import IsA
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
from openstack_dashboard.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
|
class FlavorExtrasTests(test.BaseAdminViewTests):
|
||||||
|
|
||||||
|
def test_list_extras_when_none_exists(self):
|
||||||
|
flavor = self.flavors.first()
|
||||||
|
extras = [api.FlavorExtraSpec(flavor.id, 'k1', 'v1')]
|
||||||
|
|
||||||
|
self.mox.StubOutWithMock(api.nova, 'flavor_get')
|
||||||
|
self.mox.StubOutWithMock(api.nova, 'flavor_get_extras')
|
||||||
|
|
||||||
|
# GET -- to determine correctness of output
|
||||||
|
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id).AndReturn(flavor)
|
||||||
|
api.nova.flavor_get_extras(IsA(http.HttpRequest),
|
||||||
|
flavor.id).AndReturn(extras)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
url = reverse('horizon:admin:flavors:extras:index', args=[flavor.id])
|
||||||
|
resp = self.client.get(url)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertTemplateUsed(resp, "admin/flavors/extras/index.html")
|
||||||
|
|
||||||
|
def test_extra_create_post(self):
|
||||||
|
flavor = self.flavors.first()
|
||||||
|
create_url = reverse('horizon:admin:flavors:extras:create',
|
||||||
|
args=[flavor.id])
|
||||||
|
index_url = reverse('horizon:admin:flavors:extras:index',
|
||||||
|
args=[flavor.id])
|
||||||
|
|
||||||
|
self.mox.StubOutWithMock(api.nova, 'flavor_extra_set')
|
||||||
|
|
||||||
|
# GET to display the flavor_name
|
||||||
|
api.nova.flavor_extra_set(IsA(http.HttpRequest),
|
||||||
|
int(flavor.id),
|
||||||
|
{'k1': 'v1'})
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
data = {'flavor_id': flavor.id,
|
||||||
|
'key': 'k1',
|
||||||
|
'value': 'v1'}
|
||||||
|
resp = self.client.post(create_url, data)
|
||||||
|
self.assertNoFormErrors(resp)
|
||||||
|
self.assertMessageCount(success=1)
|
||||||
|
self.assertRedirectsNoFollow(resp, index_url)
|
||||||
|
|
||||||
|
def test_extra_create_get(self):
|
||||||
|
flavor = self.flavors.first()
|
||||||
|
create_url = reverse('horizon:admin:flavors:extras:create',
|
||||||
|
args=[flavor.id])
|
||||||
|
|
||||||
|
self.mox.StubOutWithMock(api.nova, 'flavor_get')
|
||||||
|
|
||||||
|
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id).AndReturn(flavor)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
resp = self.client.get(create_url)
|
||||||
|
self.assertEqual(resp.status_code, 200)
|
||||||
|
self.assertTemplateUsed(resp,
|
||||||
|
'admin/flavors/extras/create.html')
|
29
openstack_dashboard/dashboards/admin/flavors/extras/urls.py
Normal file
29
openstack_dashboard/dashboards/admin/flavors/extras/urls.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# 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.defaults import patterns, url
|
||||||
|
|
||||||
|
from .views import IndexView, EditView, CreateView
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^$', IndexView.as_view(), name='index'),
|
||||||
|
url(r'^create/$', CreateView.as_view(), name='create'),
|
||||||
|
url(r'^(?P<key>[^/]+)/edit/$', EditView.as_view(), name='edit')
|
||||||
|
)
|
93
openstack_dashboard/dashboards/admin/flavors/extras/views.py
Normal file
93
openstack_dashboard/dashboards/admin/flavors/extras/views.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright (c) 2012 Intel, Inc.
|
||||||
|
#
|
||||||
|
# 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 logging
|
||||||
|
|
||||||
|
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 .tables import ExtraSpecsTable
|
||||||
|
from .forms import CreateExtraSpec, EditExtraSpec
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class ExtraSpecMixin(object):
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ExtraSpecMixin, self).get_context_data(**kwargs)
|
||||||
|
try:
|
||||||
|
context['flavor'] = api.nova.flavor_get(self.request,
|
||||||
|
self.kwargs['id'])
|
||||||
|
except:
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_("Unable to retrieve flavor data."))
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(ExtraSpecMixin, forms.ModalFormMixin, tables.DataTableView):
|
||||||
|
table_class = ExtraSpecsTable
|
||||||
|
template_name = 'admin/flavors/extras/index.html'
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
try:
|
||||||
|
flavor_id = self.kwargs['id']
|
||||||
|
extras_list = api.nova.flavor_get_extras(self.request, flavor_id)
|
||||||
|
extras_list.sort(key=lambda es: (es.key,))
|
||||||
|
except:
|
||||||
|
extras_list = []
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_('Unable to retrieve extra spec list.'))
|
||||||
|
return extras_list
|
||||||
|
|
||||||
|
|
||||||
|
class CreateView(ExtraSpecMixin, forms.ModalFormView):
|
||||||
|
form_class = CreateExtraSpec
|
||||||
|
template_name = 'admin/flavors/extras/create.html'
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
return {'flavor_id': self.kwargs['id']}
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
return "/admin/flavors/%s/extras/" % (self.kwargs['id'])
|
||||||
|
|
||||||
|
|
||||||
|
class EditView(ExtraSpecMixin, forms.ModalFormView):
|
||||||
|
form_class = EditExtraSpec
|
||||||
|
template_name = 'admin/flavors/extras/edit.html'
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
flavor_id = self.kwargs['id']
|
||||||
|
key = self.kwargs['key']
|
||||||
|
try:
|
||||||
|
extra_specs = api.nova.flavor_get_extras(self.request,
|
||||||
|
flavor_id,
|
||||||
|
raw=True)
|
||||||
|
except:
|
||||||
|
extra_specs = {}
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_("Unable to retrieve flavor extra spec data."))
|
||||||
|
return {'flavor_id': flavor_id,
|
||||||
|
'key': key,
|
||||||
|
'value': extra_specs.get(key, '')}
|
@ -61,19 +61,26 @@ class EditFlavor(CreateFlavor):
|
|||||||
|
|
||||||
def handle(self, request, data):
|
def handle(self, request, data):
|
||||||
try:
|
try:
|
||||||
|
flavor_id = data['flavor_id']
|
||||||
|
# grab any existing extra specs, because flavor edit currently
|
||||||
|
# implemented as a delete followed by a create
|
||||||
|
extras_dict = api.nova.flavor_get_extras(self.request, flavor_id)
|
||||||
# First mark the existing flavor as deleted.
|
# First mark the existing flavor as deleted.
|
||||||
api.nova.flavor_delete(request, data['flavor_id'])
|
api.nova.flavor_delete(request, data['flavor_id'])
|
||||||
# Then create a new flavor with the same name but a new ID.
|
# Then create a new flavor with the same name but a new ID.
|
||||||
# This is in the same try/except block as the delete call
|
# This is in the same try/except block as the delete call
|
||||||
# because if the delete fails the API will error out because
|
# because if the delete fails the API will error out because
|
||||||
# active flavors can't have the same name.
|
# active flavors can't have the same name.
|
||||||
|
new_flavor_id = uuid.uuid4()
|
||||||
flavor = api.nova.flavor_create(request,
|
flavor = api.nova.flavor_create(request,
|
||||||
data['name'],
|
data['name'],
|
||||||
data['memory_mb'],
|
data['memory_mb'],
|
||||||
data['vcpus'],
|
data['vcpus'],
|
||||||
data['disk_gb'],
|
data['disk_gb'],
|
||||||
uuid.uuid4(),
|
new_flavor_id,
|
||||||
ephemeral=data['eph_gb'])
|
ephemeral=data['eph_gb'])
|
||||||
|
if (len(extras_dict) > 0):
|
||||||
|
api.nova.flavor_extra_set(request, new_flavor_id, extras_dict)
|
||||||
msg = _('Updated flavor "%s".') % data['name']
|
msg = _('Updated flavor "%s".') % data['name']
|
||||||
messages.success(request, msg)
|
messages.success(request, msg)
|
||||||
return flavor
|
return flavor
|
||||||
|
@ -32,6 +32,13 @@ class EditFlavor(tables.LinkAction):
|
|||||||
classes = ("ajax-modal", "btn-edit")
|
classes = ("ajax-modal", "btn-edit")
|
||||||
|
|
||||||
|
|
||||||
|
class ViewFlavorExtras(tables.LinkAction):
|
||||||
|
name = "extras"
|
||||||
|
verbose_name = _("View Extra Specs")
|
||||||
|
url = "horizon:admin:flavors:extras:index"
|
||||||
|
classes = ("btn-edit",)
|
||||||
|
|
||||||
|
|
||||||
def get_size(flavor):
|
def get_size(flavor):
|
||||||
return _("%sMB") % flavor.ram
|
return _("%sMB") % flavor.ram
|
||||||
|
|
||||||
@ -51,4 +58,4 @@ class FlavorsTable(tables.DataTable):
|
|||||||
name = "flavors"
|
name = "flavors"
|
||||||
verbose_name = _("Flavors")
|
verbose_name = _("Flavors")
|
||||||
table_actions = (CreateFlavor, DeleteFlavor)
|
table_actions = (CreateFlavor, DeleteFlavor)
|
||||||
row_actions = (EditFlavor, DeleteFlavor)
|
row_actions = (EditFlavor, ViewFlavorExtras, DeleteFlavor)
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block form_id %}extra_spec_create_form{% endblock %}
|
||||||
|
{% block form_action %}{% url horizon:admin:flavors:extras:create flavor.id %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block modal_id %}extra_spec_create_modal{% endblock %}
|
||||||
|
{% block modal-header %}{% trans "Create Flavor Extra Spec" %}{% 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 "extra spec" key-value pair for a flavor.' %}</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create" %}" />
|
||||||
|
<a href="{% url horizon:admin:flavors:extras:index flavor.id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block form_id %}extra_spec_edit_form{% endblock %}
|
||||||
|
{% block form_action %}{% url horizon:admin:flavors:extras:create flavor.id %}{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block modal_id %}extra_spec_edit_modal{% endblock %}
|
||||||
|
{% block modal-header %}{% trans "Edit Flavor Extra Spec" %}{% 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 'Update an "extra spec" key-value pair for a flavor.' %}</p>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save" %}" />
|
||||||
|
<a href="{% url horizon:admin:flavors:extras:index flavor.id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,14 @@
|
|||||||
|
{% extends "horizon/common/_modal.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal_id %}extra_specs_modal{% endblock %}
|
||||||
|
{% block modal-header %}{% trans "Flavor Extra Specs" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-body %}
|
||||||
|
{{ table.render }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block modal-footer %}
|
||||||
|
<a href="{% url horizon:admin:flavors:index %}" class="btn secondary cancel close">{% trans "Close" %}</a>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Create Flavor Extra Spec" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
<h2>{% trans "Flavor" %}: {{flavor.name}} </h2>
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "admin/flavors/extras/_create.html" %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Edit Flavor Extra Spec" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
<h2>{% trans "Flavor" %}: {{flavor.name}} </h2>
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "admin/flavors/extras/_edit.html" %}
|
||||||
|
{% endblock %}
|
@ -0,0 +1,12 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Flavor Extra Specs" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
<h2>{% trans "Flavor" %}: {{flavor.name}} </h2>
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include "admin/flavors/extras/_index.html" %}
|
||||||
|
{% endblock %}
|
@ -42,7 +42,9 @@ class FlavorsTests(test.BaseAdminViewTests):
|
|||||||
def test_edit_flavor(self):
|
def test_edit_flavor(self):
|
||||||
flavor = self.flavors.first()
|
flavor = self.flavors.first()
|
||||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||||
|
extras = {}
|
||||||
self.mox.StubOutWithMock(api.nova, 'flavor_list')
|
self.mox.StubOutWithMock(api.nova, 'flavor_list')
|
||||||
|
self.mox.StubOutWithMock(api.nova, 'flavor_get_extras')
|
||||||
self.mox.StubOutWithMock(api.nova, 'flavor_get')
|
self.mox.StubOutWithMock(api.nova, 'flavor_get')
|
||||||
self.mox.StubOutWithMock(api.nova, 'flavor_delete')
|
self.mox.StubOutWithMock(api.nova, 'flavor_delete')
|
||||||
self.mox.StubOutWithMock(api.nova, 'flavor_create')
|
self.mox.StubOutWithMock(api.nova, 'flavor_create')
|
||||||
@ -52,6 +54,8 @@ class FlavorsTests(test.BaseAdminViewTests):
|
|||||||
|
|
||||||
# POST
|
# POST
|
||||||
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id).AndReturn(flavor)
|
api.nova.flavor_get(IsA(http.HttpRequest), flavor.id).AndReturn(flavor)
|
||||||
|
api.nova.flavor_get_extras(IsA(http.HttpRequest), int(flavor.id))\
|
||||||
|
.AndReturn(extras)
|
||||||
api.nova.flavor_delete(IsA(http.HttpRequest), int(flavor.id))
|
api.nova.flavor_delete(IsA(http.HttpRequest), int(flavor.id))
|
||||||
api.nova.flavor_create(IsA(http.HttpRequest),
|
api.nova.flavor_create(IsA(http.HttpRequest),
|
||||||
flavor.name,
|
flavor.name,
|
||||||
@ -62,11 +66,13 @@ class FlavorsTests(test.BaseAdminViewTests):
|
|||||||
ephemeral=eph).AndReturn(flavor)
|
ephemeral=eph).AndReturn(flavor)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
#get_test
|
||||||
url = reverse('horizon:admin:flavors:edit', args=[flavor.id])
|
url = reverse('horizon:admin:flavors:edit', args=[flavor.id])
|
||||||
resp = self.client.get(url)
|
resp = self.client.get(url)
|
||||||
self.assertEqual(resp.status_code, 200)
|
self.assertEqual(resp.status_code, 200)
|
||||||
self.assertTemplateUsed(resp, "admin/flavors/edit.html")
|
self.assertTemplateUsed(resp, "admin/flavors/edit.html")
|
||||||
|
|
||||||
|
#post test
|
||||||
data = {'flavor_id': flavor.id,
|
data = {'flavor_id': flavor.id,
|
||||||
'name': flavor.name,
|
'name': flavor.name,
|
||||||
'vcpus': flavor.vcpus + 1,
|
'vcpus': flavor.vcpus + 1,
|
||||||
|
@ -18,12 +18,13 @@
|
|||||||
# 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.defaults import patterns, url
|
from django.conf.urls.defaults import patterns, url, include
|
||||||
from .views import IndexView, CreateView, EditView
|
from .views import IndexView, CreateView, EditView
|
||||||
|
from .extras import urls as extras_urls
|
||||||
|
|
||||||
urlpatterns = patterns('openstack_dashboard.dashboards.admin.flavors.views',
|
urlpatterns = patterns('openstack_dashboard.dashboards.admin.flavors.views',
|
||||||
url(r'^$', IndexView.as_view(), name='index'),
|
url(r'^$', IndexView.as_view(), name='index'),
|
||||||
url(r'^create/$', CreateView.as_view(), name='create'),
|
url(r'^create/$', CreateView.as_view(), name='create'),
|
||||||
url(r'^(?P<id>[^/]+)/edit/$', EditView.as_view(), name='edit')
|
url(r'^(?P<id>[^/]+)/edit/$', EditView.as_view(), name='edit'),
|
||||||
|
url(r'^(?P<id>[^/]+)/extras/', include(extras_urls, namespace='extras')),
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user