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
|
||||
|
||||
|
||||
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):
|
||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
LOG.debug('novaclient connection created using token "%s" and url "%s"' %
|
||||
@ -206,6 +214,28 @@ def flavor_list(request):
|
||||
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):
|
||||
"""Fetches a list of all floating ips."""
|
||||
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):
|
||||
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.
|
||||
api.nova.flavor_delete(request, data['flavor_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
|
||||
# because if the delete fails the API will error out because
|
||||
# active flavors can't have the same name.
|
||||
new_flavor_id = uuid.uuid4()
|
||||
flavor = api.nova.flavor_create(request,
|
||||
data['name'],
|
||||
data['memory_mb'],
|
||||
data['vcpus'],
|
||||
data['disk_gb'],
|
||||
uuid.uuid4(),
|
||||
new_flavor_id,
|
||||
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']
|
||||
messages.success(request, msg)
|
||||
return flavor
|
||||
|
@ -32,6 +32,13 @@ class EditFlavor(tables.LinkAction):
|
||||
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):
|
||||
return _("%sMB") % flavor.ram
|
||||
|
||||
@ -51,4 +58,4 @@ class FlavorsTable(tables.DataTable):
|
||||
name = "flavors"
|
||||
verbose_name = _("Flavors")
|
||||
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):
|
||||
flavor = self.flavors.first()
|
||||
eph = getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral')
|
||||
extras = {}
|
||||
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_delete')
|
||||
self.mox.StubOutWithMock(api.nova, 'flavor_create')
|
||||
@ -52,6 +54,8 @@ class FlavorsTests(test.BaseAdminViewTests):
|
||||
|
||||
# POST
|
||||
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_create(IsA(http.HttpRequest),
|
||||
flavor.name,
|
||||
@ -62,11 +66,13 @@ class FlavorsTests(test.BaseAdminViewTests):
|
||||
ephemeral=eph).AndReturn(flavor)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
#get_test
|
||||
url = reverse('horizon:admin:flavors:edit', args=[flavor.id])
|
||||
resp = self.client.get(url)
|
||||
self.assertEqual(resp.status_code, 200)
|
||||
self.assertTemplateUsed(resp, "admin/flavors/edit.html")
|
||||
|
||||
#post test
|
||||
data = {'flavor_id': flavor.id,
|
||||
'name': flavor.name,
|
||||
'vcpus': flavor.vcpus + 1,
|
||||
@ -75,4 +81,4 @@ class FlavorsTests(test.BaseAdminViewTests):
|
||||
'eph_gb': eph}
|
||||
resp = self.client.post(url, data)
|
||||
self.assertRedirectsNoFollow(resp,
|
||||
reverse("horizon:admin:flavors:index"))
|
||||
reverse("horizon:admin:flavors:index"))
|
||||
|
@ -18,12 +18,13 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# 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 .extras import urls as extras_urls
|
||||
|
||||
urlpatterns = patterns('openstack_dashboard.dashboards.admin.flavors.views',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
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