Flavors table and Flavor detail page

Flavors as nested resource to resource_class, the specific content of
flavor detail and flavor template detail pages are to be decided.

Implements: blueprint flavor-and-flavor-template-details

Change-Id: Ib2b967a1cc7129ec06a97a0bd1a4de6625186504
This commit is contained in:
Jiri Tomasek 2013-08-21 13:22:24 +02:00
parent 433eb2d4d9
commit 9ff082e5a7
10 changed files with 282 additions and 3 deletions

View File

@ -641,7 +641,7 @@ class ResourceClass(StringIdAPIResourceWrapper):
added_flavors = tuskarclient(self.request).flavors.list(self.id)
self._flavors = []
for f in added_flavors:
flavor_obj = Flavor(f)
flavor_obj = Flavor(f, self.request)
#flavor_obj.max_vms = f.max_vms
# FIXME just a mock of used instances, add real values
@ -899,6 +899,13 @@ class Flavor(StringIdAPIResourceWrapper):
"""
_attrs = ['id', 'name', 'max_vms']
@classmethod
def get(cls, request, resource_class_id, flavor_id):
flavor = cls(tuskarclient(request).flavors.get(resource_class_id,
flavor_id))
flavor.request = request
return flavor
@classmethod
def create(cls, request, **kwargs):
return cls(tuskarclient(request).flavors.create(

View File

@ -0,0 +1,35 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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.utils.translation import ugettext_lazy as _
from horizon import tabs
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "flavor_overview_tab"
template_name = ("infrastructure/resource_management/flavors/"
"_detail_overview.html")
preload = False
def get_context_data(self, request):
return {"flavor": self.tab_group.kwargs['flavor'],
'resource_class': self.tab_group.kwargs['resource_class']}
class FlavorDetailTabs(tabs.TabGroup):
slug = "flavor_detail_tabs"
tabs = (OverviewTab,)
sticky = True

View File

@ -0,0 +1,64 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 django import http
from mox import IsA
from tuskar_ui import api as tuskar
from tuskar_ui.test import helpers as test
class FlavorsTests(test.BaseAdminViewTests):
@test.create_stubs({tuskar.Flavor: ('get',),
tuskar.ResourceClass: ('get',)})
def test_detail_flavor(self):
flavor = self.tuskar_flavors.first()
resource_class = self.tuskar_resource_classes.first()
tuskar.ResourceClass.get(IsA(http.HttpRequest),
resource_class.id).AndReturn(resource_class)
tuskar.Flavor.get(IsA(http.HttpRequest),
resource_class.id,
flavor.id).AndReturn(flavor)
self.mox.ReplayAll()
url = reverse('horizon:infrastructure:resource_management'
':resource_classes:flavors:detail',
args=[resource_class.id, flavor.id])
res = self.client.get(url)
self.assertTemplateUsed(res, "infrastructure/resource_management/"
"flavors/detail.html")
@test.create_stubs({tuskar.Flavor: ('get',)})
def test_detail_flavor_exception(self):
flavor = self.tuskar_flavors.first()
resource_class = self.tuskar_resource_classes.first()
tuskar.Flavor.get(IsA(http.HttpRequest),
resource_class.id,
flavor.id).AndRaise(self.exceptions.tuskar)
self.mox.ReplayAll()
url = reverse('horizon:infrastructure:resource_management:'
'resource_classes:flavors:detail',
args=[resource_class.id, flavor.id])
res = self.client.get(url)
self.assertRedirectsNoFollow(
res, reverse('horizon:infrastructure:resource_management:index'))

View File

@ -0,0 +1,28 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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
from django.conf.urls.defaults import url
from tuskar_ui.infrastructure.resource_management.flavors.views \
import DetailView
VIEW_MOD = 'tuskar_ui.infrastructure.' \
'resource_management.flavors.views'
urlpatterns = patterns(VIEW_MOD,
url(r'^(?P<flavor_id>[^/]+)/$', DetailView.as_view(), name='detail')
)

View File

@ -0,0 +1,77 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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 django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from tuskar_ui import api as tuskar
from tuskar_ui.infrastructure. \
resource_management.flavors.tabs import FlavorDetailTabs
class DetailView(tabs.TabView):
tab_group_class = FlavorDetailTabs
template_name = ('infrastructure/resource_management/flavors/detail.html')
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
context["flavor"] = self.get_flavor_data()
context["resource_class"] = self.get_resource_class_data()
return context
def get_flavor_data(self):
if not hasattr(self, "_flavor"):
try:
flavor_id = self.kwargs['flavor_id']
resource_class_id = self.kwargs['resource_class_id']
flavor = tuskar.Flavor.get(self.request,
flavor_id,
resource_class_id)
except Exception:
redirect = reverse('horizon:infrastructure:'
'resource_management:index')
exceptions.handle(self.request,
_('Unable to retrieve details for '
'flavor "%s".') % flavor_id,
redirect=redirect)
self._flavor = flavor
return self._flavor
def get_resource_class_data(self):
if not hasattr(self, "_resource_class"):
try:
resource_class_id = self.kwargs['resource_class_id']
resource_class = tuskar.ResourceClass.get(self.request,
resource_class_id)
except Exception:
redirect = reverse('horizon:infrastructure:'
'resource_management:index')
exceptions.handle(self.request,
_('Unable to retrieve details for resource '
'class "%s".') % resource_class_id,
redirect=redirect)
self._resource_class = resource_class
return self._resource_class
def get_tabs(self, request, *args, **kwargs):
flavor = self.get_flavor_data()
resource_class = self.get_resource_class_data()
return self.tab_group_class(request,
flavor=flavor,
resource_class=resource_class,
**kwargs)

View File

@ -13,6 +13,7 @@
# under the License.
import logging
import re
from django.core import urlresolvers
from django.utils.translation import ugettext_lazy as _
@ -159,9 +160,17 @@ class UpdateFlavorsClass(tables.LinkAction):
class FlavorsTable(flavor_templates_tables.FlavorTemplatesTable):
def get_flavor_detail_link(datum):
# FIXME - horizon Column.get_link_url does not allow to access GET
# params
resource_class_id = re.findall("[0-9]+", datum.request.path)[-1]
return urlresolvers.reverse("horizon:infrastructure:"
"resource_management:resource_classes:"
"flavors:detail",
args=(resource_class_id, datum.id))
name = tuskar_ui.tables.Column('name',
link=("horizon:infrastructure:"
"resource_management:flavor_templates:detail"),
link=get_flavor_detail_link,
verbose_name=_('Flavor Name'))
max_vms = tuskar_ui.tables.Column("max_vms",
verbose_name=_("Max. VMs"))

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls.defaults import include
from django.conf.urls.defaults import patterns
from django.conf.urls.defaults import url
@ -29,6 +30,8 @@ from tuskar_ui.infrastructure. \
resource_management.resource_classes.views import UpdateRacksView
from tuskar_ui.infrastructure. \
resource_management.resource_classes.views import UpdateView
from tuskar_ui.infrastructure. \
resource_management.flavors import urls as flavor_urls
RESOURCE_CLASS = r'^(?P<resource_class_id>[^/]+)/%s$'
@ -51,4 +54,6 @@ urlpatterns = patterns(
name='update_flavors'),
url(RESOURCE_CLASS % 'rack_health.json', 'rack_health',
name='rack_health'),
url(r'^(?P<resource_class_id>[^/]+)/flavors/',
include(flavor_urls, namespace='flavors')),
)

View File

@ -0,0 +1,31 @@
{% load i18n sizeformat %}
{% load url from future %}
<div class="info row-fluid detail">
<div class="span4">
<h4>{% trans "About" %}</h4>
<hr class="header_rule">
<dl>
<dt>{% trans "Name" %}</dt>
<dd>{{ flavor.name|default:_("None") }}</dd>
<dt>{% trans "Resource Class" %}</dt>
<dd>{{ resource_class.name|default:_("None") }}</dd>
</dl>
</div>
<div class="span4">
<h4>{% trans "Specification" %}</h4>
<hr class="header_rule">
<dl>
<dt>{% trans "VCPU" %}</dt>
<dd>{{ flavor.cpu.value }}</dd>
<dt>{% trans "RAM" %}</dt>
<dd>{{ flavor.memory.value }} {{ flavor.memory.unit }}</dd>
<dt>{% trans "Root Disk" %}</dt>
<dd>{{ flavor.storage.value }} {{ flavor.storage.unit }}</dd>
<dt>{% trans "Ephemeral Disk" %}</dt>
<dd>{{ flavor.ephemeral_disk.value }} {{ flavor.ephemeral_disk.unit }}</dd>
<dt>{% trans "Swap Disk" %}</dt>
<dd>{{ flavor.swap_disk.value }} {{ flavor.swap_disk.unit }}</dd>
</dl>
</div>
</div>

View File

@ -0,0 +1,23 @@
{% extends 'infrastructure/base_detail.html' %}
{% load i18n %}
{% block title %}{% trans "Flavor Detail"%}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Flavor Detail") %}
{% endblock page_header %}
{% block actions %}
{% endblock %}
{% block breadcrumbs %}
<div class="breadcrumbs">
<a href="{% url 'horizon:infrastructure:resource_management:index' %}?tab=resource_management_tabs__resource_classes_tab" >{% trans 'Home' %}</a>
<span class="separator"></span>
<a href="{% url 'horizon:infrastructure:resource_management:index' %}?tab=resource_management_tabs__resource_classes_tab" >{% trans 'Classes'%}</a>
<span class="separator"></span>
<a href="{% url 'horizon:infrastructure:resource_management:resource_classes:detail' resource_class.id %}" >{{ resource_class.name }}</a>
<span class="separator"></span>
</div>
{% endblock breadcrumbs %}
{% block name %}{{ flavor.name }}{% endblock %}