You can find a reference implementation of the code being described here on github at https://github.com/gabrielhurley/horizon_demo.
Note
It's perfectly valid to create a panel without a dashboard.
We'll talk about how later.
Follows the typical Django application layout.
visualizations
|--__init__.py
|--dashboard.py
|--templates/
|--static/
The dashboard.py module will contain our dashboard class for use by Horizon; the templates and static directories give us homes for our Django template files and static media respectively.
Within the static and templates directories it’s generally good to namespace your files:
templates/
|--visualizations/
static/
|--visualizations/
|--css/
|--js/
|--img/
A dashboard class can be incredibly simple (about 3 lines at minimum), defining nothing more than a name and a slug:
import horizon
class VizDash(horizon.Dashboard):
name = _("Visualizations")
slug = "visualizations"
In practice, a dashboard class will usually contain more information...
class VizDash(horizon.Dashboard):
name = _("Visualizations")
slug = "visualizations"
panels = ('flocking',)
default_panel = 'flocking'
roles = ('admin',)
Once our dashboard class is complete, all we need to do is register it:
horizon.register(VizDash)
The typical place for that would be the bottom of the dashboard.py file, but it could also go elsewhere.
You don't always need a dashboard to create a panel, but when you do, you'd better drink Dos Equis.
Without a dashboard
flocking/ |--__init__.py |--panel.py |--urls.py |--views.py |--templates/ |--flocking/ |--index.html
With a dashboard
visualizations/ |--__init__.py |--dashboard.py |--flocking/ |--__init__.py |--panel.py |--urls.py |--views.py |--templates/ |--visualizations/ |--flocking/ |--index.html
The panel.py file has a special meaning.
Inside the panel.py module we define our Panel class:
class Flocking(horizon.Panel):
name = _("Flocking")
slug = 'flocking'
Simple, right?
Once we’ve defined it, we register it with the dashboard:
from visualizations import dashboard
dashboard.VizDash.register(Flocking)
You need a urls.py file in your panel directory with a view named index:
from django.conf.urls.defaults import patterns, url
from .views import IndexView
urlpatterns = patterns('',
url(r'^$', IndexView.as_view(), name='index')
)
Now we get to the really exciting parts; everything before this was structural.
In a tables.py module:
from horizon import tables
class FlockingInstancesTable(tables.DataTable):
host = tables.Column("OS-EXT-SRV-ATTR:host", verbose_name=_("Host"))
tenant = tables.Column('tenant_name', verbose_name=_("Tenant"))
user = tables.Column('user_name', verbose_name=_("user"))
vcpus = tables.Column('flavor_vcpus', verbose_name=_("VCPUs"))
memory = tables.Column('flavor_memory', verbose_name=_("Memory"))
age = tables.Column('age', verbose_name=_("Age"))
class Meta:
name = "instances"
verbose_name = _("Instances")
We have a table, now what...
Let’s make a tab for our visualization:
class VizTab(tabs.Tab):
name = _("Visualization")
slug = "viz"
template_name = "visualizations/flocking/_flocking.html"
def get_context_data(self, request):
return None
We also need a tab for our data table:
from .tables import FlockingInstancesTable
class DataTab(tabs.TableTab):
name = _("Data")
slug = "data"
table_classes = (FlockingInstancesTable,)
template_name = "horizon/common/_detail_table.html"
preload = False
def get_instances_data(self):
try:
instances = utils.get_instances_data(self.tab_group.request)
except:
instances = []
exceptions.handle(self.tab_group.request,
_('Unable to retrieve instance list.'))
return instances
We want to handle both tabs and tables... There's a view for that.
from .tables import FlockingInstancesTable
from .tabs import FlockingTabs
class IndexView(tabs.TabbedTableView):
tab_group_class = FlockingTabs
table_class = FlockingInstancesTable
template_name = 'visualizations/flocking/index.html'
The vast majority of people will just customize the OpenStack Dashboard example project that ships with Horizon.
A site built on Horizon takes the form of a very typical Django project:
site/
|--__init__.py
|--manage.py
|--demo_dashboard/
|--__init__.py
|--models.py # required for Django even if unused
|--settings.py
|--templates/
|--static/
The key bits here are that demo_dashboard is on our python path, and that the settings.py` file here will contain our customized Horizon config.
There are several key things you can customiz in your site’s settings file:
HORIZON_CONFIG = {
'dashboards': ('nova', 'syspanel', 'visualizations', 'settings',),
}
Adding custom error handlers to HORIZON_CONFIG for your API client is easy:
import my_api.exceptions as my_api
'exceptions': {'recoverable': [my_api.Error,
my_api.ClientConnectionError],
'not_found': [my_api.NotFound],
'unauthorized': [my_api.NotAuthorized]},
The override file is the “god-mode” dashboard editor.
Comes between the automatic discovery mechanisms and the final setup routines for the entire site.
With great power comes great responsibility.
To specify an override file, you set the 'customization_module' value in the HORIZON_CONFIG dictionary to the dotted python path of your override module:
HORIZON_CONFIG = {
'customization_module': 'demo_dashboard.overrides'
}
The cake was a lie.
If you want to see the finished product, check out the github example referenced at the beginning of this tutorial and linked from the etherpad.
What you’ve learned here are the fundamentals.
Go forth and build!
/
#