2014-10-05 06:53:38 +09:00
|
|
|
|
============================================
|
|
|
|
|
Tutorial: Building a Dashboard using Horizon
|
|
|
|
|
============================================
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2015-04-15 15:36:59 -04:00
|
|
|
|
This tutorial covers how to use the various components in horizon to build
|
2014-06-17 15:57:14 -07:00
|
|
|
|
an example dashboard and a panel with a tab which has a table containing data
|
|
|
|
|
from the back end.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
As an example, we'll create a new ``My Dashboard`` dashboard with a ``My Panel``
|
|
|
|
|
panel that has an ``Instances Tab`` tab. The tab has a table which contains the
|
|
|
|
|
data pulled by the Nova instances API.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
This tutorial assumes you have either a ``devstack`` or ``openstack``
|
|
|
|
|
environment up and running.
|
|
|
|
|
There are a variety of other resources which may be helpful to read first.
|
|
|
|
|
For example, you may want to start
|
2012-04-18 01:31:56 -07:00
|
|
|
|
with the :doc:`Horizon quickstart guide </quickstart>` or the
|
|
|
|
|
`Django tutorial`_.
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
.. _Django tutorial: https://docs.djangoproject.com/en/1.6/intro/tutorial01/
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Creating a dashboard
|
|
|
|
|
====================
|
|
|
|
|
|
2012-04-26 19:22:51 -07:00
|
|
|
|
The quick version
|
|
|
|
|
-----------------
|
|
|
|
|
|
|
|
|
|
Horizon provides a custom management command to create a typical base
|
2014-06-17 15:57:14 -07:00
|
|
|
|
dashboard structure for you. Run the following commands at the same location
|
|
|
|
|
where the ``run_tests.sh`` file resides. It generates most of the boilerplate
|
|
|
|
|
code you need::
|
2012-04-26 19:22:51 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
mkdir openstack_dashboard/dashboards/mydashboard
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
./run_tests.sh -m startdash mydashboard \
|
|
|
|
|
--target openstack_dashboard/dashboards/mydashboard
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
mkdir openstack_dashboard/dashboards/mydashboard/mypanel
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
./run_tests.sh -m startpanel mypanel \
|
|
|
|
|
--dashboard=openstack_dashboard.dashboards.mydashboard \
|
|
|
|
|
--target=openstack_dashboard/dashboards/mydashboard/mypanel
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
You will notice that the directory ``mydashboard`` gets automatically
|
|
|
|
|
populated with the files related to a dashboard and the ``mypanel`` directory
|
|
|
|
|
gets automatically populated with the files related to a panel.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Structure
|
|
|
|
|
---------
|
|
|
|
|
If you use the ``tree mydashboard`` command to list the ``mydashboard``
|
|
|
|
|
directory in ``openstack_dashboard/dashboards`` , you will see a directory
|
|
|
|
|
structure that looks like the following::
|
|
|
|
|
|
|
|
|
|
mydashboard
|
|
|
|
|
├── dashboard.py
|
|
|
|
|
├── dashboard.pyc
|
|
|
|
|
├── __init__.py
|
|
|
|
|
├── __init__.pyc
|
|
|
|
|
├── mypanel
|
|
|
|
|
│ ├── __init__.py
|
|
|
|
|
│ ├── panel.py
|
|
|
|
|
│ ├── templates
|
|
|
|
|
│ │ └── mypanel
|
|
|
|
|
│ │ └── index.html
|
|
|
|
|
│ ├── tests.py
|
|
|
|
|
│ ├── urls.py
|
|
|
|
|
│ └── views.py
|
|
|
|
|
├── static
|
|
|
|
|
│ └── mydashboard
|
|
|
|
|
│ ├── css
|
|
|
|
|
│ │ └── mydashboard.css
|
|
|
|
|
│ └── js
|
|
|
|
|
│ └── mydashboard.js
|
|
|
|
|
└── templates
|
|
|
|
|
└── mydashboard
|
|
|
|
|
└── base.html
|
|
|
|
|
|
|
|
|
|
|
2015-10-16 08:50:41 +02:00
|
|
|
|
For this tutorial, we will not deal with the static directory, or the
|
|
|
|
|
``tests.py`` file. Leave them as they are.
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
|
|
|
|
With the rest of the files and directories in place, we can move on to add our
|
|
|
|
|
own dashboard.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Defining a dashboard
|
|
|
|
|
--------------------
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Open the ``dashboard.py`` file. You will notice the following code has been
|
|
|
|
|
automatically generated::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
import horizon
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
class Mydashboard(horizon.Dashboard):
|
|
|
|
|
name = _("Mydashboard")
|
|
|
|
|
slug = "mydashboard"
|
|
|
|
|
panels = () # Add your panels here.
|
|
|
|
|
default_panel = '' # Specify the slug of the dashboard's default panel.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
horizon.register(Mydashboard)
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
If you want the dashboard name to be something else, you can change the ``name``
|
|
|
|
|
attribute in the ``dashboard.py`` file . For example, you can change it
|
|
|
|
|
to be ``My Dashboard`` ::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
name = _("My Dashboard")
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
A dashboard class will usually contain a ``name`` attribute (the display name of
|
|
|
|
|
the dashboard), a ``slug`` attribute (the internal name that could be referenced
|
|
|
|
|
by other components), a list of panels, default panel, etc. We will cover how
|
|
|
|
|
to add a panel in the next section.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Creating a panel
|
|
|
|
|
================
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
We'll create a panel and call it ``My Panel``.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Structure
|
|
|
|
|
---------
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
As described above, the ``mypanel`` directory under
|
|
|
|
|
``openstack_dashboard/dashboards/mydashboard`` should look like the following::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
mypanel
|
|
|
|
|
├── __init__.py
|
|
|
|
|
├── models.py
|
|
|
|
|
├── panel.py
|
|
|
|
|
├── templates
|
|
|
|
|
│ └── mypanel
|
|
|
|
|
│ └── index.html
|
|
|
|
|
├── tests.py
|
|
|
|
|
├── urls.py
|
|
|
|
|
└── views.py
|
2012-04-26 19:22:51 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Defining a panel
|
|
|
|
|
----------------
|
2012-04-26 19:22:51 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
The ``panel.py`` file referenced above has a special meaning. Within a dashboard,
|
|
|
|
|
any module name listed in the ``panels`` attribute on the dashboard class will
|
|
|
|
|
be auto-discovered by looking for the ``panel.py`` file in a corresponding
|
|
|
|
|
directory (the details are a bit magical, but have been thoroughly vetted in
|
|
|
|
|
Django's admin codebase).
|
2012-04-26 19:22:51 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Open the ``panel.py`` file, you will have the following auto-generated code::
|
2012-04-26 19:22:51 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
import horizon
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from openstack_dashboard.dashboards.mydashboard import dashboard
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
class Mypanel(horizon.Panel):
|
|
|
|
|
name = _("Mypanel")
|
|
|
|
|
slug = "mypanel"
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
dashboard.Mydashboard.register(Mypanel)
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
If you want the panel name to be something else, you can change the ``name``
|
|
|
|
|
attribute in the ``panel.py`` file . For example, you can change it to be
|
|
|
|
|
``My Panel``::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
name = _("My Panel")
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Open the ``dashboard.py`` file again, insert the following code above the
|
|
|
|
|
``Mydashboard`` class. This code defines the ``Mygroup`` class and adds a panel
|
|
|
|
|
called ``mypanel``::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
class Mygroup(horizon.PanelGroup):
|
|
|
|
|
slug = "mygroup"
|
|
|
|
|
name = _("My Group")
|
|
|
|
|
panels = ('mypanel',)
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Modify the ``Mydashboard`` class to include ``Mygroup`` and add ``mypanel`` as
|
|
|
|
|
the default panel::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
class Mydashboard(horizon.Dashboard):
|
|
|
|
|
name = _("My Dashboard")
|
|
|
|
|
slug = "mydashboard"
|
|
|
|
|
panels = (Mygroup,) # Add your panels here.
|
|
|
|
|
default_panel = 'mypanel' # Specify the slug of the default panel.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
The completed ``dashboard.py`` file should look like
|
2014-06-17 15:57:14 -07:00
|
|
|
|
the following::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
|
|
|
|
|
|
import horizon
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Mygroup(horizon.PanelGroup):
|
|
|
|
|
slug = "mygroup"
|
|
|
|
|
name = _("My Group")
|
|
|
|
|
panels = ('mypanel',)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Mydashboard(horizon.Dashboard):
|
|
|
|
|
name = _("My Dashboard")
|
|
|
|
|
slug = "mydashboard"
|
|
|
|
|
panels = (Mygroup,) # Add your panels here.
|
|
|
|
|
default_panel = 'mypanel' # Specify the slug of the default panel.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
horizon.register(Mydashboard)
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Tables, Tabs, and Views
|
|
|
|
|
-----------------------
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
We'll start with the table, combine that with the tabs, and then build our
|
|
|
|
|
view from the pieces.
|
|
|
|
|
|
|
|
|
|
Defining a table
|
|
|
|
|
~~~~~~~~~~~~~~~~
|
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
Horizon provides a :class:`~horizon.forms.SelfHandlingForm` :class:`~horizon.tables.DataTable` class which simplifies
|
2012-04-18 01:31:56 -07:00
|
|
|
|
the vast majority of displaying data to an end-user. We're just going to skim
|
|
|
|
|
the surface here, but it has a tremendous number of capabilities.
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Create a ``tables.py`` file under the ``mypanel`` directory and add the
|
|
|
|
|
following code::
|
|
|
|
|
|
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
from horizon import tables
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
|
|
|
|
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"))
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
|
name = "instances"
|
|
|
|
|
verbose_name = _("Instances")
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
2012-04-18 01:31:56 -07:00
|
|
|
|
There are several things going on here... we created a table subclass,
|
2014-06-17 15:57:14 -07:00
|
|
|
|
and defined four columns that we want to retrieve data and display.
|
|
|
|
|
Each of those columns defines what attribute it accesses on the instance object
|
|
|
|
|
as the first argument, and since we like to make everything translatable,
|
|
|
|
|
we give each column a ``verbose_name`` that's marked for translation.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Lastly, we added a ``Meta`` class which indicates the meta object that describes
|
|
|
|
|
the ``instances`` table.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
This is a slight simplification from the reality of how the instance
|
2014-06-17 15:57:14 -07:00
|
|
|
|
object is actually structured. In reality, accessing other attributes
|
|
|
|
|
requires an additional step.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
Adding actions to a table
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
Horizon provides three types of basic action classes which can be taken
|
|
|
|
|
on a table's data:
|
|
|
|
|
|
|
|
|
|
- :class:`~horizon.tables.Action`
|
|
|
|
|
- :class:`~horizon.tables.LinkAction`
|
|
|
|
|
- :class:`~horizon.tables.FilterAction`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
There are also additional actions which are extensions of the basic Action classes:
|
|
|
|
|
|
|
|
|
|
- :class:`~horizon.tables.BatchAction`
|
|
|
|
|
- :class:`~horizon.tables.DeleteAction`
|
|
|
|
|
- :class:`~horizon.tables.UpdateAction`
|
|
|
|
|
- :class:`~horizon.tables.FixedFilterAction`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Now let's create and add a filter action to the table. To do so, we will need
|
|
|
|
|
to edit the ``tables.py`` file used above. To add a filter action which will
|
|
|
|
|
only show rows which contain the string entered in the filter field, we
|
|
|
|
|
must first define the action::
|
|
|
|
|
|
|
|
|
|
class MyFilterAction(tables.FilterAction):
|
|
|
|
|
name = "myfilter"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.. note::
|
|
|
|
|
|
|
|
|
|
The action specified above will default the ``filter_type`` to be ``"query"``.
|
|
|
|
|
This means that the filter will use the client side table sorter.
|
|
|
|
|
|
|
|
|
|
Then, we add that action to the table actions for our table.::
|
|
|
|
|
|
|
|
|
|
class InstancesTable:
|
|
|
|
|
class Meta:
|
|
|
|
|
table_actions = (MyFilterAction,)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The completed ``tables.py`` file should look like the following::
|
|
|
|
|
|
|
|
|
|
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,)
|
|
|
|
|
|
|
|
|
|
|
2012-04-18 01:31:56 -07:00
|
|
|
|
Defining tabs
|
|
|
|
|
~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
|
|
So we have a table, ready to receive our data. We could go straight to a view
|
2015-04-15 15:36:59 -04:00
|
|
|
|
from here, but in this case we're also going to use horizon's
|
2014-06-17 15:57:14 -07:00
|
|
|
|
:class:`~horizon.tabs.TabGroup` class.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Create a ``tabs.py`` file under the ``mypanel`` directory. Let's make a tab
|
|
|
|
|
group which has one tab. The completed code should look like the following::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from horizon import exceptions
|
|
|
|
|
from horizon import tabs
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from openstack_dashboard import api
|
|
|
|
|
from openstack_dashboard.dashboards.mydashboard.mypanel import tables
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
class InstanceTab(tabs.TableTab):
|
|
|
|
|
name = _("Instances Tab")
|
|
|
|
|
slug = "instances_tab"
|
|
|
|
|
table_classes = (tables.InstancesTable,)
|
|
|
|
|
template_name = ("horizon/common/_detail_table.html")
|
2012-04-18 01:31:56 -07:00
|
|
|
|
preload = False
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
def has_more_data(self, table):
|
|
|
|
|
return self._has_more
|
|
|
|
|
|
2012-04-18 01:31:56 -07:00
|
|
|
|
def get_instances_data(self):
|
|
|
|
|
try:
|
2014-06-17 15:57:14 -07:00
|
|
|
|
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)
|
|
|
|
|
|
2014-11-02 15:41:47 +00:00
|
|
|
|
return []
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
|
|
|
|
class MypanelTabs(tabs.TabGroup):
|
|
|
|
|
slug = "mypanel_tabs"
|
|
|
|
|
tabs = (InstanceTab,)
|
|
|
|
|
sticky = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This tab gets a little more complicated. The tab handles data tables (and
|
|
|
|
|
all their associated features), and it also uses the ``preload`` attribute to
|
|
|
|
|
specify that this tab shouldn't be loaded by default. It will instead be loaded
|
|
|
|
|
via AJAX when someone clicks on it, saving us on API calls in the vast majority
|
|
|
|
|
of cases.
|
|
|
|
|
|
|
|
|
|
Additionally, the displaying of the table is handled by a reusable template,
|
|
|
|
|
``horizon/common/_detail_table.html``. Some simple pagination code was added
|
|
|
|
|
to handle large instance lists.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2015-04-15 15:36:59 -04:00
|
|
|
|
Lastly, this code introduces the concept of error handling in horizon.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
The :func:`horizon.exceptions.handle` function is a centralized error
|
|
|
|
|
handling mechanism that takes all the guess-work and inconsistency out of
|
|
|
|
|
dealing with exceptions from the API. Use it everywhere.
|
|
|
|
|
|
|
|
|
|
Tying it together in a view
|
|
|
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
2015-04-15 15:36:59 -04:00
|
|
|
|
There are lots of pre-built class-based views in horizon. We try to provide
|
2014-06-17 15:57:14 -07:00
|
|
|
|
the starting points for all the common combinations of components.
|
|
|
|
|
|
|
|
|
|
Open the ``views.py`` file, the auto-generated code is like the following::
|
|
|
|
|
|
|
|
|
|
from horizon import views
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IndexView(views.APIView):
|
|
|
|
|
# A very simple class-based view...
|
|
|
|
|
template_name = 'mydashboard/mypanel/index.html'
|
|
|
|
|
|
|
|
|
|
def get_data(self, request, context, *args, **kwargs):
|
|
|
|
|
# Add data to the context here...
|
|
|
|
|
return context
|
|
|
|
|
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
In this case we want a starting view type that works with both tabs and
|
|
|
|
|
tables... that'd be the :class:`~horizon.tabs.TabbedTableView` class. It takes
|
|
|
|
|
the best of the dynamic delayed-loading capabilities tab groups provide and
|
|
|
|
|
mixes in the actions and AJAX-updating that tables are capable of with almost
|
2014-06-17 15:57:14 -07:00
|
|
|
|
no work on the user's end. Change ``views.APIView`` to be
|
|
|
|
|
``tabs.TabbedTableView`` and add ``MypanelTabs`` as the tab group class in the
|
|
|
|
|
``IndexView`` class::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
class IndexView(tabs.TabbedTableView):
|
2014-06-17 15:57:14 -07:00
|
|
|
|
tab_group_class = mydashboard_tabs.MypanelTabs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
After importing the proper package, the completed ``views.py`` file now looks like
|
|
|
|
|
the following::
|
|
|
|
|
|
|
|
|
|
from horizon import tabs
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
from openstack_dashboard.dashboards.mydashboard.mypanel \
|
|
|
|
|
import tabs as mydashboard_tabs
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IndexView(tabs.TabbedTableView):
|
2014-06-17 15:57:14 -07:00
|
|
|
|
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
|
|
|
|
|
----
|
|
|
|
|
The auto-generated ``urls.py`` file is like::
|
|
|
|
|
|
2014-09-16 14:07:57 +04:00
|
|
|
|
from django.conf.urls import patterns
|
|
|
|
|
from django.conf.urls import url
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
2015-01-13 09:41:43 +01:00
|
|
|
|
from openstack_dashboard.dashboards.mydashboard.mypanel.views \
|
|
|
|
|
import IndexView
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
|
|
|
|
|
2015-01-13 09:41:43 +01:00
|
|
|
|
urlpatterns = patterns(
|
|
|
|
|
'',
|
2014-06-17 15:57:14 -07:00
|
|
|
|
url(r'^$', IndexView.as_view(), name='index'),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Adjust the import of ``IndexView`` to make the code readable::
|
|
|
|
|
|
|
|
|
|
from openstack_dashboard.dashboards.mydashboard.mypanel import views
|
|
|
|
|
|
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
Replace the existing ``url`` pattern with the following line::
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
2015-03-04 16:38:26 -08:00
|
|
|
|
url(r'^$',
|
|
|
|
|
views.IndexView.as_view(), name='index'),
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
The completed ``urls.py`` file should look like the following::
|
|
|
|
|
|
2014-09-16 14:07:57 +04:00
|
|
|
|
from django.conf.urls import patterns
|
|
|
|
|
from django.conf.urls import url
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
|
|
|
|
from openstack_dashboard.dashboards.mydashboard.mypanel import views
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
urlpatterns = patterns('',
|
2015-03-04 16:38:26 -08:00
|
|
|
|
url(r'^$',
|
|
|
|
|
views.IndexView.as_view(), name='index'),
|
2014-06-17 15:57:14 -07:00
|
|
|
|
)
|
|
|
|
|
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
The template
|
|
|
|
|
~~~~~~~~~~~~
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Open the ``index.html`` file in the ``mydashboard/mypanel/templates/mypanel``
|
|
|
|
|
directory, the auto-generated code is like the following::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
{% extends 'base.html' %}
|
2012-04-18 01:31:56 -07:00
|
|
|
|
{% load i18n %}
|
2014-06-17 15:57:14 -07:00
|
|
|
|
{% block title %}{% trans "Mypanel" %}{% endblock %}
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
{% block page_header %}
|
2014-06-17 15:57:14 -07:00
|
|
|
|
{% include "horizon/common/_page_header.html" with title=_("Mypanel") %}
|
2012-04-18 01:31:56 -07:00
|
|
|
|
{% endblock page_header %}
|
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
{% block main %}
|
2014-06-17 15:57:14 -07:00
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
The ``main`` block must be modified to insert the following code::
|
2014-06-17 15:57:14 -07:00
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
<div class="row">
|
2014-07-02 14:05:54 +02:00
|
|
|
|
<div class="col-sm-12">
|
2012-04-18 01:31:56 -07:00
|
|
|
|
{{ tab_group.render }}
|
|
|
|
|
</div>
|
2015-01-07 15:28:54 -08:00
|
|
|
|
</div>
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
If you want to change the title of the ``index.html`` file to be something else,
|
|
|
|
|
you can change it. For example, change it to be ``My Panel`` in the
|
|
|
|
|
``block title`` section. If you want the ``title`` in the ``block page_header``
|
|
|
|
|
section to be something else, you can change it. For example, change it to be
|
|
|
|
|
``My Panel``. The updated code could be like::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
{% extends 'base.html' %}
|
2014-06-17 15:57:14 -07:00
|
|
|
|
{% load i18n %}
|
|
|
|
|
{% block title %}{% trans "My Panel" %}{% endblock %}
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
{% block page_header %}
|
|
|
|
|
{% include "horizon/common/_page_header.html" with title=_("My Panel") %}
|
|
|
|
|
{% endblock page_header %}
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
{% block main %}
|
2014-06-17 15:57:14 -07:00
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-sm-12">
|
|
|
|
|
{{ tab_group.render }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endblock %}
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
This gives us a custom page title, a header, and renders our tab group provided
|
|
|
|
|
by the view.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
With all our code in place, the only thing left to do is to integrate it into
|
|
|
|
|
our OpenStack Dashboard site.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
.. note::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
For more information about Django views, URLs and templates, please refer
|
|
|
|
|
to the `Django documentation`_.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
.. _Django documentation: https://docs.djangoproject.com/en/1.6/
|
2013-10-31 15:33:44 -04:00
|
|
|
|
|
2013-04-25 11:58:38 +02:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Enable and show the dashboard
|
|
|
|
|
=============================
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
In order to make ``My Dashboard`` show up along with the existing dashboards
|
2015-04-15 15:36:59 -04:00
|
|
|
|
like ``Project`` or ``Admin`` on horizon, you need to create a file called
|
2014-06-17 15:57:14 -07:00
|
|
|
|
``_50_mydashboard.py`` under ``openstack_dashboard/enabled`` and add the
|
|
|
|
|
following::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
# The name of the dashboard to be added to HORIZON['dashboards']. Required.
|
|
|
|
|
DASHBOARD = 'mydashboard'
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
# If set to True, this dashboard will not be added to the settings.
|
|
|
|
|
DISABLED = False
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
# A list of applications to be added to INSTALLED_APPS.
|
|
|
|
|
ADD_INSTALLED_APPS = [
|
|
|
|
|
'openstack_dashboard.dashboards.mydashboard',
|
|
|
|
|
]
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Run and check the dashboard
|
2015-01-07 15:28:54 -08:00
|
|
|
|
===========================
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Everything is in place, now run ``Horizon`` on the different port::
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
./run_tests.sh --runserver 0.0.0.0:8877
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Go to ``http://<your server>:8877`` using a browser. After login as an admin
|
2015-04-15 15:36:59 -04:00
|
|
|
|
you should be able see ``My Dashboard`` shows up at the left side on horizon.
|
2014-06-17 15:57:14 -07:00
|
|
|
|
Click it, ``My Group`` will expand with ``My Panel``. Click on ``My Panel``,
|
|
|
|
|
the right side panel will display an ``Instances Tab`` which has an
|
|
|
|
|
``Instances`` table.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
If you don't see any instance data, you haven't created any instances yet. Go to
|
|
|
|
|
dashboard ``Project`` -> ``Images``, select a small image, for example,
|
2016-01-19 13:42:56 +08:00
|
|
|
|
``cirros-0.3.1-x86_64-uec`` , click ``Launch`` and enter an ``Instance Name``,
|
2015-04-15 15:36:59 -04:00
|
|
|
|
click the button ``Launch``. It should create an instance if the OpenStack or
|
2014-06-17 15:57:14 -07:00
|
|
|
|
devstack is correctly set up. Once the creation of an instance is successful, go
|
|
|
|
|
to ``My Dashboard`` again to check the data.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2015-01-07 15:28:54 -08:00
|
|
|
|
|
|
|
|
|
Adding a complex action to a table
|
|
|
|
|
==================================
|
|
|
|
|
|
|
|
|
|
For a more detailed look into adding a table action, one that requires forms for
|
|
|
|
|
gathering data, you can walk through :doc:`Adding a complex action to a table
|
|
|
|
|
</topics/table_actions>` tutorial.
|
|
|
|
|
|
|
|
|
|
|
2012-04-18 01:31:56 -07:00
|
|
|
|
Conclusion
|
|
|
|
|
==========
|
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
What you've learned here is the fundamentals of how to write interfaces for
|
2015-04-15 15:36:59 -04:00
|
|
|
|
your own project based on the components horizon provides.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
If you have feedback on how this tutorial could be improved, please feel free
|
|
|
|
|
to submit a bug against ``Horizon`` in `launchpad`_.
|
2012-04-18 01:31:56 -07:00
|
|
|
|
|
2014-06-17 15:57:14 -07:00
|
|
|
|
.. _launchpad: https://bugs.launchpad.net/horizon
|