注:OpenStack Horizon项目与其它项目有所不同,它主要提供一套工具,我们可以自己定制开发我们想要的dashboard(控制面板)。
翻译自:http://docs.openstack.org/developer/horizon/topics/tutorial.html
本教程讨论如何使用Horizon中多样的组件(主要是tables和tabs)来建立一个dashboard和panel。
创建一个dashboard控制面板
注意:你可以创建一个panel而不是dashboard,然后整合它到一个已经存在的dashboard。
快速版本
Horizon提供了一套自定义管理命令来创建一个典型的基于dashboard的结构。下列命令产生了大部分的模板代码:
./run_tests.sh -m sstartdash mydashboard
非常推荐你阅读剩余的章节来了解这个命令创建了什么和为什么要创建。
结构
dashboard(或者panel)的推荐的结构适合于典型的Django应用布局,我们将我们的dashboard命名为My Dashboard”:
mydashboard
|--__init__.py
|--dashboard.py
|--templates/
|--static/
dashboard.py模块将通过使用Horizon包含我们要使用的dashboard类;templates和static目录分别是我们的Django模板文件和静态媒体文件。
在static和templates目录中,不错的命名空间应该像这样:
templates/
|--mydashboard/
static/
|--mydashboard/
|--css/
|--js/
|--img/
在那些文件和目录的地方,我们可以继续写我们自己的dashboard类。
定义一个dashboard
一个dashboard可以极其简单,至少定义一个name(dashboard对外所呈现的名称)和一个slug(被其它组件引用的内部名称):
import horizon
class MyDashboard(horizon.Dashboard):
name = _("My Dashboard")
slug = "mydashboard"
在实践中,一个dashboard类通常包括更多信息,例如一系列的panels和任何访问dashboard的权限:
class MyDashboard(horizon.Dashboard):
name = _("My Dashboard")
slug = "mydashboard"
panels = ('mypanel',)
default_panel = 'mypanel'
permissions = ('openstack.roles.admin'<span style="font-size:12px;">,)</span>
一旦我们的dashboard完成,我们需要做的就是注册它:
horizon.register(MyDashboard)
这个操作的典型的位置是dashboard.py文件的底部,但是它可以在任何的其他地方,例如在一个重写的文件中。
创建一个panel
现在我们已经有了我们写的dashboard,我们也可以创建我们的panel,我们将panel命名为“My Panel”。
注意:你不需要为了添加panel而去写一个dashboard,这个结构是为了保证教程的完整性。
快速版本
Horizon提供了一套自定义的管理命令来创建一个典型的基于panel的结构。下面的命令生成了大部分的模板代码:
./run_test.sh -m startpanel mypanel --dashboard=mydashboard --target=auto
dashboard参数是必选的,告诉命令panel注册的dashboard。target参数是可选的,auto意味着创建的panel文件应该在dashboard模块内相对于当前的目录(默认)。
非常推荐你阅读剩余的章节来了解这个命令创建了什么和为什么要创建。
结构
一个Panel是一个相对扁平的结构,继续我们的mydashboard/mypanel的例子,它们看起来应该是这样:
mypanel/
|--__init__.py
|--panel.py
|--urls.py
|--views.py
|--templates/
|--mypanel/
|--index.html
定义一个panel
上面指定的panel.py文件有一个特殊的意义,在一个dashboard中,dashboard类中指定的"panels属性"列出任何模块名字,将自动在panel.py文件中查找相应的记录。
在panel.py模块中,我们定义我们的panel类:
class MyPanel(horizon.Panel):
name = _("My Panel")
slug = 'mypanel'
一旦定义了它,我们还需要在dashboard中注册它,通常在panel.py文件末尾:
dashboard.VizDash.register(MyPanel)
从之前构建的例子,我们可能想要定义一组panels共享主题,而且在导航上有一个子标题,我们将我们的panels组命名为“My Group”:
class MyGroup(horizon.PanelGroup):
slug = "mygroup"
name = _("My Group")
panels = ('mypanel',)
PanelGroup可以添加到dashboard类的panels列表。
Tables,Tabs,Views
我们从table开始,把table和tab整合到一起,最后把它们统一合成到一个视图(view)中。
从高层次视图开始,我们最终的目标是创建一个视图(我们的IndexViex类),使用Horizon的DataTable类来展示数据,使用Horizon的TabGroup类来提供给用户一个分页式的展示方式。
我们先从table开始,整合它与tab,然后建立我们的视图。
定义一个table
Horizon提供了一个DataTable类,简化了绝大多数显示数据给最终用户的工作。这种情况下,我们使用table呈现数据,所以开始定义我们的table,一个table.py文件:
from django.utils.translation import ugettext_lazy as _
from horizon import tables
class InstancesTable(tables.DataTable):
name = tables.Column("name",verbose_name=_("Name"))
status = tables.Column("status",verbose_name=_("Status")
zone = tables.Column('availability_zone',verbose_name=_("Availability Zone"))
image_name = tables.Column('image_name',verbose_name=_("Image Name")
class Meta:
name = "instances"
verbose_name = _("Instances")
有几件事情发生了,我们创建了一个table子类,定义了四列。每列定义了它访问实例类的属性作为第一个参数,由于我们希望所有的事情都是可以被翻译的,我们给没列一个verbose_name标记它们可以被翻译。
最后我们添加了一个Meta类来定义一些描述table的元数据。
向table添加action
Horizon提供了三种基本的可以呈现table数据的action类:
在基本的action的基础上,还提供了一些扩展的action类:
现在,我们创建并向table添加一个“filter action”。我们需要编辑上面的table.py文件,添加了“filter action”后,我们将只能看到filter域中规定的字段。先定义一个FilterAction类:
class MyFilterAction(tables.FilterAction):
name = "myfilter"
然后,我们将这个action加入到table中:
class InstancesTable:
class Meta:
table_actions = (MyFilterAction,)
完整的table.py文件看起来应该是这样:
from django.utils.translation import ugettext_lazy as _
from horizon import tables
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class InstancesTable(tables.DataTable):
name = tables.Column('name', \
verbose_name=_("Name"))
status = tables.Column('status', \
verbose_name=_("Status"))
zone = tables.Column('availability_zone', \
verbose_name=_("Availability Zone"))
image_name = tables.Column('image_name', \
verbose_name=_("Image Name"))
class Meta:
name = "instances"
verbose_name = _("Instances")
table_actions = (MyFilterAction,)
定义tabs
因为我们有了table,可以接收数据,我们可以直接得到一个视图,这种情况下,我们同样适用Horizon的TabGroup类,它给我们一个干净、简化的tabs接口来显示我们的可视化数据。
整合table与tab:
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.mydashboard.mypanel import tables
class InstanceTab(tabs.TableTab):
name = _("Instances Tab")
slug = "instances_tab"
table_classes = (tables.InstancesTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_instances_data(self):
try:
marker = self.request.GET.get(
tables.InstancesTable._meta.pagination_param, None)
instances,self._has_more = api.nova.server_list(
self.request,
search_opts = {'marker': marker,'paginate': True})
return instances
except Exception:
self._has_more = False
error_message = _('Unable to get instances'>)
exceptions.handle(self.request, error_message)
return []
class MypanelTabs(tabs.TabGroup):
slug = "mypanel_tabs"
tabs = (InstanceTab,)
sticky = True
这个tab有一点复杂。tab处理tables的数据(以及所有有关的特性),同时它可以使用preload属性来指定这个tab该不该被加载,默认情况下为不加载。当有人点击它时,它将通过AJAX方式加载,在绝大多数情况下保存我们的API调用。
把他们整合到一个视图(view)中
在Horizon中,有很多基于类的预建视图,我们试着给所有通用整合组件提供起始点。
这种情况我们想要一个起始的视图类型与tables和tabs一起工作,那应该是TabbedTableView类。它很好的动态延迟加载能力(tab group提供的并混合在actions中)和AJAX方式更新tables的能力,使得在用户端基本不用做任何工作。
代码看起来应该是这样:
from horizon import tabs
from openstack_dashboard.dashboards.mydashboard.mypanel \
import tabs as mydashboard_tabs
class IndexView(tabs.TabbedTableView):
tab_group_class = mydashboard_tabs.MypanelTabs
template_name = 'mydashboard/mypanel/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
URLs
一个充满智慧的设想就是Panel类可以在url.py文件中被找到,我们定义一个叫做index的视图作为panel的默认处理视图,一个完整的url.py文件看起来应该是这样:
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.mydashboard.mypanel import views
urlpatterns = patterns('',
url(r'^$',
views.IndexView.as_view(), name='index'),
)
Template(模板)
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Mypanel" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Mypanel") %}
{% endblock page_header %}
{% block main %}
<pre><div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}
如果你想更改index.html的title,你可以更改{% block title %}块中的内容。