Plugin Docs
Added tutorial folder for future tutorials. Added tutorial for creating and installing plugins. Moved dashboard tutorial into tutorial folder. Change-Id: I17c1ff279b47292d723c100b580667418aa05636 Closes-Bug: #1511593
This commit is contained in:
parent
cb71cc6087
commit
15c7b03d06
@ -61,10 +61,20 @@ Brief guides to areas of interest and importance when developing Horizon.
|
||||
|
||||
intro
|
||||
quickstart
|
||||
topics/tutorial
|
||||
contributing
|
||||
testing
|
||||
plugins
|
||||
plugin_registry
|
||||
|
||||
Tutorials
|
||||
---------
|
||||
|
||||
Detailed tutorials to help you get started.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
tutorials/plugin
|
||||
tutorials/dashboard
|
||||
|
||||
Topic Guides
|
||||
------------
|
||||
|
@ -329,7 +329,7 @@ To manually add files, add the following arrays and file paths to the enabled fi
|
||||
Plugins
|
||||
-------
|
||||
|
||||
Add a new panel/ panel group/ dashboard (See :doc:`tutorial`). JavaScript file
|
||||
Add a new panel/ panel group/ dashboard (See :doc:`/tutorials/dashboard`). JavaScript file
|
||||
inclusion is the same as the Upstream process.
|
||||
|
||||
To include external stylesheets, you must ensure that ``ADD_SCSS_FILES`` is
|
||||
|
@ -6,7 +6,7 @@ This tutorial covers how to add a more complex action to a table, one that requi
|
||||
an action and form definitions, as well as changes to the view, urls, and table.
|
||||
|
||||
This tutorial assumes you have already completed :doc:`Building a Dashboard using
|
||||
Horizon </topics/tutorial>`. If not, please do so now as we will be modifying the
|
||||
Horizon </tutorials/dashboard>`. If not, please do so now as we will be modifying the
|
||||
files created there.
|
||||
|
||||
This action will create a snapshot of the instance. When the action is taken,
|
||||
|
445
doc/source/tutorials/plugin.rst
Normal file
445
doc/source/tutorials/plugin.rst
Normal file
@ -0,0 +1,445 @@
|
||||
==============
|
||||
Horizon Plugin
|
||||
==============
|
||||
|
||||
Why should I package my code as a plugin?
|
||||
=========================================
|
||||
|
||||
We highly encourage that you write and maintain your code using our plugin
|
||||
architecture. A plugin by definition means the ability to be connected. In
|
||||
practical terms, plugins are a way to extend and add to the functionality that
|
||||
already exists. You can control its content and progress at a rate independent
|
||||
of Horizon. If you write and package your code as a plugin, it will continue to
|
||||
work in future releases.
|
||||
|
||||
Writing your code as a plugin also modularizes your code making it easier to
|
||||
translate and test. This also makes it easier for deployers to consume your code
|
||||
allowing selective enablement of features. We are currently using this pattern
|
||||
internally for our dashboards.
|
||||
|
||||
Creating the Plugin
|
||||
===================
|
||||
|
||||
This tutorial assumes you have a basic understanding of Python, HTML,
|
||||
JavaScript. Knowledge of AngularJS is optional but recommended if you are
|
||||
attempting to create an Angular plugin.
|
||||
|
||||
Types of Plugins that add content
|
||||
---------------------------------
|
||||
|
||||
The file structure for your plugin type will be different depending on your
|
||||
needs. Your plugin can be categorized into two types:
|
||||
|
||||
* Plugins that create new panels or dashboards
|
||||
* Plugins that modify existing workflows, actions, etc... (Angular only)
|
||||
|
||||
We will cover the basics of working with panels for both Python and Angular.
|
||||
If you are interested in creating a new panel, follow the steps below.
|
||||
|
||||
.. Note ::
|
||||
|
||||
This tutorial shows you how to create a new panel. If you are interested in
|
||||
creating a new dashboard plugin, use the file structure from
|
||||
:doc:`Tutorial: Building a Dashboard using Horizon </tutorials/dashboard>`
|
||||
instead.
|
||||
|
||||
File Structure
|
||||
--------------
|
||||
Below is a skeleton of what your plugin should look like.::
|
||||
|
||||
myplugin
|
||||
│
|
||||
├── myplugin
|
||||
│ ├── __init__.py
|
||||
│ │
|
||||
│ ├── enabled
|
||||
│ │ └──_31000_myplugin.py
|
||||
│ │
|
||||
│ ├── api
|
||||
│ │ ├──__init__.py
|
||||
│ │ ├── my_rest_api.py
|
||||
│ │ └── myservice.py
|
||||
│ │
|
||||
│ ├── content
|
||||
│ │ ├──__init__.py
|
||||
│ │ └── mypanel
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── panel.py
|
||||
│ │ ├── tests.py
|
||||
│ │ ├── urls.py
|
||||
│ │ ├── views.py
|
||||
│ │ └── templates
|
||||
│ │ └── mypanel
|
||||
│ │ └── index.html
|
||||
│ │
|
||||
│ └── static
|
||||
│ └── dashboard
|
||||
│ └── identity
|
||||
│ └── myplugin
|
||||
│ └── mypanel
|
||||
│ ├── mypanel.html
|
||||
│ ├── mypanel.js
|
||||
│ └── mypanel.scss
|
||||
├── setup.py
|
||||
├── setup.cfg
|
||||
├── LICENSE
|
||||
├── MANIFEST.in
|
||||
└── README.rst
|
||||
|
||||
If you are creating a Python plugin, you may ignore the ``static`` folder. Most
|
||||
of the classes you need are provided for in Python. If you intend on adding
|
||||
custom front-end logic, you will need to include additional JavaScript here.
|
||||
|
||||
An AngularJS plugin is a collection of JavaScript files or static resources.
|
||||
Because it runs entirely in your browser, we need to place all of our static
|
||||
resources inside the ``static`` folder. This ensures that the Django static
|
||||
collector picks it up and distributes it to the browser correctly.
|
||||
|
||||
The Enabled File
|
||||
----------------
|
||||
|
||||
The enabled folder contains the configuration file(s) that registers your
|
||||
plugin with Horizon. The file is prefixed with an alpha-numberic string that
|
||||
determines the load order of your plugin. For more information on what you can
|
||||
include in this file, see pluggable settings in
|
||||
:doc:`Settings and Configuration </topics/settings>`
|
||||
|
||||
_31000_myplugin.py::
|
||||
|
||||
# The name of the panel to be added to HORIZON_CONFIG. Required.
|
||||
PANEL = 'mypanel'
|
||||
|
||||
# The name of the dashboard the PANEL associated with. Required.
|
||||
PANEL_DASHBOARD = 'identity'
|
||||
|
||||
# Python panel class of the PANEL to be added.
|
||||
ADD_PANEL = 'myplugin.content.mypanel.panel.MyPanel'
|
||||
|
||||
# A list of applications to be prepended to INSTALLED_APPS
|
||||
ADD_INSTALLED_APPS = ['myplugin']
|
||||
|
||||
# A list of AngularJS modules to be loaded when Angular bootstraps.
|
||||
ADD_ANGULAR_MODULES = ['horizon.dashboard.identity.myplugin.mypanel']
|
||||
|
||||
# Automatically discover static resources in installed apps
|
||||
AUTO_DISCOVER_STATIC_FILES = True
|
||||
|
||||
# A list of scss files to be included in the compressed set of files
|
||||
ADD_SCSS_FILES = ['dashboard/identity/myplugin/myplugin.scss']
|
||||
|
||||
my_rest_api.py
|
||||
--------------
|
||||
|
||||
This file will likely be necessary if creating a plugin using Angular. Your
|
||||
plugin will need to communicate with a new service or require new interactions
|
||||
with a service already supported by Horizon. In this particular example, the
|
||||
plugin will augment the support for the already supported Identity service,
|
||||
Keystone. This file serves to define new REST interfaces for the plugin's
|
||||
clientside to communicate with Horizon. Typically, the REST interfaces here
|
||||
make calls into ``myservice.py``.
|
||||
|
||||
This file is unnecessary in a purely Django based plugin, or if your Angular
|
||||
based plugin is relying on CORS support in the desired service. For more
|
||||
information on CORS, see
|
||||
`http://docs.openstack.org/admin-guide-cloud/cross_project_cors.html`
|
||||
|
||||
myservice.py
|
||||
------------
|
||||
|
||||
This file will likely be necessary if creating a Django or Angular driven
|
||||
plugin. This file is intended to act as a convenient location for interacting
|
||||
with the new service this plugin is supporting. While interactions with the
|
||||
service can be handled in the ``views.py``, isolating the logic is an
|
||||
established pattern in Horizon.
|
||||
|
||||
panel.py
|
||||
--------
|
||||
|
||||
We define a panel where our plugin's content will reside in. This is currently a
|
||||
neccessity even for Angular plugins. The slug is the panel's unique identifier
|
||||
and is often use as part of the URL. Make sure that it matches what you have in
|
||||
your enabled file.::
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
import horizon
|
||||
|
||||
|
||||
class MyPanel(horizon.Panel):
|
||||
name = _("My Panel")
|
||||
slug = "mypanel"
|
||||
|
||||
tests.py
|
||||
--------
|
||||
|
||||
Write some tests for the Django portion of your plugin and place them here.
|
||||
|
||||
urls.py
|
||||
-------
|
||||
|
||||
Now that we have a panel, we need to provide a URL so that users can visit our
|
||||
new panel! This URL generally will point to a view.::
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from myplugin.content.mypanel import views
|
||||
|
||||
urlpatterns = patterns(
|
||||
url(r'^$', views.IndexView.as_view(), name='index'),
|
||||
)
|
||||
|
||||
views.py
|
||||
--------
|
||||
|
||||
Because rendering is done client-side, all our view needs is to reference some
|
||||
HTML page. If you are writing a Python plugin, this view can be much more
|
||||
complex. Refer to the topic guides for more details.::
|
||||
|
||||
from django.views import generic
|
||||
|
||||
|
||||
class IndexView(generic.TemplateView):
|
||||
template_name = 'identity/mypanel/index.html'
|
||||
|
||||
index.html
|
||||
----------
|
||||
|
||||
The index HTML is where rendering occurs. In this example, we are only using
|
||||
Django. If you are interested in using Angular directives instead, read the
|
||||
AngularJS section below.::
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "My plugin" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_domain_page_header.html"
|
||||
with title=_("My Panel") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
Hello world!
|
||||
{% endblock %}
|
||||
|
||||
At this point, you have a very basic plugin. Note that new templates are
|
||||
required to extend base.html. Including base.html is important for a number of
|
||||
reasons. It is the template that contains all of your static resources along
|
||||
with any functionality external to your panel (things like navigation, context
|
||||
selection, etc...). As of this moment, this is also true for Angular plugins.
|
||||
|
||||
MANIFEST.in
|
||||
-----------
|
||||
This file is responsible for listing the paths you want included in your tar.::
|
||||
|
||||
include setup.py
|
||||
|
||||
recursive-include myplugin *.js *.html *.scss
|
||||
|
||||
|
||||
setup.py
|
||||
--------
|
||||
::
|
||||
|
||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr>=1.8'],
|
||||
pbr=True)
|
||||
|
||||
setup.cfg
|
||||
--------
|
||||
::
|
||||
|
||||
[metadata]
|
||||
name = myplugin
|
||||
version = 0.0.1
|
||||
summary = A panel plugin for OpenStack Dashboard
|
||||
description-file =
|
||||
README.rst
|
||||
author = myname
|
||||
author_email = myemail
|
||||
home-page = http://www.openstack.org/
|
||||
classifiers = [
|
||||
Environment :: OpenStack
|
||||
Framework :: Django
|
||||
Intended Audience :: Developers
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
myplugin
|
||||
|
||||
AngularJS Plugin
|
||||
================
|
||||
|
||||
If you have no plans to add AngularJS to your plugin, you may skip this section.
|
||||
In the tutorial below, we will show you how to customize your panel using
|
||||
Angular.
|
||||
|
||||
index.html
|
||||
----------
|
||||
|
||||
The index HTML is where rendering occurs and serves as an entry point for
|
||||
Angular. This is where we start to diverge from the traditional Python plugin.
|
||||
In this example, we use a Django template as the glue to our Angular template.
|
||||
Why are we going through a Django template for an Angular plugin? Long story
|
||||
short, ``base.html`` contains the navigation piece that we still need for each
|
||||
panel.
|
||||
|
||||
::
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "My panel" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header
|
||||
header="{$ 'My panel' | translate $}"
|
||||
description="{$ 'My custom panel!' | translate $}">
|
||||
</hz-page-header>
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<ng-include
|
||||
src="'{{ STATIC_URL }}dashboard/identity/myplugin/mypanel/mypanel.html'">
|
||||
</ng-include>
|
||||
{% endblock %}
|
||||
|
||||
This template contains both Django and AngularJS code. Angular is denoted by
|
||||
{$..$} while Django is denoted by {{..}} and {%..%}. This template gets
|
||||
processed twice, once by Django on the server-side and once more by Angular on
|
||||
the client-side. This means that the expressions in {{..}} and {%..%} are
|
||||
substituted with values by the time it reaches your Angular template.
|
||||
|
||||
What you chose to include in ``block main`` is entirely up to you. Since you are
|
||||
creating an Angular plugin, we recommend that you keep everything in this
|
||||
section Angular. Do not mix Python code in here! If you find yourself passing in
|
||||
Python data, do it via our REST services instead.
|
||||
|
||||
Remember to always use ``STATIC_URL`` when referencing your static resources.
|
||||
This ensures that changes to the static path in settings will continue to serve
|
||||
your static resources properly.
|
||||
|
||||
.. Note ::
|
||||
|
||||
Angular's directives are prefixed with ng. Similarly, Horizon's directives
|
||||
are prefixed with hz. You can think of them as namespaces.
|
||||
|
||||
mypanel.js
|
||||
-----------
|
||||
|
||||
Your controller is the glue between the model and the view. In this example, we
|
||||
are going to give it some fake data to render. To load more complex data,
|
||||
consider using the $http service.
|
||||
|
||||
::
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.identity.myplugin.mypanel', [])
|
||||
.controller('horizon.dashboard.identity.myPluginController',
|
||||
myPluginController);
|
||||
|
||||
myPluginController.$inject = [ '$http' ];
|
||||
|
||||
function myPluginController($http) {
|
||||
var ctrl = this;
|
||||
ctrl.items = [
|
||||
{ name: 'abc', id: 123 },
|
||||
{ name: 'efg', id: 345 },
|
||||
{ name: 'hij', id: 678 }
|
||||
];
|
||||
}
|
||||
})();
|
||||
|
||||
This is a basic example where we mocked the data. For exercise, load your data
|
||||
using the ``$http`` service.
|
||||
|
||||
mypanel.html
|
||||
-------------
|
||||
|
||||
This is our view. In this example, we are looping through the list of items
|
||||
provided by the controller and displaying the name and id. The important thing
|
||||
to note is the reference to our controller using the ``ng-controller``
|
||||
directive.
|
||||
|
||||
::
|
||||
|
||||
<div ng-controller="horizon.dashboard.identity.myPluginController as ctrl">
|
||||
<div>Loading data from your controller:</div>
|
||||
<ul>
|
||||
<li ng-repeat="item in ctrl.items">
|
||||
<span class="c1">{$ item.name $}</span>
|
||||
<span class="c2">{$ item.id $}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
mypanel.scss
|
||||
-------------
|
||||
|
||||
You can choose to customize your panel by providing your own scss.
|
||||
Be sure to include it in your enabled file via the ``ADD_SCSS_FILES`` setting.
|
||||
|
||||
::
|
||||
|
||||
div[ng-controller="horizon.dashboard.identity.myPluginController as ctrl"] {
|
||||
/* your custom style here */
|
||||
}
|
||||
|
||||
Installing Your Plugin
|
||||
======================
|
||||
|
||||
Now that you have a complete plugin, it is time to install and test it. The
|
||||
instructions below assume that you have a working plugin.
|
||||
|
||||
* ``plugin`` is the location of your plugin
|
||||
* ``horizon`` is the location of horizon
|
||||
* ``package`` is the complete name of your packaged plugin
|
||||
|
||||
::
|
||||
|
||||
1. Run "cd ``plugin`` & python setup.py sdist"
|
||||
2. Run "cp -rv enabled ``horizon``/openstack_dashboard/local/"
|
||||
3. Run "``horizon``/tools/with_venv.sh pip install dist/``package``.tar.gz"
|
||||
4. Restart Apache or your Django test server
|
||||
|
||||
.. Note ::
|
||||
|
||||
Step 3 installs your package into the Horizon's virtual environment. You can
|
||||
install your plugin without using ``with_venv.sh`` and ``pip``. The package
|
||||
would simply be installed in the ``PYTHON_PATH`` of the system instead.
|
||||
|
||||
If you are able to hit the URL pattern in ``urls.py`` in your browser, you have
|
||||
successfully deployed your plugin! For plugins that do not have a URL, check
|
||||
that your static resources are loaded using the browser inspector.
|
||||
|
||||
Assuming you implemented ``my_rest_api.py``, you can use a REST client to hit
|
||||
the url directly and test it. There should be many REST clients available on
|
||||
your web browser.
|
||||
|
||||
Note that you may need to rebuild your virtual environment if your plugin is not
|
||||
showing up properly. If your plugin does not show up properly, check your
|
||||
``.venv`` folder to make sure the plugin's content is as you expect.
|
||||
|
||||
.. Note ::
|
||||
|
||||
To uninstall, use ``pip uninstall``. You will also need to remove the enabled
|
||||
file from the ``local/enabled`` folder.
|
Loading…
Reference in New Issue
Block a user