remove doc file and modify install/uninstall/upgrade
shell about python-django-horizon-doc Change-Id: Ief6d7e2f4805b704919b40a58e8a2bf54c444b71 Signed-off-by: luyao <lu.yao135@zte.com.cn>
This commit is contained in:
parent
cacc1dc749
commit
64f76b55a9
@ -1,153 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Horizon.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Horizon.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/Horizon"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Horizon"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
@ -1,438 +0,0 @@
|
||||
# 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.
|
||||
#
|
||||
# Horizon documentation build configuration file, created by
|
||||
# sphinx-quickstart on Thu Oct 27 11:38:59 2011.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
#
|
||||
# Note that not all possible configuration values are present in this
|
||||
# autogenerated file.
|
||||
#
|
||||
# All configuration values have a default; values that are commented out
|
||||
# serve to show the default.
|
||||
|
||||
from __future__ import print_function
|
||||
import horizon.version
|
||||
import os
|
||||
import sys
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
|
||||
|
||||
sys.path.insert(0, ROOT)
|
||||
|
||||
# This is required for ReadTheDocs.org, but isn't a bad idea anyway.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openstack_dashboard.settings')
|
||||
|
||||
|
||||
def write_autodoc_index():
|
||||
|
||||
def find_autodoc_modules(module_name, sourcedir):
|
||||
"""returns a list of modules in the SOURCE directory."""
|
||||
modlist = []
|
||||
os.chdir(os.path.join(sourcedir, module_name))
|
||||
print("SEARCHING %s" % sourcedir)
|
||||
for root, dirs, files in os.walk("."):
|
||||
for filename in files:
|
||||
if filename == 'tests.py':
|
||||
continue
|
||||
if filename.endswith(".py"):
|
||||
# remove the pieces of the root
|
||||
elements = root.split(os.path.sep)
|
||||
# replace the leading "." with the module name
|
||||
elements[0] = module_name
|
||||
# and get the base module name
|
||||
base, extension = os.path.splitext(filename)
|
||||
if not (base == "__init__"):
|
||||
elements.append(base)
|
||||
result = ".".join(elements)
|
||||
# print result
|
||||
modlist.append(result)
|
||||
return modlist
|
||||
|
||||
RSTDIR = os.path.abspath(os.path.join(BASE_DIR, "sourcecode"))
|
||||
SRCS = [('horizon', ROOT),
|
||||
('openstack_dashboard', ROOT)]
|
||||
|
||||
EXCLUDED_MODULES = ('horizon.test',
|
||||
'openstack_dashboard.enabled',
|
||||
'openstack_dashboard.test',
|
||||
'openstack_dashboard.openstack.common',
|
||||
)
|
||||
CURRENT_SOURCES = {}
|
||||
|
||||
if not(os.path.exists(RSTDIR)):
|
||||
os.mkdir(RSTDIR)
|
||||
CURRENT_SOURCES[RSTDIR] = ['autoindex.rst']
|
||||
|
||||
INDEXOUT = open(os.path.join(RSTDIR, "autoindex.rst"), "w")
|
||||
INDEXOUT.write("""
|
||||
=================
|
||||
Source Code Index
|
||||
=================
|
||||
|
||||
.. contents::
|
||||
:depth: 1
|
||||
:local:
|
||||
|
||||
""")
|
||||
|
||||
for modulename, path in SRCS:
|
||||
sys.stdout.write("Generating source documentation for %s\n" %
|
||||
modulename)
|
||||
INDEXOUT.write("\n%s\n" % modulename.capitalize())
|
||||
INDEXOUT.write("%s\n" % ("=" * len(modulename),))
|
||||
INDEXOUT.write(".. toctree::\n")
|
||||
INDEXOUT.write(" :maxdepth: 1\n")
|
||||
INDEXOUT.write("\n")
|
||||
|
||||
MOD_DIR = os.path.join(RSTDIR, modulename)
|
||||
CURRENT_SOURCES[MOD_DIR] = []
|
||||
if not(os.path.exists(MOD_DIR)):
|
||||
os.mkdir(MOD_DIR)
|
||||
for module in find_autodoc_modules(modulename, path):
|
||||
if any([module.startswith(exclude) for exclude
|
||||
in EXCLUDED_MODULES]):
|
||||
print("Excluded module %s." % module)
|
||||
continue
|
||||
mod_path = os.path.join(path, *module.split("."))
|
||||
generated_file = os.path.join(MOD_DIR, "%s.rst" % module)
|
||||
|
||||
INDEXOUT.write(" %s/%s\n" % (modulename, module))
|
||||
|
||||
# Find the __init__.py module if this is a directory
|
||||
if os.path.isdir(mod_path):
|
||||
source_file = ".".join((os.path.join(mod_path, "__init__"),
|
||||
"py",))
|
||||
else:
|
||||
source_file = ".".join((os.path.join(mod_path), "py"))
|
||||
|
||||
CURRENT_SOURCES[MOD_DIR].append("%s.rst" % module)
|
||||
# Only generate a new file if the source has changed or we don't
|
||||
# have a doc file to begin with.
|
||||
if not os.access(generated_file, os.F_OK) or (
|
||||
os.stat(generated_file).st_mtime <
|
||||
os.stat(source_file).st_mtime):
|
||||
print("Module %s updated, generating new documentation."
|
||||
% module)
|
||||
FILEOUT = open(generated_file, "w")
|
||||
header = "The :mod:`%s` Module" % module
|
||||
FILEOUT.write("%s\n" % ("=" * len(header),))
|
||||
FILEOUT.write("%s\n" % header)
|
||||
FILEOUT.write("%s\n" % ("=" * len(header),))
|
||||
FILEOUT.write(".. automodule:: %s\n" % module)
|
||||
FILEOUT.write(" :members:\n")
|
||||
FILEOUT.write(" :undoc-members:\n")
|
||||
FILEOUT.write(" :show-inheritance:\n")
|
||||
FILEOUT.write(" :noindex:\n")
|
||||
FILEOUT.close()
|
||||
|
||||
INDEXOUT.close()
|
||||
|
||||
# Delete auto-generated .rst files for sources which no longer exist
|
||||
for directory, subdirs, files in list(os.walk(RSTDIR)):
|
||||
for old_file in files:
|
||||
if old_file not in CURRENT_SOURCES.get(directory, []):
|
||||
print("Removing outdated file for %s" % old_file)
|
||||
os.remove(os.path.join(directory, old_file))
|
||||
|
||||
|
||||
write_autodoc_index()
|
||||
|
||||
# If extensions (or modules to document with autodoc) are in another directory,
|
||||
# add these directories to sys.path here. If the directory is relative to the
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
# -- General configuration ----------------------------------------------------
|
||||
|
||||
# If your documentation needs a minimal Sphinx version, state it here.
|
||||
# needs_sphinx = '1.0'
|
||||
|
||||
# Add any Sphinx extension module names here, as strings.
|
||||
# They can be extensions coming with Sphinx (named 'sphinx.ext.*')
|
||||
# or your custom ones.
|
||||
extensions = ['sphinx.ext.autodoc',
|
||||
'sphinx.ext.todo',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.pngmath',
|
||||
'sphinx.ext.viewcode',
|
||||
'oslosphinx',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The encoding of source files.
|
||||
# source_encoding = 'utf-8-sig'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Horizon'
|
||||
copyright = u'2012, OpenStack Foundation'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = horizon.version.version_info.version_string()
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = horizon.version.version_info.release_string()
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
# language = None
|
||||
|
||||
# There are two options for replacing |today|: either, you set today to some
|
||||
# non-false value, then it is used:
|
||||
# today = ''
|
||||
# Else, today_fmt is used as the format for a strftime call.
|
||||
# today_fmt = '%B %d, %Y'
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['**/#*', '**~', '**/#*#']
|
||||
|
||||
# The reST default role (used for this markup: `text`)
|
||||
# to use for all documents.
|
||||
# default_role = None
|
||||
|
||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||
# add_function_parentheses = True
|
||||
|
||||
# If true, the current module name will be prepended to all description
|
||||
# unit titles (such as .. function::).
|
||||
# add_module_names = True
|
||||
|
||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||
# output. They are ignored by default.
|
||||
show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
# modindex_common_prefix = []
|
||||
|
||||
primary_domain = 'py'
|
||||
nitpicky = False
|
||||
|
||||
|
||||
# -- Options for HTML output --------------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
# html_theme_path = ['.']
|
||||
# html_theme = '_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
html_theme_options = {
|
||||
"nosidebar": "false"
|
||||
}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
# html_theme_path = []
|
||||
|
||||
# The name for this set of Sphinx documents. If None, it defaults to
|
||||
# "<project> v<release> documentation".
|
||||
# html_title = None
|
||||
|
||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||
# html_short_title = None
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top
|
||||
# of the sidebar.
|
||||
# html_logo = None
|
||||
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
# html_favicon = None
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||
# using the given strftime format.
|
||||
# html_last_updated_fmt = '%b %d, %Y'
|
||||
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
|
||||
html_last_updated_fmt = os.popen(git_cmd).read()
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'Horizondoc'
|
||||
|
||||
|
||||
# -- Options for LaTeX output -------------------------------------------------
|
||||
|
||||
latex_elements = {
|
||||
# The paper size ('letterpaper' or 'a4paper').
|
||||
# 'papersize': 'letterpaper',
|
||||
|
||||
# The font size ('10pt', '11pt' or '12pt').
|
||||
# 'pointsize': '10pt',
|
||||
|
||||
# Additional stuff for the LaTeX preamble.
|
||||
# 'preamble': '',
|
||||
}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass
|
||||
# [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Horizon.tex', u'Horizon Documentation',
|
||||
u'OpenStack Foundation', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# latex_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output -------------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'horizon', u'Horizon Documentation',
|
||||
[u'OpenStack'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -----------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Horizon', u'Horizon Documentation', u'OpenStack',
|
||||
'Horizon', 'One line description of project.', 'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
|
||||
# -- Options for Epub output --------------------------------------------------
|
||||
|
||||
# Bibliographic Dublin Core info.
|
||||
epub_title = u'Horizon'
|
||||
epub_author = u'OpenStack'
|
||||
epub_publisher = u'OpenStack'
|
||||
epub_copyright = u'2012, OpenStack'
|
||||
|
||||
# The language of the text. It defaults to the language option
|
||||
# or en if the language is not set.
|
||||
# epub_language = ''
|
||||
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
# epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be an ISBN number
|
||||
# or the project homepage.
|
||||
# epub_identifier = ''
|
||||
|
||||
# A unique identification for the text.
|
||||
# epub_uid = ''
|
||||
|
||||
# A tuple containing the cover image and cover page html template filenames.
|
||||
# epub_cover = ()
|
||||
|
||||
# HTML files that should be inserted before the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
# epub_pre_files = []
|
||||
|
||||
# HTML files shat should be inserted after the pages created by sphinx.
|
||||
# The format is a list of tuples containing the path and title.
|
||||
# epub_post_files = []
|
||||
|
||||
# A list of files that should not be packed into the epub file.
|
||||
# epub_exclude_files = []
|
||||
|
||||
# The depth of the table of contents in toc.ncx.
|
||||
# epub_tocdepth = 3
|
||||
|
||||
# Allow duplicate toc entries.
|
||||
# epub_tocdup = True
|
@ -1,559 +0,0 @@
|
||||
==================
|
||||
Contributing Guide
|
||||
==================
|
||||
|
||||
First and foremost, thank you for wanting to contribute! It's the only way
|
||||
open source works!
|
||||
|
||||
Before you dive into writing patches, here are some of the basics:
|
||||
|
||||
* Project page: http://launchpad.net/horizon
|
||||
* Bug tracker: https://bugs.launchpad.net/horizon
|
||||
* Source code: https://github.com/openstack/horizon
|
||||
* Code review: https://review.openstack.org/#q,status:open+project:openstack/horizon,n,z
|
||||
* Continuous integration:
|
||||
|
||||
* Jenkins: https://jenkins.openstack.org
|
||||
* Zuul: http://status.openstack.org/zuul
|
||||
* IRC Channel: #openstack-horizon on Freenode.
|
||||
|
||||
Making Contributions
|
||||
====================
|
||||
|
||||
Getting Started
|
||||
---------------
|
||||
|
||||
We'll start by assuming you've got a working checkout of the repository (if
|
||||
not then please see the :doc:`quickstart`).
|
||||
|
||||
Second, you'll need to take care of a couple administrative tasks:
|
||||
|
||||
#. Create an account on Launchpad.
|
||||
#. Sign the `OpenStack Contributor License Agreement`_ and follow the associated
|
||||
instructions to verify your signature.
|
||||
#. Join the `Horizon Developers`_ team on Launchpad.
|
||||
#. Follow the `instructions for setting up git-review`_ in your
|
||||
development environment.
|
||||
|
||||
Whew! Got all that? Okay! You're good to go.
|
||||
|
||||
Ways To Contribute
|
||||
------------------
|
||||
|
||||
The easiest way to get started with Horizon's code is to pick a bug on
|
||||
Launchpad that interests you, and start working on that. Alternatively, if
|
||||
there's an OpenStack API feature you would like to see implemented in Horizon
|
||||
feel free to try building it.
|
||||
|
||||
If those are too big, there are lots of great ways to get involved without
|
||||
plunging in head-first:
|
||||
|
||||
* Report bugs, triage new tickets, and review old tickets on
|
||||
the `bug tracker`_.
|
||||
* Propose ideas for improvements via `Launchpad Blueprints`_, via the
|
||||
mailing list on the project page, or on IRC.
|
||||
* Write documentation!
|
||||
* Write unit tests for untested code!
|
||||
* Help improve the `User Experience Design`_ or contribute to the `Persona Working Group`_.
|
||||
|
||||
.. _`bug tracker`: https://bugs.launchpad.net/horizon
|
||||
.. _`Launchpad Blueprints`: https://blueprints.launchpad.net/horizon
|
||||
.. _`User Experience Design`: https://wiki.openstack.org/wiki/UX#Getting_Started
|
||||
.. _`Persona Working Group`: https://wiki.openstack.org/wiki/Personas
|
||||
|
||||
|
||||
Choosing Issues To Work On
|
||||
--------------------------
|
||||
|
||||
In general, if you want to write code, there are three cases for issues
|
||||
you might want to work on:
|
||||
|
||||
#. Confirmed bugs
|
||||
#. Approved blueprints (features)
|
||||
#. New bugs you've discovered
|
||||
|
||||
If you have an idea for a new feature that isn't in a blueprint yet, it's
|
||||
a good idea to write the blueprint first so you don't end up writing a bunch
|
||||
of code that may not go in the direction the community wants.
|
||||
|
||||
For bugs, open the bug first, but if you can reproduce the bug reliably and
|
||||
identify its cause then it's usually safe to start working on it. However,
|
||||
getting independent confirmation (and verifying that it's not a duplicate)
|
||||
is always a good idea if you can be patient.
|
||||
|
||||
After You Write Your Patch
|
||||
--------------------------
|
||||
|
||||
Once you've made your changes, there are a few things to do:
|
||||
|
||||
* Make sure the unit tests pass: ``./run_tests.sh``
|
||||
* Make sure PEP8 is clean: ``./run_tests.sh --pep8``
|
||||
* Make sure your code is ready for translation: ``./run_tests.sh --pseudo de`` See the Translatability section below for details.
|
||||
* Make sure your code is up-to-date with the latest master: ``git pull --rebase``
|
||||
* Finally, run ``git review`` to upload your changes to Gerrit for review.
|
||||
|
||||
The Horizon core developers will be notified of the new review and will examine
|
||||
it in a timely fashion, either offering feedback or approving it to be merged.
|
||||
If the review is approved, it is sent to Jenkins to verify the unit tests pass
|
||||
and it can be merged cleanly. Once Jenkins approves it, the change will be
|
||||
merged to the master repository and it's time to celebrate!
|
||||
|
||||
.. _`OpenStack Contributor License Agreement`: http://wiki.openstack.org/CLA
|
||||
.. _`OpenStack Contributors`: https://launchpad.net/~openstack-cla
|
||||
.. _`Horizon Developers`: https://launchpad.net/~horizon
|
||||
.. _`instructions for setting up git-review`: http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
Etiquette
|
||||
=========
|
||||
|
||||
The community's guidelines for etiquette are fairly simple:
|
||||
|
||||
* Treat everyone respectfully and professionally.
|
||||
* If a bug is "in progress" in the bug tracker, don't start working on it
|
||||
without contacting the author. Try on IRC, or via the launchpad email
|
||||
contact link. If you don't get a response after a reasonable time, then go
|
||||
ahead. Checking first avoids duplicate work and makes sure nobody's toes
|
||||
get stepped on.
|
||||
* If a blueprint is assigned, even if it hasn't been started, be sure you
|
||||
contact the assignee before taking it on. These larger issues often have a
|
||||
history of discussion or specific implementation details that the assignee
|
||||
may be aware of that you are not.
|
||||
* Please don't re-open tickets closed by a core developer. If you disagree with
|
||||
the decision on the ticket, the appropriate solution is to take it up on
|
||||
IRC or the mailing list.
|
||||
* Give credit where credit is due; if someone helps you substantially with
|
||||
a piece of code, it's polite (though not required) to thank them in your
|
||||
commit message.
|
||||
|
||||
Translatability
|
||||
===============
|
||||
Horizon gets translated into multiple languages. The pseudo translation tool
|
||||
can be used to verify that code is ready to be translated. The pseudo tool
|
||||
replaces a language's translation with a complete, fake translation. Then
|
||||
you can verify that your code properly displays fake translations to validate
|
||||
that your code is ready for translation.
|
||||
|
||||
Running the pseudo translation tool
|
||||
-----------------------------------
|
||||
|
||||
#. Make sure your English po file is up to date: ``./run_tests.sh --makemessages``
|
||||
#. Run the pseudo tool to create pseudo translations. For example, to replace the German translation with a pseudo translation: ``./run_tests.sh --pseudo de``
|
||||
#. Compile the catalog: ``./run_tests.sh --compilemessages``
|
||||
#. Run your development server.
|
||||
#. Log in and change to the language you pseudo translated.
|
||||
|
||||
It should look weird. More specifically, the translatable segments are going
|
||||
to start and end with a bracket and they are going to have some added
|
||||
characters. For example, "Log In" will become "[~Log In~您好яшçあ]"
|
||||
This is useful because you can inspect for the following, and consider if your
|
||||
code is working like it should:
|
||||
|
||||
* If you see a string in English it's not translatable. Should it be?
|
||||
* If you see brackets next to each other that might be concatenation. Concatenation
|
||||
can make quality translations difficult or impossible. See
|
||||
https://wiki.openstack.org/wiki/I18n/TranslatableStrings#Use_string_formating_variables.2C_never_perform_string_concatenation
|
||||
for additional information.
|
||||
* If there is unexpected wrapping/truncation there might not be enough
|
||||
space for translations.
|
||||
* If you see a string in the proper translated language, it comes from an
|
||||
external source. (That's not bad, just sometimes useful to know)
|
||||
* If you get new crashes, there is probably a bug.
|
||||
|
||||
Don't forget to cleanup any pseudo translated po files. Those don't get merged!
|
||||
|
||||
Code Style
|
||||
==========
|
||||
|
||||
As a project, Horizon adheres to code quality standards.
|
||||
|
||||
Python
|
||||
------
|
||||
|
||||
We follow PEP8_ for all our Python code, and use ``pep8.py`` (available
|
||||
via the shortcut ``./run_tests.sh --pep8``) to validate that our code
|
||||
meets proper Python style guidelines.
|
||||
|
||||
.. _PEP8: http://www.python.org/dev/peps/pep-0008/
|
||||
|
||||
Django
|
||||
------
|
||||
|
||||
Additionally, we follow `Django's style guide`_ for templates, views, and
|
||||
other miscellany.
|
||||
|
||||
.. _Django's style guide: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
|
||||
|
||||
JavaScript
|
||||
----------
|
||||
|
||||
The following standards are divided into required and recommended sections.
|
||||
Our main goal in establishing these best practices is to have code that is
|
||||
reliable, readable, and maintainable.
|
||||
|
||||
Required
|
||||
~~~~~~~~
|
||||
|
||||
|
||||
**Reliable**
|
||||
|
||||
* The code has to work on the stable and latest versions of Firefox, Chrome,
|
||||
Safari, and Opera web browsers, and on Microsoft Internet Explorer 9 and
|
||||
later.
|
||||
|
||||
* If you turned compression off during development via ``COMPRESS_ENABLED =
|
||||
False`` in local_settings.py, re-enable compression and test your code
|
||||
before submitting.
|
||||
|
||||
* Use ``===`` as opposed to ``==`` for equality checks. The ``==`` will do a
|
||||
type cast before comparing, which can lead to unwanted results.
|
||||
|
||||
.. Note ::
|
||||
If typecasting is desired, explicit casting is preferred to keep the
|
||||
meaning of your code clear.
|
||||
|
||||
* Keep document reflows to a minimum. DOM manipulation is expensive, and can
|
||||
become a performance issue. If you are accessing the DOM, make sure that you
|
||||
are doing it in the most optimized way. One example is to build up a document
|
||||
fragment and then append the fragment to the DOM in one pass instead of doing
|
||||
multiple smaller DOM updates.
|
||||
* Use “strict”, enclosing each JavaScript file inside a self-executing
|
||||
function. The self-executing function keeps the strict scoped to the file,
|
||||
so its variables and methods are not exposed to other JavaScript files in
|
||||
the product.
|
||||
|
||||
.. Note ::
|
||||
Using strict will throw exceptions for common coding errors, like
|
||||
accessing global vars, that normally are not flagged.
|
||||
|
||||
Example:
|
||||
|
||||
.. code ::
|
||||
|
||||
(function(){
|
||||
'use strict';
|
||||
// code...
|
||||
})();
|
||||
|
||||
* Use ``forEach`` | ``each`` when looping whenever possible. AngularJS, and
|
||||
jQuery both provide for each loops that provide both iteration and scope.
|
||||
|
||||
AngularJS:
|
||||
|
||||
.. code ::
|
||||
|
||||
angular.forEach(objectToIterateOver, function(value, key) {
|
||||
// loop logic
|
||||
});
|
||||
|
||||
jQuery:
|
||||
|
||||
.. code ::
|
||||
|
||||
$.each(objectToIterateOver, function( key, value ) {
|
||||
// loop logic
|
||||
});
|
||||
|
||||
|
||||
* Do not put variables or functions in the global namespace. There are several
|
||||
reasons why globals are bad, one being that all JavaScript included in an
|
||||
application runs in the same scope. The issue with that is if another script
|
||||
has the same method or variable names they overwrite each other.
|
||||
* Always put ``var`` in front of your variables. Not putting ``var`` in front
|
||||
of a variable puts that variable into the global space, see above.
|
||||
* Do not use ``eval( )``. The eval (expression) evaluates the expression
|
||||
passed to it. This can open up your code to security vulnerabilities and
|
||||
other issues.
|
||||
* Do not use '``with`` object {code}'. The ``with`` statement is used to access
|
||||
properties of an object. The issue with ``with`` is that its execution is not
|
||||
consistent, so by reading the statement in the code it is not always clear
|
||||
how it is being used.
|
||||
|
||||
|
||||
**Readable & Maintainable**
|
||||
|
||||
* Give meaningful names to methods and variables.
|
||||
* Avoid excessive nesting.
|
||||
* Avoid HTML and CSS in JS code. HTML and CSS belong in templates and
|
||||
stylesheets respectively. For example:
|
||||
|
||||
* In our HTML files, we should focus on layout.
|
||||
|
||||
1. Reduce the small/random ``<script>`` and ``<style>`` elements in HTML.
|
||||
|
||||
2. Avoid in-lining styles into element in HTML. Use attributes and
|
||||
classes instead.
|
||||
* In our JS files, we should focus on logic rather than attempting to
|
||||
manipulate/style elements.
|
||||
|
||||
1. Avoid statements such as ``element.css({property1,property2...})`` they
|
||||
belong in a CSS class.
|
||||
|
||||
2. Avoid statements such as ``$("<div><span>abc</span></div>")`` they
|
||||
belong in a HTML template file. Use ``show`` | ``hide`` | ``clone``
|
||||
elements if dynamic content is required.
|
||||
|
||||
3. Avoid using classes for detection purposes only, instead, defer to
|
||||
attributes. For example to find a div:
|
||||
.. code ::
|
||||
|
||||
<div class="something"></div>
|
||||
$(".something").html("Don't find me this way!");
|
||||
|
||||
Is better found like:
|
||||
|
||||
.. code ::
|
||||
|
||||
<div data-something></div>
|
||||
$("div[data-something]").html("You found me correctly!");
|
||||
|
||||
* Avoid commented-out code.
|
||||
* Avoid dead code.
|
||||
|
||||
**Performance**
|
||||
|
||||
* Avoid creating instances of the same object repeatedly within the same scope.
|
||||
Instead, assign the object to a variable and re-use the existing object. For
|
||||
example:
|
||||
|
||||
.. code ::
|
||||
|
||||
$(document).on('click', function() { /* do something. */ });
|
||||
$(document).on('mouseover', function() { /* do something. */ });
|
||||
|
||||
A better approach:
|
||||
|
||||
.. code ::
|
||||
|
||||
var $document = $(document);
|
||||
$document.on('click', function() { /* do something. */ });
|
||||
$document.on('mouseover', function() { /* do something. */ });
|
||||
|
||||
In the first approach a jQuery object for ``document`` is created each time.
|
||||
The second approach creates only one jQuery object and reuses it. Each object
|
||||
needs to be created, uses memory, and needs to be garbage collected.
|
||||
|
||||
Recommended
|
||||
~~~~~~~~~~~
|
||||
|
||||
|
||||
**Readable & Maintainable**
|
||||
|
||||
* Put a comment at the top of every file explaining what the purpose of this
|
||||
file is when the naming is not obvious. This guideline also applies to
|
||||
methods and variables.
|
||||
* Source-code formatting – (or “beautification”) is recommended but should be
|
||||
used with caution. Keep in mind that if you reformat an entire file that was
|
||||
not previously formatted the same way, it will mess up the diff during the
|
||||
code review. It is best to use a formatter when you are working on a new file
|
||||
by yourself, or with others who are using the same formatter. You can also
|
||||
choose to format a selected portion of a file only. Instructions for setting
|
||||
up JSHint for Eclipse, Sublime Text, Notepad++ and WebStorm/PyCharm are
|
||||
provided_.
|
||||
* Use 2 spaces for code indentation.
|
||||
* Use ``{ }`` for ``if``, ``for``, ``while`` statements, and don't combine them
|
||||
on one line.
|
||||
|
||||
.. code ::
|
||||
|
||||
// Do this //Not this // Not this
|
||||
if(x) { if(x) if(x) y =x;
|
||||
y=x; y=x;
|
||||
}
|
||||
* Use JSHint in your development environment.
|
||||
|
||||
|
||||
AngularJS
|
||||
---------
|
||||
The following standards are divided into required and recommended sections.
|
||||
|
||||
Required
|
||||
~~~~~~~~
|
||||
|
||||
* Organization: Define your Angular app under the root Angular folder (such
|
||||
as ``horizon/static/horizon/js/angular/hz.table.js``). If your application is
|
||||
small enough you can choose to lump your Controllers, Directives, Filters,
|
||||
etc.. all in the one file. But if you find your file is growing too large and
|
||||
readability is becoming an issue, consider moving functionality into their
|
||||
own files under sub folders as described in the Recommended section.
|
||||
* Separate presentation and business logic. Controllers are for business logic,
|
||||
and directives for presentation.
|
||||
|
||||
* Controllers and Services should not contain DOM references. Directives
|
||||
should.
|
||||
* Services are singletons and contain logic independent of view.
|
||||
* Scope is not the model (model is your JavaScript Objects). The scope
|
||||
references the model.
|
||||
|
||||
* Read-only in templates.
|
||||
* Write-only in controllers.
|
||||
* Since Django already uses ``{{ }}``, use ``{$ $}`` or ``{% verbatim %}``
|
||||
instead.
|
||||
* For localization in JavaScript files use either ``gettext`` or ``ngettext``.
|
||||
Only those two methods are recognized by our tools and will be included in
|
||||
the .po file after running ``./run_tests --makemessages``.
|
||||
|
||||
.. code ::
|
||||
|
||||
// recognized
|
||||
gettext("translatable text");
|
||||
ngettext("translatable text");
|
||||
|
||||
// not recognized
|
||||
var _ = gettext;
|
||||
_('translatable text');
|
||||
|
||||
$window.gettext('translatable text');
|
||||
|
||||
* For localization of AngularJS templates in Horizon, there are a couple of
|
||||
ways to do it.
|
||||
|
||||
* Using ``gettext`` or ``ngettext`` function that is passed from server to
|
||||
client. If you're only translating a few things, this methodology is ok
|
||||
to use.
|
||||
|
||||
* Use an Angular directive that will fetch a django template instead of a
|
||||
static HTML file. The advantage here is that you can now use
|
||||
``{% trans %}`` and anything else Django has to offer. You can also cache
|
||||
the page according to the locale if you know that the content is static.
|
||||
|
||||
Recommended
|
||||
~~~~~~~~~~~
|
||||
|
||||
* Use these directories: filters, directives, controllers, and templates.
|
||||
|
||||
.. Note ::
|
||||
|
||||
When you use the directory name, the file name does not have to include
|
||||
words like "directive" or "filter".
|
||||
|
||||
* Put "Ctrl" on the end of a controller file name.
|
||||
* Don't use variables like "app" that are at the highest level in the file,
|
||||
when Angular gives an alternative. For example use function chaining:
|
||||
|
||||
.. code ::
|
||||
|
||||
angular.module('my_module')
|
||||
.controller('my_controller', ['$scope', function($scope) {
|
||||
// controller code
|
||||
}]).service('my_service', ['$scope', function($scope) {
|
||||
// service code
|
||||
}]);
|
||||
|
||||
|
||||
JSHint
|
||||
------
|
||||
JSHint is a great tool to be used during your code editing to improve
|
||||
JavaScript quality by checking your code against a configurable list of checks.
|
||||
Therefore, JavaScript developers should configure their editors to use JSHint
|
||||
to warn them of any such errors so they can be addressed. Since JSHint has a
|
||||
ton of configuration options to choose from, links are provided below to the
|
||||
options Horizon wants enforced along with the instructions for setting up
|
||||
JSHint for Eclipse, Sublime Text, Notepad++ and WebStorm/PyCharm.
|
||||
|
||||
JSHint configuration file: `.jshintrc`_
|
||||
|
||||
Instructions for setting up JSHint: `JSHint setup instructions`_
|
||||
|
||||
.. Note ::
|
||||
JSHint is part of the automated unit tests performed by Jenkins. The
|
||||
automated test use the default configurations, which are less strict than
|
||||
the configurations we recommended to run in your local development
|
||||
environment.
|
||||
|
||||
.. _.jshintrc: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig/Settings#.jshintrc
|
||||
.. _JSHint setup instructions: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig
|
||||
.. _provided: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig
|
||||
|
||||
|
||||
|
||||
CSS
|
||||
---
|
||||
|
||||
Style guidelines for CSS are currently quite minimal. Do your best to make the
|
||||
code readable and well-organized. Two spaces are preferred for indentation
|
||||
so as to match both the JavaScript and HTML files.
|
||||
|
||||
|
||||
JavaScript and CSS libraries
|
||||
----------------------------
|
||||
|
||||
We do not bundle the third-party code within Horizon's source tree anymore, any
|
||||
code that is still there is just left over and will be cleaned up and packaged
|
||||
properly eventually. What we do instead, is packaging the required files as
|
||||
XStatic Python packages and adding them as dependencies to Horizon. In
|
||||
particular, when you need to add a new third-party JavaScript or CSS library to
|
||||
Horizon, follow those steps:
|
||||
|
||||
1. Check if the library is already packaged as Xstatic on PyPi, by searching
|
||||
for the library name. If it already is, go to step 5. If it is, but not in
|
||||
the right version, contact the original packager.
|
||||
2. Package the library as an Xstatic package by following the instructions in
|
||||
Xstatic documentation_.
|
||||
3. `Create a new repository on StackForge`_. Use "xstatic-core" and
|
||||
"xstatic-ptl" groups for the ACLs. Make sure to include the
|
||||
``publish-to-pypi`` job.
|
||||
4. `Setup PyPi`_ to allow OpenStack to publish your package.
|
||||
5. `Tag your release`_. That will cause it to be automatically packaged and
|
||||
released to PyPi.
|
||||
6. Add the package to global-requirements_. Make sure to mention the license.
|
||||
7. Add the package to Horizon's ``requirements.txt`` file, to its
|
||||
``settings.py``, and to the ``_scripts.html`` or ``_stylesheets.html``
|
||||
templates. Make sure to keep the order alphabetic.
|
||||
|
||||
.. _documentation: http://xstatic.rtfd.org/en/latest/packaging.html
|
||||
.. _`Create a new repository on StackForge`: http://docs.openstack.org/infra/manual/creators.html
|
||||
.. _global-requirements: https://github.com/openstack/requirements/blob/master/global-requirements.txt
|
||||
.. _`Tag your release`: http://docs.openstack.org/infra/manual/drivers.html#tagging-a-release
|
||||
.. _`Setup PyPi`: http://docs.openstack.org/infra/manual/creators.html#give-openstack-permission-to-publish-releases
|
||||
|
||||
|
||||
.. warning::
|
||||
|
||||
Note that once a package is released, you can not "unrealease" it. You
|
||||
should never attempt to modify, delete or rename a released package without
|
||||
a lot of careful planning and feedback from all projects that use it.
|
||||
|
||||
For the purpose of fixing packaging mistakes, XStatic has the build number
|
||||
mechanism. Simply fix the error, increment the build number and release the
|
||||
newer package.
|
||||
|
||||
|
||||
HTML
|
||||
----
|
||||
|
||||
Again, readability is paramount; however be conscientious of how the browser
|
||||
will handle whitespace when rendering the output. Two spaces is the preferred
|
||||
indentation style to match all front-end code.
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
Horizon's documentation is written in reStructuredText and uses Sphinx for
|
||||
additional parsing and functionality, and should follow
|
||||
standard practices for writing reST. This includes:
|
||||
|
||||
* Flow paragraphs such that lines wrap at 80 characters or less.
|
||||
* Use proper grammar, spelling, capitalization and punctuation at all times.
|
||||
* Make use of Sphinx's autodoc feature to document modules, classes
|
||||
and functions. This keeps the docs close to the source.
|
||||
* Where possible, use Sphinx's cross-reference syntax (e.g.
|
||||
``:class:`~horizon.foo.Bar```) when referring to other Horizon components.
|
||||
The better-linked our docs are, the easier they are to use.
|
||||
|
||||
Be sure to generate the documentation before submitting a patch for review.
|
||||
Unexpected warnings often appear when building the documentation, and slight
|
||||
reST syntax errors frequently cause links or cross-references not to work
|
||||
correctly.
|
||||
|
||||
Conventions
|
||||
-----------
|
||||
|
||||
Simply by convention, we have a few rules about naming:
|
||||
|
||||
* The term "project" is used in place of Keystone's "tenant" terminology
|
||||
in all user-facing text. The term "tenant" is still used in API code to
|
||||
make things more obvious for developers.
|
||||
|
||||
* The term "dashboard" refers to a top-level dashboard class, and "panel" to
|
||||
the sub-items within a dashboard. Referring to a panel as a dashboard is
|
||||
both confusing and incorrect.
|
@ -1,45 +0,0 @@
|
||||
==========================
|
||||
Frequently Asked Questions
|
||||
==========================
|
||||
|
||||
What is the relationship between ``Dashboards``, ``Panels``, and navigation?
|
||||
|
||||
The navigational structure is strongly encouraged to flow from
|
||||
``Dashboard`` objects as top-level navigation items to ``Panel`` objects as
|
||||
sub-navigation items as in the current implementation. Template tags
|
||||
are provided to automatically generate this structure.
|
||||
|
||||
That said, you are not required to use the provided tools and can write
|
||||
templates and URLconfs by hand to create any desired structure.
|
||||
|
||||
Does a panel have to be an app in ``INSTALLED_APPS``?
|
||||
|
||||
A panel can live in any Python module. It can be a standalone which ties
|
||||
into an existing dashboard, or it can be contained alongside others within
|
||||
a larger dashboard "app". There is no strict enforcement here. Python
|
||||
is "a language for consenting adults." A module containing a Panel does
|
||||
not need to be added to ``INSTALLED_APPS``, but this is a common and
|
||||
convenient way to load a standalone panel.
|
||||
|
||||
Could I hook an external service into a panel using, for example, an iFrame?
|
||||
|
||||
Panels are just entry-points to hook views into the larger dashboard
|
||||
navigational structure and enforce common attributes like RBAC. The
|
||||
view and corresponding templates can contain anything you would like,
|
||||
including iFrames.
|
||||
|
||||
What does this mean for visual design?
|
||||
|
||||
The ability to add an arbitrary number of top-level navigational items
|
||||
(``Dashboard`` objects) poses a new design challenge. Horizon's lead
|
||||
designer has taken on the challenge of providing a reference design
|
||||
for Horizon which supports this possibility.
|
||||
|
||||
What browsers are supported?
|
||||
|
||||
Horizon is primarily tested and supported on the latest version of Firefox,
|
||||
the latest version of Chrome, and IE9+. Issues related to Safari and Opera
|
||||
will also be considered. The list of supported browsers and versions is
|
||||
informally documented on the `Browser Support wiki page
|
||||
<https://wiki.openstack.org/wiki/Horizon/BrowserSupport>`_.
|
||||
|
@ -1,24 +0,0 @@
|
||||
========
|
||||
Glossary
|
||||
========
|
||||
|
||||
Horizon
|
||||
|
||||
The OpenStack dashboard project. Also the name of the top-level
|
||||
Python object which handles registration for the app.
|
||||
|
||||
Dashboard
|
||||
|
||||
A Python class representing a top-level navigation item (e.g. "project")
|
||||
which provides a consistent API for Horizon-compatible applications.
|
||||
|
||||
Panel
|
||||
|
||||
A Python class representing a sub-navigation item (e.g. "instances")
|
||||
which contains all the necessary logic (views, forms, tests, etc.) for
|
||||
that interface.
|
||||
|
||||
Project
|
||||
|
||||
Used in user-facing text in place of the term "Tenant" which is Keystone's
|
||||
word.
|
@ -1,132 +0,0 @@
|
||||
..
|
||||
Copyright 2012 OpenStack Foundation
|
||||
All Rights Reserved.
|
||||
|
||||
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.
|
||||
|
||||
========================================
|
||||
Horizon: The OpenStack Dashboard Project
|
||||
========================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Horizon is the canonical implementation of `OpenStack's Dashboard
|
||||
<https://github.com/openstack/horizon>`_, which provides a web based user
|
||||
interface to OpenStack services including Nova, Swift, Keystone, etc.
|
||||
|
||||
For a more in-depth look at Horizon and its architecture, see the
|
||||
:doc:`Introduction to Horizon <intro>`.
|
||||
|
||||
To learn what you need to know to get going, see the :doc:`quickstart`.
|
||||
|
||||
Using Horizon
|
||||
=============
|
||||
|
||||
How to use Horizon in your own projects.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
topics/install
|
||||
topics/deployment
|
||||
topics/settings
|
||||
topics/customizing
|
||||
|
||||
Developer Docs
|
||||
==============
|
||||
|
||||
For those wishing to develop Horizon itself, or go in-depth with building
|
||||
your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes,
|
||||
the following documentation is provided.
|
||||
|
||||
General information
|
||||
-------------------
|
||||
|
||||
Brief guides to areas of interest and importance when developing Horizon.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
intro
|
||||
quickstart
|
||||
topics/tutorial
|
||||
contributing
|
||||
testing
|
||||
|
||||
Topic Guides
|
||||
------------
|
||||
|
||||
Information on how to work with specific areas of Horizon can be found in
|
||||
the following topic guides.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
topics/workflows
|
||||
topics/tables
|
||||
topics/policy
|
||||
topics/testing
|
||||
topics/table_actions
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
In-depth documentation for Horizon and its APIs.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
ref/run_tests
|
||||
ref/horizon
|
||||
ref/workflows
|
||||
ref/tables
|
||||
ref/tabs
|
||||
ref/forms
|
||||
ref/middleware
|
||||
ref/context_processors
|
||||
ref/decorators
|
||||
ref/exceptions
|
||||
ref/test
|
||||
ref/local_conf
|
||||
|
||||
Source Code Reference
|
||||
---------------------
|
||||
|
||||
Auto-generated reference for the complete source code.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
sourcecode/autoindex
|
||||
|
||||
Release Notes
|
||||
=============
|
||||
|
||||
.. toctree::
|
||||
:glob:
|
||||
:maxdepth: 1
|
||||
|
||||
releases/*
|
||||
|
||||
Information
|
||||
===========
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
faq
|
||||
glossary
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
@ -1,121 +0,0 @@
|
||||
==============
|
||||
Horizon Basics
|
||||
==============
|
||||
|
||||
Values
|
||||
======
|
||||
|
||||
"Think simple" as my old master used to say - meaning reduce
|
||||
the whole of its parts into the simplest terms, getting back
|
||||
to first principles.
|
||||
|
||||
-- Frank Lloyd Wright
|
||||
|
||||
Horizon holds several key values at the core of its design and architecture:
|
||||
|
||||
* Core Support: Out-of-the-box support for all core OpenStack projects.
|
||||
* Extensible: Anyone can add a new component as a "first-class citizen".
|
||||
* Manageable: The core codebase should be simple and easy-to-navigate.
|
||||
* Consistent: Visual and interaction paradigms are maintained throughout.
|
||||
* Stable: A reliable API with an emphasis on backwards-compatibility.
|
||||
* Usable: Providing an *awesome* interface that people *want* to use.
|
||||
|
||||
The only way to attain and uphold those ideals is to make it *easy* for
|
||||
developers to implement those values.
|
||||
|
||||
History
|
||||
=======
|
||||
|
||||
Horizon started life as a single app to manage OpenStack's compute project.
|
||||
As such, all it needed was a set of views, templates, and API calls.
|
||||
|
||||
From there it grew to support multiple OpenStack projects and APIs gradually,
|
||||
arranged rigidly into "dash" and "syspanel" groupings.
|
||||
|
||||
During the "Diablo" release cycle an initial plugin system was added using
|
||||
signals to hook in additional URL patterns and add links into the "dash"
|
||||
and "syspanel" navigation.
|
||||
|
||||
This incremental growth served the goal of "Core Support" phenomenally, but
|
||||
left "Extensible" and "Manageable" behind. And while the other key values took
|
||||
shape of their own accord, it was time to re-architect for an extensible,
|
||||
modular future.
|
||||
|
||||
|
||||
The Current Architecture & How It Meets Our Values
|
||||
==================================================
|
||||
|
||||
At its core, **Horizon should be a registration pattern for
|
||||
applications to hook into**. Here's what that means and how it is
|
||||
implemented in terms of our values:
|
||||
|
||||
Core Support
|
||||
------------
|
||||
|
||||
Horizon ships with three central dashboards, a "User Dashboard", a
|
||||
"System Dashboard", and a "Settings" dashboard. Between these three they
|
||||
cover the core OpenStack applications and deliver on Core Support.
|
||||
|
||||
The Horizon application also ships with a set of API abstractions
|
||||
for the core OpenStack projects in order to provide a consistent, stable set
|
||||
of reusable methods for developers. Using these abstractions, developers
|
||||
working on Horizon don't need to be intimately familiar with the APIs of
|
||||
each OpenStack project.
|
||||
|
||||
Extensible
|
||||
----------
|
||||
|
||||
A Horizon dashboard application is based around the :class:`~horizon.Dashboard`
|
||||
class that provides a consistent API and set of capabilities for both
|
||||
core OpenStack dashboard apps shipped with Horizon and equally for third-party
|
||||
apps. The :class:`~horizon.Dashboard` class is treated as a top-level
|
||||
navigation item.
|
||||
|
||||
Should a developer wish to provide functionality within an existing dashboard
|
||||
(e.g. adding a monitoring panel to the user dashboard) the simple registration
|
||||
pattern makes it possible to write an app which hooks into other dashboards
|
||||
just as easily as creating a new dashboard. All you have to do is import the
|
||||
dashboard you wish to modify.
|
||||
|
||||
Manageable
|
||||
----------
|
||||
|
||||
Within the application, there is a simple method for registering a
|
||||
:class:`~horizon.Panel` (sub-navigation items). Each panel contains the
|
||||
necessary logic (views, forms, tests, etc.) for that interface. This granular
|
||||
breakdown prevents files (such as ``api.py``) from becoming thousands of
|
||||
lines long and makes code easy to find by correlating it directly to the
|
||||
navigation.
|
||||
|
||||
Consistent
|
||||
----------
|
||||
|
||||
By providing the necessary core classes to build from, as well as a
|
||||
solid set of reusable templates and additional tools (base form classes,
|
||||
base widget classes, template tags, and perhaps even class-based views)
|
||||
we can maintain consistency across applications.
|
||||
|
||||
Stable
|
||||
------
|
||||
|
||||
By architecting around these core classes and reusable components we
|
||||
create an implicit contract that changes to these components will be
|
||||
made in the most backwards-compatible ways whenever possible.
|
||||
|
||||
Usable
|
||||
------
|
||||
|
||||
Ultimately that's up to each and every developer that touches the code,
|
||||
but if we get all the other goals out of the way then we are free to focus
|
||||
on the best possible experience.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`Quickstart <quickstart>`
|
||||
A short guide to getting started with using Horizon.
|
||||
|
||||
:doc:`Frequently Asked Questions <faq>`
|
||||
Common questions and answers.
|
||||
|
||||
:doc:`Glossary <glossary>`
|
||||
Common terms and their definitions.
|
@ -1,314 +0,0 @@
|
||||
==========
|
||||
Quickstart
|
||||
==========
|
||||
|
||||
.. Note ::
|
||||
|
||||
This section has been tested for Horizon on Ubuntu (12.04-64) and Fedora-based (RHEL 6.4) distributions. Feel free to add notes and any changes according to your experiences or operating system.
|
||||
|
||||
Linux Systems
|
||||
=============
|
||||
|
||||
Install the prerequisite packages.
|
||||
|
||||
On Ubuntu::
|
||||
|
||||
> sudo apt-get install git python-dev python-virtualenv libssl-dev libffi-dev
|
||||
|
||||
On Fedora-based distributions (e.g., Fedora/RHEL/CentOS/Scientific Linux)::
|
||||
|
||||
> sudo yum install gcc git-core python-devel python-virtualenv openssl-devel libffi-devel which
|
||||
|
||||
Setup
|
||||
=====
|
||||
|
||||
To setup a Horizon development environment simply clone the Horizon git
|
||||
repository from http://github.com/openstack/horizon and execute the
|
||||
``run_tests.sh`` script from the root folder (see :doc:`ref/run_tests`)::
|
||||
|
||||
> git clone https://github.com/openstack/horizon.git
|
||||
> cd horizon
|
||||
> ./run_tests.sh
|
||||
|
||||
.. note::
|
||||
|
||||
Running ``run_tests.sh`` will build a virtualenv, ``.venv``, where all the
|
||||
python dependencies for Horizon are installed and referenced. After the
|
||||
dependencies are installed, the unit test suites in the Horizon repo will be
|
||||
executed. There should be no errors from the tests.
|
||||
|
||||
Next you will need to setup your Django application config by copying ``openstack_dashboard/local/local_settings.py.example`` to ``openstack_dashboard/local/local_settings.py``. To do this quickly you can use the following command::
|
||||
|
||||
> cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
|
||||
|
||||
.. note::
|
||||
|
||||
To add new settings or customize existing settings, modify the ``local_settings.py`` file.
|
||||
|
||||
Horizon assumes a single end-point for OpenStack services which defaults to
|
||||
the local host (127.0.0.1), as is the default in DevStack. If this is not the
|
||||
case change the ``OPENSTACK_HOST`` setting in the
|
||||
``openstack_dashboard/local/local_settings.py`` file, to the actual IP address
|
||||
of the OpenStack end-point Horizon should use.
|
||||
|
||||
You can save changes you made to
|
||||
``openstack_dashboard/local/local_settings.py`` with the following command::
|
||||
|
||||
> python manage.py migrate_settings --gendiff
|
||||
|
||||
.. note::
|
||||
|
||||
This creates a ``local_settings.diff`` file which is a diff between
|
||||
``local_settings.py`` and ``local_settings.py.example``
|
||||
|
||||
If you upgrade Horizon, you might need to update your
|
||||
``openstack_dashboard/local/local_settings.py`` file with new parameters from
|
||||
``openstack_dashboard/local/local_settings.py.example`` to do so, first update
|
||||
Horizon::
|
||||
|
||||
> git remote update && git pull --ff-only origin master
|
||||
|
||||
Then update your ``openstack_dashboard/local/local_settings.py`` file::
|
||||
|
||||
> mv openstack_dashboard/local/local_settings.py openstack_dashboard/local/local_settings.py.old
|
||||
> python manage.py migrate_settings
|
||||
|
||||
.. note::
|
||||
|
||||
This applies ``openstack_dashboard/local/local_settings.diff`` on
|
||||
``openstack_dashboard/local/local_settings.py.example`` to regenerate an
|
||||
``openstack_dashboard/local/local_settings.py`` file.
|
||||
The migration can sometimes have difficulties to migrate some settings, if
|
||||
this happens you will be warned with a conflict message pointing to an
|
||||
``openstack_dashboard/local/local_settings.py_Some_DateTime.rej`` file.
|
||||
In this file, you will see the lines which could not be automatically
|
||||
changed and you will have to redo only these few changes manually instead
|
||||
of modifying the full
|
||||
``openstack_dashboard/local/local_settings.py.example`` file.
|
||||
|
||||
When all settings have been migrated, it is safe to regenerate a clean diff in
|
||||
order to prevent Conflicts for future migrations::
|
||||
|
||||
> mv openstack_dashboard/local/local_settings.diff openstack_dashboard/local/local_settings.diff.old
|
||||
> python manage.py migrate_settings --gendiff
|
||||
|
||||
To start the Horizon development server use ``run_tests.sh``::
|
||||
|
||||
> ./run_tests.sh --runserver localhost:9000
|
||||
|
||||
.. note::
|
||||
|
||||
The default port for runserver is 8000 which is already consumed by
|
||||
heat-api-cfn in DevStack. If not running in DevStack
|
||||
`./run_tests.sh --runserver` will start the test server at
|
||||
`http://localhost:8000`.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
The ``run_tests.sh`` script provides wrappers around ``manage.py``.
|
||||
For more information on manage.py which is a django, see
|
||||
`https://docs.djangoproject.com/en/dev/ref/django-admin/`
|
||||
|
||||
|
||||
Once the Horizon server is running, point a web browser to http://localhost:9000
|
||||
or to the IP and port the server is listening for.
|
||||
|
||||
.. note::
|
||||
|
||||
The ``DevStack`` project (http://devstack.org/) can be used to install
|
||||
an OpenStack development environment from scratch. For a local.conf that
|
||||
enables most services that Horizon supports managing see
|
||||
:doc:`local.conf <ref/local_conf>`
|
||||
|
||||
.. note::
|
||||
|
||||
The minimum required set of OpenStack services running includes the
|
||||
following:
|
||||
|
||||
* Nova (compute, api, scheduler, and network)
|
||||
* Glance
|
||||
* Keystone
|
||||
* Neutron (unless nova-network is used)
|
||||
|
||||
Horizon provides optional support for other services.
|
||||
See :ref:`system-requirements-label` for the supported services.
|
||||
If Keystone endpoint for a service is configured, Horizon detects it
|
||||
and enables its support automatically.
|
||||
|
||||
|
||||
Editing Horizon's Source
|
||||
========================
|
||||
|
||||
Although DevStack installs and configures an instance of Horizon when running
|
||||
stack.sh, the preferred development setup follows the instructions above on the
|
||||
server/VM running DevStack. The are several advantages to maintaining a
|
||||
separate copy of the Horizon repo, rather than editing the devstack installed
|
||||
copy.
|
||||
|
||||
* Source code changes aren't as easily lost when running unstack.sh/stack.sh
|
||||
* The development server picks up source code changes (other than JavaScript
|
||||
and CSS due to compression and compilation) while still running.
|
||||
* Log messages and print statements go directly to the console.
|
||||
* Debugging with pdb becomes much simpler to interact with.
|
||||
|
||||
.. Note::
|
||||
JavaScript and CSS changes require a development server restart. Also,
|
||||
forcing a refresh of the page (e.g. using Shift-F5) in the browser is
|
||||
required to pull down non-cached versions of the CSS and JavaScript. The
|
||||
default setting in Horizon is to do compilation and compression of these
|
||||
files at server startup. If you have configured your local copy to do
|
||||
offline compression, more steps are required.
|
||||
|
||||
|
||||
Horizon's Structure
|
||||
===================
|
||||
|
||||
This project is a bit different from other OpenStack projects in that it has
|
||||
two very distinct components underneath it: ``horizon``, and
|
||||
``openstack_dashboard``.
|
||||
|
||||
The ``horizon`` directory holds the generic libraries and components that can
|
||||
be used in any Django project.
|
||||
|
||||
The ``openstack_dashboard`` directory contains a reference Django project that
|
||||
uses ``horizon``.
|
||||
|
||||
For development, both pieces share an environment which (by default) is
|
||||
built with the ``tools/install_venv.py`` script. That script creates a
|
||||
virtualenv and installs all the necessary packages.
|
||||
|
||||
If dependencies are added to either ``horizon`` or ``openstack_dashboard``,
|
||||
they should be added to ``requirements.txt``.
|
||||
|
||||
.. important::
|
||||
|
||||
If you do anything which changes the environment (adding new dependencies
|
||||
or renaming directories are both great examples) be sure to increment the
|
||||
``environment_version`` counter in :doc:`run_tests.sh <ref/run_tests>`.
|
||||
|
||||
Project
|
||||
=======
|
||||
|
||||
Dashboard configuration
|
||||
-----------------------
|
||||
|
||||
To add a new dashboard to your project, you need to add a configuration file to
|
||||
``openstack_dashboard/local/enabled`` directory. For more information on this,
|
||||
see :ref:`pluggable-settings-label`.
|
||||
|
||||
There is also an alternative way to add a new dashboard, by adding it to
|
||||
Django's ``INSTALLED_APPS`` setting. For more information about this, see
|
||||
:ref:`dashboards`. However, please note that the recommended way is to take
|
||||
advantage of the pluggable settings feature.
|
||||
|
||||
URLs
|
||||
----
|
||||
|
||||
Then you add a single line to your project's ``urls.py``::
|
||||
|
||||
url(r'', include(horizon.urls)),
|
||||
|
||||
Those urls are automatically constructed based on the registered Horizon apps.
|
||||
If a different URL structure is desired it can be constructed by hand.
|
||||
|
||||
Templates
|
||||
---------
|
||||
|
||||
Pre-built template tags generate navigation. In your ``nav.html``
|
||||
template you might have the following::
|
||||
|
||||
{% load horizon %}
|
||||
|
||||
<div class='nav'>
|
||||
{% horizon_main_nav %}
|
||||
</div>
|
||||
|
||||
And in your ``sidebar.html`` you might have::
|
||||
|
||||
{% load horizon %}
|
||||
|
||||
<div class='sidebar'>
|
||||
{% horizon_dashboard_nav %}
|
||||
</div>
|
||||
|
||||
These template tags are aware of the current "active" dashboard and panel
|
||||
via template context variables and will render accordingly.
|
||||
|
||||
Application
|
||||
===========
|
||||
|
||||
Structure
|
||||
---------
|
||||
|
||||
An application would have the following structure (we'll use project as
|
||||
an example)::
|
||||
|
||||
project/
|
||||
|---__init__.py
|
||||
|---dashboard.py <-----Registers the app with Horizon and sets dashboard properties
|
||||
|---overview/
|
||||
|---images/
|
||||
|-- images
|
||||
|-- __init__.py
|
||||
|---panel.py <-----Registers the panel in the app and defines panel properties
|
||||
|-- snapshots/
|
||||
|-- templates/
|
||||
|-- tests.py
|
||||
|-- urls.py
|
||||
|-- views.py
|
||||
...
|
||||
...
|
||||
|
||||
Dashboard Classes
|
||||
-----------------
|
||||
|
||||
Inside of ``dashboard.py`` you would have a class definition and the registration
|
||||
process::
|
||||
|
||||
import horizon
|
||||
|
||||
....
|
||||
# ObjectStorePanels is an example for a PanelGroup
|
||||
# for panel classes in general, see below
|
||||
class ObjectStorePanels(horizon.PanelGroup):
|
||||
slug = "object_store"
|
||||
name = _("Object Store")
|
||||
panels = ('containers',)
|
||||
|
||||
|
||||
class Project(horizon.Dashboard):
|
||||
name = _("Project") # Appears in navigation
|
||||
slug = "project" # Appears in URL
|
||||
# panels may be strings or refer to classes, such as
|
||||
# ObjectStorePanels
|
||||
panels = (BasePanels, NetworkPanels, ObjectStorePanels)
|
||||
default_panel = 'overview'
|
||||
...
|
||||
|
||||
horizon.register(Project)
|
||||
|
||||
Panel Classes
|
||||
-------------
|
||||
|
||||
To connect a :class:`~horizon.Panel` with a :class:`~horizon.Dashboard` class
|
||||
you register it in a ``panel.py`` file like so::
|
||||
|
||||
import horizon
|
||||
|
||||
from openstack_dashboard.dashboards.project import dashboard
|
||||
|
||||
|
||||
class Images(horizon.Panel):
|
||||
name = "Images"
|
||||
slug = 'images'
|
||||
permissions = ('openstack.roles.admin', 'my.other.permission',)
|
||||
|
||||
|
||||
# You could also register your panel with another application's dashboard
|
||||
dashboard.Project.register(Images)
|
||||
|
||||
By default a :class:`~horizon.Panel` class looks for a ``urls.py`` file in the
|
||||
same directory as ``panel.py`` to include in the rollup of url patterns from
|
||||
panels to dashboards to Horizon, resulting in a wholly extensible, configurable
|
||||
URL structure.
|
@ -1,6 +0,0 @@
|
||||
==========================
|
||||
Horizon Context Processors
|
||||
==========================
|
||||
|
||||
.. automodule:: horizon.context_processors
|
||||
:members:
|
@ -1,6 +0,0 @@
|
||||
==================
|
||||
Horizon Decorators
|
||||
==================
|
||||
|
||||
.. automodule:: horizon.decorators
|
||||
:members:
|
@ -1,6 +0,0 @@
|
||||
==================
|
||||
Horizon Exceptions
|
||||
==================
|
||||
|
||||
.. automodule:: horizon.exceptions
|
||||
:members:
|
@ -1,98 +0,0 @@
|
||||
=============
|
||||
Horizon Forms
|
||||
=============
|
||||
|
||||
Horizon ships with some very useful base form classes, form fields,
|
||||
class-based views, and javascript helpers which streamline most of the common
|
||||
tasks related to form handling.
|
||||
|
||||
Form Classes
|
||||
============
|
||||
|
||||
.. automodule:: horizon.forms.base
|
||||
:members:
|
||||
|
||||
Form Fields
|
||||
===========
|
||||
|
||||
.. automodule:: horizon.forms.fields
|
||||
:members:
|
||||
|
||||
Form Views
|
||||
==========
|
||||
|
||||
.. automodule:: horizon.forms.views
|
||||
:members:
|
||||
|
||||
Forms Javascript
|
||||
================
|
||||
|
||||
Switchable Fields
|
||||
-----------------
|
||||
|
||||
By marking fields with the ``"switchable"`` and ``"switched"`` classes along
|
||||
with defining a few data attributes you can programmatically hide, show,
|
||||
and rename fields in a form.
|
||||
|
||||
The triggers are fields using a ``select`` input widget, marked with the
|
||||
"switchable" class, and defining a "data-slug" attribute. When they are changed,
|
||||
any input with the ``"switched"`` class and defining a ``"data-switch-on"``
|
||||
attribute which matches the ``select`` input's ``"data-slug"`` attribute will be
|
||||
evaluated for necessary changes. In simpler terms, if the ``"switched"`` target
|
||||
input's ``"switch-on"`` matches the ``"slug"`` of the ``"switchable"`` trigger
|
||||
input, it gets switched. Simple, right?
|
||||
|
||||
The ``"switched"`` inputs also need to define states. For each state in which
|
||||
the input should be shown, it should define a data attribute like the
|
||||
following: ``data-<slug>-<value>="<desired label>"``. When the switch event
|
||||
happens the value of the ``"switchable"`` field will be compared to the
|
||||
data attributes and the correct label will be applied to the field. If
|
||||
a corresponding label for that value is *not* found, the field will
|
||||
be hidden instead.
|
||||
|
||||
A simplified example is as follows::
|
||||
|
||||
source = forms.ChoiceField(
|
||||
label=_('Source'),
|
||||
choices=[
|
||||
('cidr', _('CIDR')),
|
||||
('sg', _('Security Group'))
|
||||
],
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switchable',
|
||||
'data-slug': 'source'
|
||||
})
|
||||
)
|
||||
|
||||
cidr = fields.IPField(
|
||||
label=_("CIDR"),
|
||||
required=False,
|
||||
widget=forms.TextInput(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'source',
|
||||
'data-source-cidr': _('CIDR')
|
||||
})
|
||||
)
|
||||
|
||||
security_group = forms.ChoiceField(
|
||||
label=_('Security Group'),
|
||||
required=False,
|
||||
widget=forms.Select(attrs={
|
||||
'class': 'switched',
|
||||
'data-switch-on': 'source',
|
||||
'data-source-sg': _('Security Group')
|
||||
})
|
||||
)
|
||||
|
||||
That code would create the ``"switchable"`` control field ``source``, and the
|
||||
two ``"switched"`` fields ``cidr`` and ``security group`` which are hidden or
|
||||
shown depending on the value of ``source``.
|
||||
|
||||
|
||||
NOTE: A field can only safely define one slug in its ``"switch-on"`` attribute.
|
||||
While switching on multiple fields is possible, the behavior is very hard to
|
||||
predict due to the events being fired from the various switchable fields in
|
||||
order. You generally end up just having it hidden most of the time by accident,
|
||||
so it's not recommended. Instead just add a second field to the form and control
|
||||
the two independently, then merge their results in the form's clean or handle
|
||||
methods at the end.
|
@ -1,45 +0,0 @@
|
||||
======================
|
||||
The ``horizon`` Module
|
||||
======================
|
||||
|
||||
.. module:: horizon
|
||||
|
||||
Horizon ships with a single point of contact for hooking into your project if
|
||||
you aren't developing your own :class:`~horizon.Dashboard` or
|
||||
:class:`~horizon.Panel`::
|
||||
|
||||
import horizon
|
||||
|
||||
From there you can access all the key methods you need.
|
||||
|
||||
Horizon
|
||||
=======
|
||||
|
||||
.. attribute:: urls
|
||||
|
||||
The auto-generated URLconf for Horizon. Usage::
|
||||
|
||||
url(r'', include(horizon.urls)),
|
||||
|
||||
.. autofunction:: register
|
||||
.. autofunction:: unregister
|
||||
.. autofunction:: get_absolute_url
|
||||
.. autofunction:: get_user_home
|
||||
.. autofunction:: get_dashboard
|
||||
.. autofunction:: get_default_dashboard
|
||||
.. autofunction:: get_dashboards
|
||||
|
||||
Dashboard
|
||||
=========
|
||||
|
||||
.. autoclass:: Dashboard
|
||||
:members:
|
||||
|
||||
Panel
|
||||
=====
|
||||
|
||||
.. autoclass:: Panel
|
||||
:members:
|
||||
|
||||
.. autoclass:: PanelGroup
|
||||
:members:
|
@ -1,70 +0,0 @@
|
||||
==========
|
||||
local.conf
|
||||
==========
|
||||
|
||||
Configuring DevStack for Horizon
|
||||
================================
|
||||
|
||||
Place the following content into `devstack/local.conf` to start the services
|
||||
that Horizon supports in DevStack when `stack.sh` is run.
|
||||
::
|
||||
|
||||
[[local|localrc]]
|
||||
|
||||
ADMIN_PASSWORD=pass
|
||||
MYSQL_PASSWORD=pass
|
||||
RABBIT_PASSWORD=pass
|
||||
SERVICE_PASSWORD=pass
|
||||
SERVICE_TOKEN=a682f596-76f3-11e3-b3b2-e716f9080d50
|
||||
|
||||
# Recloning will insure that your stack is up to date. The downside
|
||||
# is overhead on restarts and potentially losing a stable environment.
|
||||
# If set to yes, will reclone all repos every time stack.sh is run.
|
||||
# The default is no.
|
||||
#RECLONE=yes
|
||||
|
||||
# Note: there are several network setting changes that may be
|
||||
# required to get networking properly configured in your environment.
|
||||
# This file is just using the defaults set up by devstack.
|
||||
# For a more detailed treatment of devstack network configuration
|
||||
# options, please see: http://devstack.org/guides/single-machine.html
|
||||
|
||||
# Enable Swift (object-store) Service without replication
|
||||
enable_service s-proxy s-object s-container s-account
|
||||
SWIFT_HASH=66a3d6b56c1f479c8b4e70ab5c2000f5
|
||||
SWIFT_REPLICAS=1
|
||||
SWIFT_DATA_DIR=$DEST/data/swift
|
||||
|
||||
# enabling Neutron (network) Service
|
||||
# to use nova net rather than neutron, comment out the following group
|
||||
disable_service n-net
|
||||
enable_service q-svc
|
||||
enable_service q-agt
|
||||
enable_service q-dhcp
|
||||
enable_service q-l3
|
||||
enable_service q-meta
|
||||
enable_service q-metering
|
||||
enable_service neutron
|
||||
enable_service q-lbaas
|
||||
enable_service q-fwaas
|
||||
enable_service q-vpn
|
||||
# end group
|
||||
|
||||
# enable Sahara (data-processing) Service
|
||||
enable_service sahara
|
||||
|
||||
# enable Trove (database) Service
|
||||
enable_service trove tr-api tr-tmgr tr-cond
|
||||
|
||||
# enable Ceilometer (metering) Service
|
||||
enable_service ceilometer-acompute ceilometer-acentral ceilometer-anotification ceilometer-collector ceilometer-api
|
||||
|
||||
|
||||
# Set ``OFFLINE`` to ``True`` to configure ``stack.sh`` to run cleanly without
|
||||
# Internet access. ``stack.sh`` must have been previously run with Internet
|
||||
# access to install prerequisites and fetch repositories.
|
||||
# OFFLINE=True
|
||||
|
||||
[[post-config|$GLANCE_API_CONF]]
|
||||
[DEFAULT]
|
||||
default_store=file
|
@ -1,6 +0,0 @@
|
||||
==================
|
||||
Horizon Middleware
|
||||
==================
|
||||
|
||||
.. automodule:: horizon.middleware
|
||||
:members:
|
@ -1,263 +0,0 @@
|
||||
===========================
|
||||
The ``run_tests.sh`` Script
|
||||
===========================
|
||||
|
||||
.. contents:: Contents:
|
||||
:local:
|
||||
|
||||
Horizon ships with a script called ``run_tests.sh`` at the root of the
|
||||
repository. This script provides many crucial functions for the project,
|
||||
and also makes several otherwise complex tasks trivial for you as a
|
||||
developer.
|
||||
|
||||
First Run
|
||||
=========
|
||||
|
||||
If you start with a clean copy of the Horizon repository, the first thing
|
||||
you should do is to run ``./run_tests.sh`` from the root of the repository.
|
||||
This will do two things for you:
|
||||
|
||||
#. Set up a virtual environment for both the ``horizon`` module and
|
||||
the ``openstack_dashboard`` project using ``./tools/install_venv.py``.
|
||||
#. Run the tests for both ``horizon`` and ``openstack_dashboard`` using
|
||||
their respective environments and verify that everything is working.
|
||||
|
||||
Setting up the environment the first time can take several minutes, but only
|
||||
needs to be done once. If dependencies are added in the future, updating the
|
||||
environments will be necessary but not as time consuming.
|
||||
|
||||
I just want to run the tests!
|
||||
=============================
|
||||
|
||||
Running the full set of unit tests quickly and easily is the main goal of this
|
||||
script. All you need to do is::
|
||||
|
||||
./run_tests.sh
|
||||
|
||||
Yep, that's it. However, for a more thorough test run you can include the
|
||||
Selenium tests by using the ``--with-selenium`` flag::
|
||||
|
||||
./run_tests.sh --with-selenium
|
||||
|
||||
If you run horizon in a minimal installation VM, you will probably need
|
||||
the following (steps for Fedora 18 minimal installation):
|
||||
|
||||
#. Install these packages in the VM:
|
||||
``yum install xorg-x11-xauth xorg-x11-fonts-Type1.noarch``.
|
||||
#. Install firefox in the VM:
|
||||
``yum install firefox``.
|
||||
#. Connect to the VM by ``ssh -X``
|
||||
(if you run ``set|grep DISP``, you should see that the DISPLAY is set).
|
||||
#. Run
|
||||
``./run_tests.sh --with-selenium``.
|
||||
|
||||
Running a subset of tests
|
||||
-------------------------
|
||||
|
||||
Instead of running all tests, you can specify an individual directory, file,
|
||||
class, or method that contains test code.
|
||||
|
||||
To run the tests in the ``horizon/test/tests/tables.py`` file::
|
||||
|
||||
./run_tests.sh horizon.test.tests.tables
|
||||
|
||||
To run the tests in the `WorkflowsTests` class in
|
||||
``horizon/test/tests/workflows``::
|
||||
|
||||
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests
|
||||
|
||||
To run just the `WorkflowsTests.test_workflow_view` test method::
|
||||
|
||||
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests.test_workflow_view
|
||||
|
||||
Running the integration tests
|
||||
-----------------------------
|
||||
|
||||
The Horizon integration tests treat Horizon as a black box, and similar
|
||||
to Tempest must be run against an existing OpenStack system. These
|
||||
tests are not run by default.
|
||||
|
||||
#. Update the configuration file
|
||||
`openstack_dashboard/test/integration_tests/horizon.conf` as
|
||||
required (the format is similar to the Tempest configuration file).
|
||||
|
||||
#. Run the tests with the following command: ::
|
||||
|
||||
$ ./run_tests.sh --integration
|
||||
|
||||
Like for the unit tests, you can choose to only run a subset. ::
|
||||
|
||||
$ ./run_tests.sh --integration openstack_dashboard.test.integration_tests.tests.test_login
|
||||
|
||||
|
||||
Using Dashboard and Panel Templates
|
||||
===================================
|
||||
|
||||
Horizon has a set of convenient management commands for creating new
|
||||
dashboards and panels based on basic templates.
|
||||
|
||||
Dashboards
|
||||
----------
|
||||
|
||||
To create a new dashboard, run the following::
|
||||
|
||||
./run_tests.sh -m startdash <dash_name>
|
||||
|
||||
This will create a directory with the given dashboard name, a ``dashboard.py``
|
||||
module with the basic dashboard code filled in, and various other common
|
||||
"boilerplate" code.
|
||||
|
||||
Available options:
|
||||
|
||||
* ``--target``: the directory in which the dashboard files should be created.
|
||||
Default: A new directory within the current directory.
|
||||
|
||||
Panels
|
||||
------
|
||||
|
||||
To create a new panel, run the following::
|
||||
|
||||
./run_tests -m startpanel <panel_name>
|
||||
|
||||
This will create a directory with the given panel name, and ``panel.py``
|
||||
module with the basic panel code filled in, and various other common
|
||||
"boilerplate" code.
|
||||
|
||||
Available options:
|
||||
|
||||
* ``-d``, ``--dashboard``: The dotted python path to your dashboard app (the
|
||||
module which containers the ``dashboard.py`` file.). If not specified, the
|
||||
target dashboard should be specified in a pluggable settings file for the
|
||||
panel.
|
||||
* ``--target``: the directory in which the panel files should be created.
|
||||
If the value is ``auto`` the panel will be created as a new directory inside
|
||||
the dashboard module's directory structure. Default: A new directory within
|
||||
the current directory.
|
||||
|
||||
Give me metrics!
|
||||
================
|
||||
|
||||
You can generate various reports and metrics using command line arguments
|
||||
to ``run_tests.sh``.
|
||||
|
||||
Coverage
|
||||
--------
|
||||
|
||||
To run coverage reports::
|
||||
|
||||
./run_tests.sh --coverage
|
||||
|
||||
The reports are saved to ``./reports/`` and ``./coverage.xml``.
|
||||
|
||||
PEP8
|
||||
----
|
||||
|
||||
You can check for PEP8 violations as well::
|
||||
|
||||
./run_tests.sh --pep8
|
||||
|
||||
The results are saved to ``./pep8.txt``.
|
||||
|
||||
PyLint
|
||||
------
|
||||
|
||||
For more detailed code analysis you can run::
|
||||
|
||||
./run_tests.sh --pylint
|
||||
|
||||
The output will be saved in ``./pylint.txt``.
|
||||
|
||||
JsHint
|
||||
------
|
||||
|
||||
For code analysis of JavaScript files::
|
||||
|
||||
./run_tests.sh --jshint
|
||||
|
||||
You need to have jshint installed before running the command.
|
||||
|
||||
Tab Characters
|
||||
--------------
|
||||
|
||||
For those who dislike having a mix of tab characters and spaces for indentation
|
||||
there's a command to check for that in Python, CSS, JavaScript and HTML files::
|
||||
|
||||
./run_tests.sh --tabs
|
||||
|
||||
This will output a total "tab count" and a list of the offending files.
|
||||
|
||||
Running the development server
|
||||
==============================
|
||||
|
||||
As an added bonus, you can run Django's development server directly from
|
||||
the root of the repository with ``run_tests.sh`` like so::
|
||||
|
||||
./run_tests.sh --runserver
|
||||
|
||||
This is effectively just an alias for::
|
||||
|
||||
./tools/with_venv.sh ./manage.py runserver
|
||||
|
||||
Generating the documentation
|
||||
============================
|
||||
|
||||
You can build Horizon's documentation automatically by running::
|
||||
|
||||
./run_tests.sh --docs
|
||||
|
||||
The output is stored in ``./doc/build/html/``.
|
||||
|
||||
Updating the translation files
|
||||
==============================
|
||||
|
||||
You can update all of the translation files for both the ``horizon`` app and
|
||||
``openstack_dashboard`` project with a single command::
|
||||
|
||||
./run_tests.sh --makemessages
|
||||
|
||||
or, more compactly::
|
||||
|
||||
./run_tests.sh --m
|
||||
|
||||
Starting clean
|
||||
==============
|
||||
|
||||
If you ever want to start clean with a new environment for Horizon, you can
|
||||
run::
|
||||
|
||||
./run_tests.sh --force
|
||||
|
||||
That will blow away the existing environments and create new ones for you.
|
||||
|
||||
Non-interactive Mode
|
||||
====================
|
||||
|
||||
There is an optional flag which will run the script in a non-interactive
|
||||
(and eventually less verbose) mode::
|
||||
|
||||
./run_tests.sh --quiet
|
||||
|
||||
This will automatically take the default action for actions which would
|
||||
normally prompt for user input such as installing/updating the environment.
|
||||
|
||||
Environment Backups
|
||||
===================
|
||||
|
||||
To speed up the process of doing clean checkouts, running continuous
|
||||
integration tests, etc. there are options for backing up the current
|
||||
environment and restoring from a backup::
|
||||
|
||||
./run_tests.sh --restore-environment
|
||||
./run_tests.sh --backup-environment
|
||||
|
||||
The environment backup is stored in ``/tmp/.horizon_environment/``.
|
||||
|
||||
Environment Versioning
|
||||
======================
|
||||
|
||||
Horizon keeps track of changes to the environment by comparing the
|
||||
current requirements files (``requirements.txt`` and
|
||||
``test-requirements.txt``) and the files last time the virtual
|
||||
environment was created or updated. If there is any difference,
|
||||
the virtual environment will be update automatically when you run
|
||||
``run_tests.sh``.
|
@ -1,104 +0,0 @@
|
||||
==================
|
||||
Horizon DataTables
|
||||
==================
|
||||
|
||||
.. module:: horizon.tables
|
||||
|
||||
Horizon includes a componentized API for programmatically creating tables
|
||||
in the UI. Why would you want this? It means that every table renders
|
||||
correctly and consistently, table- and row-level actions all have a consistent
|
||||
API and appearance, and generally you don't have to reinvent the wheel or
|
||||
copy-and-paste every time you need a new table!
|
||||
|
||||
.. seealso::
|
||||
|
||||
For usage information, tips & tricks and more examples check out the :doc:`DataTables Topic
|
||||
Guide </topics/tables>`.
|
||||
|
||||
DataTable
|
||||
=========
|
||||
|
||||
The core class which defines the high-level structure of the table being
|
||||
represented. Example::
|
||||
|
||||
class MyTable(DataTable):
|
||||
name = Column('name')
|
||||
email = Column('email')
|
||||
|
||||
class Meta:
|
||||
name = "my_table"
|
||||
table_actions = (MyAction, MyOtherAction)
|
||||
row_actions - (MyAction)
|
||||
|
||||
A full reference is included below:
|
||||
|
||||
.. autoclass:: DataTable
|
||||
:members:
|
||||
|
||||
DataTable Options
|
||||
=================
|
||||
|
||||
The following options can be defined in a ``Meta`` class inside a
|
||||
:class:`.DataTable` class. Example::
|
||||
|
||||
class MyTable(DataTable):
|
||||
class Meta:
|
||||
name = "my_table"
|
||||
verbose_name = "My Table"
|
||||
|
||||
.. autoclass:: horizon.tables.base.DataTableOptions
|
||||
:members:
|
||||
|
||||
FormsetDataTable
|
||||
================
|
||||
|
||||
You can integrate the :class:`.DataTable` with a Django Formset using one of following classes:
|
||||
|
||||
.. autoclass:: horizon.tables.formset.FormsetDataTableMixin
|
||||
:members:
|
||||
|
||||
.. autoclass:: horizon.tables.formset.FormsetDataTable
|
||||
:members:
|
||||
|
||||
Table Components
|
||||
================
|
||||
|
||||
.. autoclass:: Column
|
||||
:members:
|
||||
|
||||
.. autoclass:: Row
|
||||
:members:
|
||||
|
||||
Actions
|
||||
=======
|
||||
|
||||
.. autoclass:: Action
|
||||
:members:
|
||||
|
||||
.. autoclass:: LinkAction
|
||||
:members:
|
||||
|
||||
.. autoclass:: FilterAction
|
||||
:members:
|
||||
|
||||
.. autoclass:: FixedFilterAction
|
||||
:members:
|
||||
|
||||
.. autoclass:: BatchAction
|
||||
:members:
|
||||
|
||||
.. autoclass:: DeleteAction
|
||||
:members:
|
||||
|
||||
.. autoclass:: UpdateAction
|
||||
:members:
|
||||
|
||||
Class-Based Views
|
||||
=================
|
||||
|
||||
Several class-based views are provided to make working with DataTables
|
||||
easier in your UI.
|
||||
|
||||
.. autoclass:: DataTableView
|
||||
|
||||
.. autoclass:: MultiTableView
|
@ -1,45 +0,0 @@
|
||||
==========================
|
||||
Horizon Tabs and TabGroups
|
||||
==========================
|
||||
|
||||
.. module:: horizon.tabs
|
||||
|
||||
Horizon includes a set of reusable components for programmatically
|
||||
building tabbed interfaces with fancy features like dynamic AJAX loading
|
||||
and nearly effortless templating and styling.
|
||||
|
||||
Tab Groups
|
||||
==========
|
||||
|
||||
For any tabbed interface, your fundamental element is the tab group which
|
||||
contains all your tabs. This class provides a dead-simple API for building
|
||||
tab groups and encapsulates all the necessary logic behind the scenes.
|
||||
|
||||
.. autoclass:: TabGroup
|
||||
:members:
|
||||
|
||||
Tabs
|
||||
====
|
||||
|
||||
The tab itself is the discrete unit for a tab group, representing one
|
||||
view of data.
|
||||
|
||||
.. autoclass:: Tab
|
||||
:members:
|
||||
|
||||
.. autoclass:: TableTab
|
||||
:members:
|
||||
|
||||
|
||||
|
||||
TabView
|
||||
=======
|
||||
|
||||
There is also a useful and simple generic class-based view for handling
|
||||
the display of a :class:`~horizon.tabs.TabGroup` class.
|
||||
|
||||
.. autoclass:: TabView
|
||||
:members:
|
||||
|
||||
.. autoclass:: TabbedTableView
|
||||
:members:
|
@ -1,25 +0,0 @@
|
||||
========================
|
||||
Horizon TestCase Classes
|
||||
========================
|
||||
|
||||
.. module:: horizon.test.helpers
|
||||
|
||||
Horizon provides a base test case class which provides several useful
|
||||
pre-prepared attributes for testing Horizon components.
|
||||
|
||||
.. autoclass:: TestCase
|
||||
:members:
|
||||
|
||||
.. module :: openstack_dashboard.test.helpers
|
||||
|
||||
The OpenStack Dashboard also provides test case classes for greater
|
||||
ease-of-use when testing APIs and OpenStack-specific auth scenarios.
|
||||
|
||||
.. autoclass:: TestCase
|
||||
:members:
|
||||
|
||||
.. autoclass:: APITestCase
|
||||
:members:
|
||||
|
||||
.. autoclass:: BaseAdminViewTests
|
||||
:members:
|
@ -1,38 +0,0 @@
|
||||
=================
|
||||
Horizon Workflows
|
||||
=================
|
||||
|
||||
.. module:: horizon.workflows
|
||||
|
||||
One of the most challenging aspects of building a compelling user experience
|
||||
is crafting complex multi-part workflows. Horizon's ``workflows`` module
|
||||
aims to bring that capability within everyday reach.
|
||||
|
||||
.. seealso::
|
||||
|
||||
For usage information, tips & tricks and more examples check out the
|
||||
:doc:`Workflows Topic Guide </topics/workflows>`.
|
||||
|
||||
Workflows
|
||||
=========
|
||||
|
||||
.. autoclass:: Workflow
|
||||
:members:
|
||||
|
||||
Steps
|
||||
=====
|
||||
|
||||
.. autoclass:: Step
|
||||
:members:
|
||||
|
||||
Actions
|
||||
=======
|
||||
|
||||
.. autoclass:: Action
|
||||
:members:
|
||||
|
||||
WorkflowView
|
||||
============
|
||||
|
||||
.. autoclass:: WorkflowView
|
||||
:members:
|
@ -1,148 +0,0 @@
|
||||
======================
|
||||
Horizon 2012.1 "Essex"
|
||||
======================
|
||||
|
||||
Release Overview
|
||||
================
|
||||
|
||||
During the Essex release cycle, Horizon underwent a significant set of internal
|
||||
changes to allow extensibility and customization while also adding a significant
|
||||
number of new features and bringing much greater stability to every interaction
|
||||
with the underlying components.
|
||||
|
||||
Highlights
|
||||
==========
|
||||
|
||||
Extensibility
|
||||
-------------
|
||||
|
||||
Making Horizon extensible for third-party developers was one of the core
|
||||
goals for the Essex release cycle. Massive strides have been made to allow
|
||||
for the addition of new "plug-in" components and customization of OpenStack
|
||||
Dashboard deployments.
|
||||
|
||||
To support this extensibility, all the components used to build on Horizon's
|
||||
interface are now modular and reusable. Horizon's own dashboards use these
|
||||
components, and they have all been built with third-party developers in mind.
|
||||
Some of the main components are listed below.
|
||||
|
||||
Dashboards and Panels
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Horizon's structure has been divided into logical groupings called dashboards
|
||||
and panels. Horizon's classes representing these concepts handle all the
|
||||
structural concerns associated with building a complete user interface
|
||||
(navigation, access control, url structure, etc.).
|
||||
|
||||
Data Tables
|
||||
~~~~~~~~~~~
|
||||
|
||||
One of the most common activities in a dashboard user interface is simply
|
||||
displaying a list of resources or data and allowing the user to take actions on
|
||||
that data. To this end, Horizon abstracted the commonalities of this task into a
|
||||
reusable set of classes which allow developers to programmatically create
|
||||
displays and interactions for their data with minimal effort and zero
|
||||
boilerplate.
|
||||
|
||||
Tabs and TabGroups
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Another extremely common user-interface element is the use of "tabs" to break
|
||||
down discrete groups of data into manageable chunks. Since these tabs often
|
||||
encompass vastly different data, may have completely different access
|
||||
restrictions, and may sometimes be better-off being loaded dynamically rather
|
||||
than with the initial page load, Horizon includes tab and tab group classes for
|
||||
constructing these interfaces elegantly and with no knowledge of the HTML, CSS
|
||||
or JavaScript involved.
|
||||
|
||||
Nova Features
|
||||
-------------
|
||||
|
||||
Support for Nova's features has been greatly improved in Essex:
|
||||
|
||||
* Support for Nova volumes, including:
|
||||
* Volumes creation and management.
|
||||
* Volume snapshots.
|
||||
* Realtime AJAX updating for volumes in transition states.
|
||||
* Improved Nova instance display and interactions, including:
|
||||
* Launching instances from volumes.
|
||||
* Pausing/suspending instances.
|
||||
* Displaying instance power states.
|
||||
* Realtime AJAX updating for instances in transition states.
|
||||
* Support for managing Floating IP address pools.
|
||||
* New instance and volume detail views.
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
A new "Settings" area was added that offers several useful functions:
|
||||
|
||||
* EC2 credentials download.
|
||||
* OpenStack RC file download.
|
||||
* User language preference customization.
|
||||
|
||||
User Experience Improvements
|
||||
----------------------------
|
||||
|
||||
* Support for batch actions on multiple resources (e.g. terminating multiple
|
||||
instances at once).
|
||||
* Modal interactions throughout the entire UI.
|
||||
* AJAX form submission for in-place validation.
|
||||
* Improved in-context help for forms (tooltips and validation messages).
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
* Creation and publication of a set of Human Interface Guidelines (HIG).
|
||||
* Copious amounts of documentation for developers.
|
||||
|
||||
Under The Hood
|
||||
--------------
|
||||
|
||||
* Internationalization fully enabled, with all strings marked for translation.
|
||||
* Client library changes:
|
||||
* Full migration to python-novaclient from the deprecated openstackx library.
|
||||
* Migration to python-keystoneclient from the deprecated keystone portion
|
||||
of the python-novaclient library.
|
||||
* Client-side templating capabilities for more easily creating dynamic
|
||||
interactions.
|
||||
* Frontend overhaul to use the Bootstrap CSS/JS framework.
|
||||
* Centralized error handling for vastly improved stability/reliability
|
||||
across APIs/clients.
|
||||
* Completely revamped test suite with comprehensive test data.
|
||||
* Forward-compatibility with Django 1.4 and the option of cookie-based sessions.
|
||||
|
||||
Known Issues and Limitations
|
||||
============================
|
||||
|
||||
Quantum
|
||||
-------
|
||||
|
||||
Quantum support has been removed from Horizon for the Essex release. It will be
|
||||
restored in Folsom in conjunction with Quantum's first release as a core
|
||||
OpenStack project.
|
||||
|
||||
Keystone
|
||||
--------
|
||||
|
||||
Due to the mechanisms by which Keystone determines "admin"-ness for a user, an
|
||||
admin user interacting with the "Project" dashboard may see some inconsistent
|
||||
behavior such as all resources being listed instead of only those belonging to
|
||||
that project, or only being able to return to the "Admin" dashboard while
|
||||
accessing certain projects.
|
||||
|
||||
Exceptions during customization
|
||||
-------------------------------
|
||||
|
||||
Exceptions raised while overriding built-in Horizon behavior via the
|
||||
"customization_module" setting may trigger a bug in the error handling
|
||||
which will mask the original exception.
|
||||
|
||||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
The Essex Horizon release is only partially backwards-compatible with Diablo
|
||||
OpenStack components. While it is largely possible to log in and interact, many
|
||||
functions in Nova, Glance and Keystone changed too substantially in Essex to
|
||||
maintain full compatibility.
|
@ -1,159 +0,0 @@
|
||||
=======================
|
||||
Horizon 2012.2 "Folsom"
|
||||
=======================
|
||||
|
||||
Release Overview
|
||||
================
|
||||
|
||||
The Folsom release cycle brought several major advances to Horizon's user
|
||||
experience while also reintroducing Quantum networking as a core piece
|
||||
of the OpenStack Dashboard.
|
||||
|
||||
Highlights
|
||||
==========
|
||||
|
||||
Networking (Quantum)
|
||||
--------------------
|
||||
|
||||
With Quantum being a core project for the Folsom release, we worked closely
|
||||
with the Quantum team to bring networking support back into Horizon. This
|
||||
appears in two primary places: the Networks panel in both the Project and
|
||||
Admin dashboards, and the Network tab in the Launch Instance workflow. Expect
|
||||
further improvements in these areas as Quantum continues to mature and more
|
||||
users adopt this model of virtual network management.
|
||||
|
||||
User Experience
|
||||
---------------
|
||||
|
||||
Workflows
|
||||
~~~~~~~~~
|
||||
|
||||
By far the biggest UI/UX change in the Folsom release is the introduction of
|
||||
programmatic workflows. These components allow developers to create concise
|
||||
interactions that combine discrete tasks spanning multiple services and
|
||||
resources in a user-friendly way and with minimal boilerplate code. Within
|
||||
a workflow, related objects can also be dynamically created so users don't lose
|
||||
their place when they realize the item they wanted isn't currently available.
|
||||
Look for examples of these workflows in Launch Instance, Associate Floating IP,
|
||||
and Create/Edit Project.
|
||||
|
||||
Resource Browser
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Another cool new component is an interface designed for "browsing" resources
|
||||
which are nested under a parent resource. The object store (Swift) is a prime
|
||||
example of this. Now there is a consistent top-level navigation for containers
|
||||
on the left-hand pane of the "browser" while the right-hand pane lets you
|
||||
explore within those containers and sub-folders.
|
||||
|
||||
User Experience Improvements
|
||||
----------------------------
|
||||
|
||||
* Timezone support is now enabled. You can select your preferred timezone
|
||||
in the User Settings panel.
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
* Third-party developers who wish to build on Horizon can get started much
|
||||
faster using the new dashboard and panel templates. See the docs on
|
||||
`creating a dashboard`_ and `creating a panel`_ for more information.
|
||||
|
||||
* A `thorough set of documentation`_ for developers on how to go about
|
||||
internationalizing, localizing and translating OpenStack projects
|
||||
is now available.
|
||||
|
||||
.. _creating a dashboard: http://docs.openstack.org/developer/horizon/topics/tutorial.html#creating-a-dashboard
|
||||
.. _creating a panel: http://docs.openstack.org/developer/horizon/topics/tutorial.html#creating-a-panel
|
||||
.. _thorough set of documentation: http://wiki.openstack.org/Translations
|
||||
|
||||
Under The Hood
|
||||
--------------
|
||||
|
||||
* The python-swiftclient library and python-cinderclient libraries are now
|
||||
used under the hood instead of cloudfiles and python-novaclient respectively.
|
||||
|
||||
* Internationalization of client-side JavaScript is now possible in addition
|
||||
to server-side Python code.
|
||||
|
||||
* Keystone authentication is now handled by a proper pluggable Django
|
||||
authentication backend, offering significantly better and more reliable
|
||||
security for Horizon.
|
||||
|
||||
Other Improvements and Fixes
|
||||
----------------------------
|
||||
|
||||
Some of the general areas of improvement include:
|
||||
|
||||
* Images can now be added to Glance by providing a URL for Glance to download
|
||||
the image data from.
|
||||
|
||||
* Quotas are now displayed dynamically throughout the Project dashboard.
|
||||
|
||||
* API endpoints are now displayed on the OpenStack RC File panel so they
|
||||
can be organically discovered by an end-user.
|
||||
|
||||
* DataTables now support a summation row at the bottom of the table.
|
||||
|
||||
* Better cross-browser support (Safari and IE particularly).
|
||||
|
||||
* Fewer API calls to OpenStack endpoints (improves performance).
|
||||
|
||||
* Better validation of what actions are permitted when.
|
||||
|
||||
* Improved error handling and error messages.
|
||||
|
||||
Known Issues and Limitations
|
||||
============================
|
||||
|
||||
Floating IPs and Quantum
|
||||
------------------------
|
||||
|
||||
Due to the very late addition of floating IP support in Quantum, Nova's
|
||||
integration there is lacking, so floating IP-related API calls to Nova will
|
||||
fail when your OpenStack deployment uses Quantum for networking. This means
|
||||
that Horizon actions such as "allocate" and "associate" floating IPs will
|
||||
not work either since they rely on the underlying APIs.
|
||||
|
||||
Pagination
|
||||
----------
|
||||
|
||||
A number of the "index" pages don't fully work with API pagination yet,
|
||||
causing them to only display the first chunk of results returned by the API.
|
||||
This number is often 1000 (as in the case of novaclient results), but does vary
|
||||
somewhat.
|
||||
|
||||
Deleting large numbers of resources simultaneously
|
||||
--------------------------------------------------
|
||||
|
||||
Using the "select all" checkbox to delete large numbers of resources via the
|
||||
API can cause network timeouts (depending on configuration). This is
|
||||
due to the APIs not supporting bulk-deletion natively, and consequently Horizon
|
||||
has to send requests to delete each resource individually behind the scenes.
|
||||
|
||||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
The Folsom Horizon release should be fully-compatible with both Folsom and
|
||||
Essex versions of the rest of the OpenStack core projects (Nova, Swift, etc.).
|
||||
While some features work significantly better with an all-Folsom stack due
|
||||
to bugfixes, etc. in underlying services, there should not be any limitations
|
||||
on what will or will not function. (Note: Quantum was not a core OpenStack
|
||||
project in Essex, and thus this statement does not apply to network management.)
|
||||
|
||||
In terms of APIs provided for extending Horizon, there are a handful of
|
||||
backwards-incompatible changes that were made:
|
||||
|
||||
* The ``can_haz`` and ``can_haz_list`` template filters have been renamed
|
||||
to ``has_permissions`` and ``has_permissions_on_list`` respectively.
|
||||
|
||||
* The dashboard-specific ``base.html`` templates (e.g. ``nova/base.html``,
|
||||
``syspanel/base.html``, etc.) have been removed in favor of a single
|
||||
``base.html`` template.
|
||||
|
||||
* In conjunction with the previous item, the dashboard-specific template blocks
|
||||
(e.g. ``nova_main``, ``syspanel_main``, etc.) have been removed in favor of
|
||||
a single ``main`` template block.
|
||||
|
||||
Overall, though, great effort has been made to maintain compatibility for
|
||||
third-party developers who may have built on Horizon so far.
|
@ -1,274 +0,0 @@
|
||||
========================
|
||||
Horizon 2013.1 "Grizzly"
|
||||
========================
|
||||
|
||||
Release Overview
|
||||
================
|
||||
|
||||
The Grizzly release cycle saw sweeping improvements to overall user experience,
|
||||
huge stability improvements, lots of new networking, instance management and
|
||||
image management features, a long-needed architectural clarification, and big
|
||||
increases in community engagement! Read on to get the specifics.
|
||||
|
||||
Highlights
|
||||
==========
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
Networking
|
||||
~~~~~~~~~~
|
||||
|
||||
Quantum added a huge number of new features in Grizzly, including L3 support
|
||||
(routers), load balancers, network topology infographics, better compatibility
|
||||
with Nova networking APIs (VNIC ordering when launching an instance; security
|
||||
groups and floating IP integration) and vastly improved informational displays.
|
||||
|
||||
Direct Image Upload To Glance
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is now possible (though there are numerous deployment/security implications)
|
||||
to upload an image file directly from a user's hard disk to Glance through
|
||||
Horizon. For multi-GB images it is still strongly recommended that the upload
|
||||
be done using the Glance CLI. Further improvements to this feature will come in
|
||||
future releases.
|
||||
|
||||
Flavor Extra Specs Support
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In Folsom, Nova added support for "extra specs" on flavors--additional metadata
|
||||
which custom schedulers could use for appropriately scheduling instances. As of
|
||||
the Grizzly release, Horizon now supports reading and writing that data on any
|
||||
flavor.
|
||||
|
||||
Migrate Instance
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Administrators now have the ability to migrate an instance off of its current
|
||||
host via the Admin dashboard's Instances panel.
|
||||
|
||||
|
||||
User Experience Improvements
|
||||
----------------------------
|
||||
|
||||
"Not Authorized" & Being Logged Out
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A shocking number of the problems first-time deployers of OpenStack have can be
|
||||
summarized as "I thought I set everything up, then I tried to log into the
|
||||
dashboard and I was immediately logged back out." The root cause of this was
|
||||
that in an effort to be as secure as possible any 401 or 403 response from
|
||||
any service API was being treated the same as if it was an attempt to access
|
||||
an unauthorized portion of Horizon, and the user was summarily logged out with
|
||||
little to no information as to why.
|
||||
|
||||
In Grizzly we have instead chosen to improve this by treating service API
|
||||
401 and 403 errors as slightly less severe than unauthorized access attempts
|
||||
to restricted areas of Horizon. The reason for this is threefold:
|
||||
|
||||
#. For a non-malicious user these errors are almost 100% the result of
|
||||
misconfiguration and this makes debugging possible.
|
||||
#. A malicious user can make the exact same "unauthorized" requests via the
|
||||
CLI as they can via the dashboard; no special privileges are granted.
|
||||
#. API errors are generated by external systems not under the purview of our
|
||||
project and while we should attempt to respect and take appropriate action
|
||||
on those errors, we should not do anything drastic or even potentially
|
||||
destructive because of them.
|
||||
|
||||
Going forward the user will not be logged out, but no information will be
|
||||
populated on the page and they will be presented with error messages informing
|
||||
them that they are unauthorized for the data they attempted to access.
|
||||
|
||||
Reorganizations
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
A couple of long-standing user confusions were fixed in Grizzly.
|
||||
|
||||
First off, the API Access panel (containing a user's API endpoints, rc files,
|
||||
and EC2 credentials) was moved from Settings to the Access & Security section
|
||||
of the Project dashboard.
|
||||
|
||||
Second, the Default Quotas and Services panels (which were both strictly
|
||||
informational) were combined into tabs in a single System Info panel to make
|
||||
it clear that these panels are thematically related, and to create a home for
|
||||
informational-only displays like these.
|
||||
|
||||
One-click Floating IP Management
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A common complaint from users was that associating a floating IP to an
|
||||
instance involved numerous clicks and form selections for something that
|
||||
the majority of users had no knowledge of and didn't care about. As such, a
|
||||
one-click "simple" floating IP association option has been created. For
|
||||
deployments which only have a single floating IP pool, this allows users to
|
||||
ignore explicit floating IP management and just click a button to associate
|
||||
or disassociate a floating IP with an instance.
|
||||
|
||||
Organized Images
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The Images table now has a new feature: predefined filters for seeing your own
|
||||
images, images that have been shared with you, or public images. This makes
|
||||
finding the image you're looking for a great deal easier and more pleasant.
|
||||
|
||||
Security Group Rule Editing Improvements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The security group rule editing experience has always been inherently very
|
||||
complicated simply given the number of options and the very technical terms
|
||||
involved. Moreover, the combined table-plus-form approach the OpenStack
|
||||
Dashboard had taken only made the UX more frustrating for an already difficult
|
||||
area.
|
||||
|
||||
In Grizzly this has all been reworked to be significantly simpler, and to
|
||||
provide as much contextual help and streamlining as possible.
|
||||
|
||||
Icons!
|
||||
~~~~~~
|
||||
|
||||
In an effort to make the dashboard more at-a-glance usable, we've added icons
|
||||
to most of the common action buttons throughout the dashboard.
|
||||
|
||||
"More Actions", More Better
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Lots of feedback came in that the "more actions" dropdown menu (for tables with
|
||||
numerous actions available on each row) was confusing to new users and/or
|
||||
difficult to click.
|
||||
|
||||
We've now improved it so that the button to open the menu is clearly labeled
|
||||
and the hitbox for clicking it is significantly larger.
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
Docs, docs, and more docs!
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Large amounts of new documentation was added during the Grizzly cycle, most
|
||||
notably are sections documenting: all of the available settings for Horizon and
|
||||
the OpenStack Dashboard; security and deployment considerations; and deeper
|
||||
guides on customizing the OpenStack Dashboard.
|
||||
|
||||
IRC Meeting
|
||||
~~~~~~~~~~~
|
||||
|
||||
During the Grizzly cycle we started holding a weekly project meeting on IRC.
|
||||
This has been extremely beneficial for the growth and progress of the project.
|
||||
Check out the `OpenStack Meetings wiki page`_ for specifics.
|
||||
|
||||
.. _OpenStack Meetings wiki page: https://wiki.openstack.org/wiki/Meetings#Horizon_team_meeting
|
||||
|
||||
|
||||
Under The Hood
|
||||
--------------
|
||||
|
||||
Legacy Dashboard Names & Code Separation
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Very early in the Grizzly cycle we took the opportunity to do some longstanding
|
||||
cleanup and refactoring work. The "nova" dashboard was renamed to "project" and
|
||||
the "syspanel" dashboard was renamed to "admin" to better reflect their
|
||||
respective purposes.
|
||||
|
||||
Moreover, a better separation was created between code related to the core
|
||||
Horizon framework code (which is not related to OpenStack specifically) and
|
||||
the OpenStack Dashboard code. At this point *all* code related to OpenStack
|
||||
lives in the OpenStack Dashboard directory, while the Horizon framework is
|
||||
completely agnostic and is a reusable Django app.
|
||||
|
||||
Object Storage Delimiters and Pseudo-folder Objects
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When Horizon's object storage interface was first added, Swift's documentation
|
||||
recommended adding 0-byte objects with a special content type to denote
|
||||
pseudo-folders within a container. They have since decided that this is not the
|
||||
recommended practice, and that pseudo-folders should only be demarcated by
|
||||
a delimiting character (usually "/") in the object name.
|
||||
|
||||
Horizon has been updated under the hood to use this method, which should bring
|
||||
it better into line with how most deployments are using their object storage.
|
||||
|
||||
|
||||
Other Improvements and Fixes
|
||||
----------------------------
|
||||
|
||||
* Support for Keystone's PKI tokens.
|
||||
|
||||
* Flavor editing was made significantly more stable.
|
||||
|
||||
* Security groups can be added to a running instance.
|
||||
|
||||
* Volume quotas are handled by the appropriate service depending on whether
|
||||
or not Cinder is enabled.
|
||||
|
||||
* Password confirmation boxes are now validated for matching passwords on
|
||||
the client side for more immediate feedback.
|
||||
|
||||
* Numerous fixes to display more and better information for instances and
|
||||
volumes in their overview pages.
|
||||
|
||||
* Improved unicode support for the Object Storage panels.
|
||||
|
||||
* Logout now attempts to delete the token(s) associated with the current
|
||||
session to avoid replay attacks, etc.
|
||||
|
||||
* Various fixes for browser compatibility and rendering.
|
||||
|
||||
* Many, many other bugfixes and improvements. Check out Launchpad for the full
|
||||
list of what went on in Grizzly.
|
||||
|
||||
|
||||
Known Issues and Limitations
|
||||
============================
|
||||
|
||||
Editing a Flavor Which Results In An API Error Will Delete The Flavor
|
||||
---------------------------------------------------------------------
|
||||
|
||||
Due to the way that Nova handles flavor editing/replacement it is necessary
|
||||
to delete the old flavor before creating the replacement flavor. As such,
|
||||
if an API error occurs while creating the replacement it is possible to
|
||||
lose the old flavor without the new one being created.
|
||||
|
||||
Creating Rich Network Topologies
|
||||
--------------------------------
|
||||
|
||||
Due to several Quantum features landing very late in the Grizzly cycle, it
|
||||
is not possible to create particularly complex networking configurations
|
||||
through the OpenStack Dashboard. These features will continue to grow
|
||||
throughout future releases.
|
||||
|
||||
Loadbalancer Feature
|
||||
--------------------
|
||||
|
||||
The Loadbalancer feature landed in the 11th hour for both Quantum and Horizon
|
||||
and, though we did our best to test it, may still contain undiscovered bugs. It
|
||||
is best considered a "beta" or "experimental" feature for the Grizzly release.
|
||||
|
||||
Quantum Brocade Plugin Not Compatible
|
||||
-------------------------------------
|
||||
|
||||
The Brocade plugin for Quantum does not support key features of the floating
|
||||
IP addresses API which are considered central to Horizon's functionality. As
|
||||
such, it is not compatible with the Grizzly release's Quantum integration.
|
||||
|
||||
Deleting large numbers of resources simultaneously
|
||||
--------------------------------------------------
|
||||
|
||||
Using the "select all" checkbox to delete large numbers of resources via the
|
||||
API can cause network timeouts (depending on configuration). This is
|
||||
due to the APIs not supporting bulk-deletion natively, and consequently Horizon
|
||||
has to send requests to delete each resource individually behind the scenes.
|
||||
|
||||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
The Grizzly Horizon release should be fully compatible with both Grizzly and
|
||||
Folsom versions of the rest of the OpenStack core projects (Nova, Swift, etc.).
|
||||
While some features work significantly better with an all-Grizzly stack due
|
||||
to bugfixes, etc. in underlying services, there should not be limitations
|
||||
on what will or will not function.
|
||||
|
||||
Overall, great effort has been made to maintain compatibility for
|
||||
third-party developers who may have built on Horizon so far.
|
@ -1,254 +0,0 @@
|
||||
=======================
|
||||
Horizon 2013.2 "Havana"
|
||||
=======================
|
||||
|
||||
Release Overview
|
||||
================
|
||||
|
||||
The Havana release cycle brings support for *three* new projects, plus
|
||||
significant new features for several existing projects. On top of that, many
|
||||
aspects of user experience have been improved for both end users and
|
||||
administrators. The community continues to grow and expand. The Havana release
|
||||
is solidly the best release of the OpenStack Dashboard project yet!
|
||||
|
||||
Highlights
|
||||
==========
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
Heat
|
||||
~~~~
|
||||
|
||||
The OpenStack Orchestration project (Heat) debuted in Havana, and Horizon
|
||||
delivers full support for managing your Heat stacks. Highlights include
|
||||
support for dynamic form generation from supported Heat template formats,
|
||||
stack topology visualizations, and full stack resource inspection.
|
||||
|
||||
Ceilometer
|
||||
~~~~~~~~~~
|
||||
|
||||
Also debuting in Havana is the OpenStack Metering project (Ceilometer). Initial
|
||||
support for Ceilometer is included in Horizon so that it is possible for an
|
||||
administrator to query the usage of the cloud through the OpenStack Dashboard
|
||||
and better understand how the system is functioning and being utilized.
|
||||
|
||||
Domains, Groups, and More: Keystone v3 API Support
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
With the OpenStack Identity Service (Keystone) v3 API fully fledged in the
|
||||
Havana release, Horizon has added full support for all the new features such
|
||||
as Domains and Groups, Role management and assignment to Domains and Groups,
|
||||
Domain-based authentication, and Domain context switching.
|
||||
|
||||
Trove Databases
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The OpenStack Database as a Service project (Trove) graduated from incubation
|
||||
in the Havana cycle, and thanks to their industriousness they delivered a
|
||||
set of panels for the OpenStack dashboard to allow for provisioning and managing
|
||||
your Trove databases and backups. Disclaimer: Given that Trove's first official
|
||||
release as an integrated project will not be until Icehouse this feature should
|
||||
still be considered experimental and may be subject to change.
|
||||
|
||||
Nova Features
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The number of OpenStack Compute (Nova) features that are supported in Horizon
|
||||
continues to grow. New features in the Havana release include:
|
||||
|
||||
* Editable default quotas.
|
||||
* The ability for an administrator to reset the password of a server/instance.
|
||||
* Availability zone support.
|
||||
* Improved region support.
|
||||
* Instance resizing.
|
||||
* Improved boot-from-volume support.
|
||||
* Per-project flavor support.
|
||||
|
||||
All of these provide a richer set of options for controlling where, when and how
|
||||
instances are launched, and improving how they're managed once they're up and
|
||||
running.
|
||||
|
||||
Neutron Features
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
A number of important new OpenStack Networking (Neutron) features are showcased
|
||||
in the Havana release, most notably:
|
||||
|
||||
* VPN as a Service.
|
||||
* Firewall as a Service.
|
||||
* Editable and interactive network topology visualizations.
|
||||
* Full security group and quota parity between Neutron and Nova network.
|
||||
|
||||
These features allow for tremendous flexibility when constructing
|
||||
software-defined networks for your cloud using Neutron.
|
||||
|
||||
|
||||
User Experience Improvements
|
||||
----------------------------
|
||||
|
||||
Self-Service Password Change
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Empowered by changes to the Keystone API, users can now change their own
|
||||
passwords without the need to involve an administrator. This is more secure and
|
||||
takes the hassle out of things for everyone.
|
||||
|
||||
Better Admin Information Architecture
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Several sections of the Admin dashboard have been rearranged to more logically
|
||||
group information together. Additionally, new sources of information have been
|
||||
added to allow Admins to better understand the state of the hosts in the cloud
|
||||
and their relationship to host aggregates, availability zones, etc.
|
||||
|
||||
Improved Messaging To Users On Logout
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Several new indicators have been added to inform users why they've been logged
|
||||
out when they land on the login screen unexpectedly. These indicators make it
|
||||
clear whether the user's session has expired, they timed out due to inactivity,
|
||||
or they are not authorized for the section of the dashboard they attempted to
|
||||
access.
|
||||
|
||||
Security Group Rule Templates
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Since there are many very common security group rules which users tediously
|
||||
re-add each time (rules for SSH and ping, for example) the Horizon team has
|
||||
added pre-configured templates for common rules which a user can select and
|
||||
add to their security group with two clicks. These rules are configurable
|
||||
via the ``SECURITY_GROUP_RULES`` setting.
|
||||
|
||||
|
||||
Community
|
||||
---------
|
||||
|
||||
Translation Team
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The OpenStack Translations team came fully into its own during the Havana cycle
|
||||
and the quality of the translations in Horizon are the best yet by far.
|
||||
Congratulations to that team for their success in building the community that
|
||||
started primarily within the OpenStack Dashboard project.
|
||||
|
||||
User Experience Group
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A fledgling OpenStack User Experience Group formed during the Havana cycle with
|
||||
the mission of improving UX throughout OpenStack. They have quickly made
|
||||
themselves indispensable to the process of designing and improving features in
|
||||
the OpenStack Dashboard. Expect significant future improvement in User
|
||||
Experience now that there are dedicated people actively collaborating in the
|
||||
open to raise the bar.
|
||||
|
||||
|
||||
Under The Hood
|
||||
--------------
|
||||
|
||||
Less Complicated LESS Compilation: No More NodeJS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Due to outcry from various parties, and made possible by improvements in the
|
||||
Python community's support for LESS, Horizon has removed all traces of NodeJS
|
||||
from the project. We now use the ``lesscpy`` module to compile our LESS into
|
||||
the final stylesheets. This should not affect most users in any way, but it
|
||||
should make life easier for downstream distributions and the like.
|
||||
|
||||
Role-Based Access Controls
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Horizon has begun the transition to using the other OpenStack projects'
|
||||
``policy.json`` files to enforce access controls in the dashboard if the files
|
||||
are provided. This means access controls are more configurable and can be kept
|
||||
in sync between the originating project and Horizon. Currently this is only
|
||||
supported for Keystone and parts of Nova's policy files. Full support will
|
||||
come in the next release. You will need to set the ``POLICY_FILES_PATH`` and
|
||||
``POLICY_FILES`` settings in order to enable this feature.
|
||||
|
||||
|
||||
Other Improvements and Fixes
|
||||
----------------------------
|
||||
|
||||
* Swift container and object metadata are now supported.
|
||||
* New visualizations for utilization and quotas.
|
||||
* The Cisco N1K Router plugin's additional features are available through a
|
||||
special additional dashboard when enabled and supported in Neutron.
|
||||
* Support for self-signed or other specified SSL certificate checking.
|
||||
* Glance image types are now configurable.
|
||||
* Sorting has been improved in many places through the dashboard.
|
||||
* API call efficiency optimizations.
|
||||
* Required fields in forms are now better indicated.
|
||||
* Session timeout can now be enabled to log out the user after a period of
|
||||
inactivity as a security feature.
|
||||
* Significant PEP8 and code quality compliance improvements.
|
||||
* Hundreds of bugfixes and minor user experience improvements.
|
||||
|
||||
|
||||
Upgrade Information
|
||||
===================
|
||||
|
||||
Allowed Hosts
|
||||
-------------
|
||||
|
||||
For production deployments of Horizon you must add the ``ALLOWED_HOSTS``
|
||||
setting to your ``local_settings.py`` file. This setting
|
||||
was added in Django 1.5 and is an important security feature. For more
|
||||
information on it please consult the ``local_settings.py.example`` file
|
||||
or Django's documentation.
|
||||
|
||||
Enabling Keystone and Neutron Features
|
||||
--------------------------------------
|
||||
|
||||
If you have existing configurations for the ``OPENSTACK_KEYSTONE_BACKEND``
|
||||
or ``OPENSTACK_NEUTRON_NETWORK`` settings, you will want to consult the
|
||||
``local_settings.example.py`` file for information on the new options that
|
||||
have been added. Existing configurations will continue to work, but may not
|
||||
have the correct keys to enable some of the new features in Havana.
|
||||
|
||||
|
||||
Known Issues and Limitations
|
||||
============================
|
||||
|
||||
Session Creation and Health Checks
|
||||
----------------------------------
|
||||
|
||||
If you use a health monitoring service that pings the home page combined with
|
||||
a database-backed session backend you may experience excessive session creation.
|
||||
This issue is slated to be fixed soon, but in the interim the recommended
|
||||
solution is to write a periodic job that deletes expired sessions from your
|
||||
session store on a regular basis.
|
||||
|
||||
Deleting large numbers of resources simultaneously
|
||||
--------------------------------------------------
|
||||
|
||||
Using the "select all" checkbox to delete large numbers of resources at once
|
||||
can cause network timeouts (depending on configuration). This is due to the
|
||||
underlying APIs not supporting bulk-deletion natively, and consequently Horizon
|
||||
has to send requests to delete each resource individually behind the scenes.
|
||||
|
||||
Conflicting Security Group Names With Neutron
|
||||
---------------------------------------------
|
||||
|
||||
Whereas Nova Network uses only the name of a security group when specifying
|
||||
security groups at instance launch time, Neutron can accept either a name or
|
||||
a UUID. In order to support both, Horizon passes in the name of the selected
|
||||
security groups. However, due to some data-isolation issues in Neutron there is
|
||||
an issue that can arise if an admin user tries to specify a security group with
|
||||
the same name as another security group in a different project which they also
|
||||
have access to. Neutron will find multiple matches for the security group
|
||||
name and will fail to launch the instance. The current workaround is to treat
|
||||
security group names as unique for admin users.
|
||||
|
||||
|
||||
Backwards Compatibility
|
||||
=======================
|
||||
|
||||
The Havana Horizon release should be fully compatible with both Havana and
|
||||
Grizzly versions of the rest of the OpenStack integrated projects (Nova, Swift,
|
||||
etc.). New features in other OpenStack projects which did not exist in Grizzly
|
||||
will obviously only work in Horizon if the rest of the stack supports them as
|
||||
well.
|
||||
|
||||
Overall, great effort has been made to maintain compatibility for
|
||||
third-party developers who have built on Horizon so far.
|
@ -1,190 +0,0 @@
|
||||
=========================
|
||||
Horizon 2014.1 "Icehouse"
|
||||
=========================
|
||||
|
||||
Release Overview
|
||||
================
|
||||
|
||||
The Icehouse release cycle brings several improvements to Horizon's
|
||||
user experience, improved extensibility, and support for many
|
||||
additional features in existing projects. The community continues to
|
||||
grow. Read more for the specifics.
|
||||
|
||||
Highlights
|
||||
==========
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
Nova
|
||||
~~~~
|
||||
|
||||
The number of OpenStack Compute (Nova) features that are supported in Icehouse
|
||||
grew. New features in the Icehouse release include:
|
||||
|
||||
* Live Migration Support
|
||||
* HyperV Console Support
|
||||
* Disk config extension support
|
||||
* Improved support for managing host aggregates and availability zones
|
||||
* Support for easily setting flavor extra specs
|
||||
|
||||
Cinder
|
||||
~~~~~~
|
||||
|
||||
In an ongoing effort to implement Role Based Access Support throughout Horizon,
|
||||
access controls were added in the OpenStack Volume (Cinder) related panels.
|
||||
Utilization of the Cinder v2 API is now a supported option in the Icehouse
|
||||
release. The ability to extend volumes is now available as well.
|
||||
|
||||
Neutron
|
||||
~~~~~~~
|
||||
|
||||
Display of Router Rules for routers where they are defined is now supported in
|
||||
Horizon.
|
||||
|
||||
Swift
|
||||
~~~~~
|
||||
|
||||
With Icehouse, the ability for users to create containers and mark them as
|
||||
public is now available. Links are added to download these public containers.
|
||||
Users can now explicitly create pseudo directories rather than being required to
|
||||
create them as part of the container creation process.
|
||||
|
||||
Heat
|
||||
~~~~
|
||||
|
||||
In Icehouse, Horizon delivers support for updating existing Heat stacks.
|
||||
Now stacks that have already been deployed can be adjusted and redeployed. The
|
||||
updated template is also validated when updated. Additionally, support for
|
||||
adding environment files was included.
|
||||
|
||||
Ceilometer
|
||||
~~~~~~~~~~
|
||||
|
||||
Horizon has added support for administrators to query Ceilometer and
|
||||
view a daily usage report per project across services through the
|
||||
OpenStack Dashboard to better understand how system resources are being
|
||||
consumed by individual projects.
|
||||
|
||||
Trove Databases
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
The OpenStack Database as a Service project (Trove) is part of the
|
||||
integrated release in the Icehouse cycle. Improvements to the client
|
||||
connections and overall stability were added in the Icehouse cycle.
|
||||
|
||||
|
||||
User Experience Improvements
|
||||
----------------------------
|
||||
|
||||
Extensible Enhancements
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The primary dashboard and panel navigation has been updated from the tab
|
||||
navigation to an accordion implementation. Dashboards and Panel Groups are now
|
||||
expandable and collapsible in the page navigation. This change allows for the
|
||||
addition of more dashboards as well as accommodates the increasing number of
|
||||
panels in dashboards.
|
||||
|
||||
Wizard
|
||||
~~~~~~
|
||||
|
||||
Horizon now provides a Wizard control to complete multi-step interdependent
|
||||
tasks. This is now utilized in the create network action.
|
||||
|
||||
Inline Table Editing
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tables can now be written to support editing fields in the table to reduce the
|
||||
need for opening separate forms. The first sample of this is in the Admin
|
||||
dashboard, Projects panel.
|
||||
|
||||
Self-Service Password Change
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Leveraging enhancements to Identity API v3 (Keystone), users can now change
|
||||
their own passwords without the need to involve an administrator. This
|
||||
functionality was previously only available with Identity API v2.0.
|
||||
|
||||
Server Side Table Filtering
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Tables can now easily be wired to filter results from underlying API calls
|
||||
based on criteria selected by the user rather than just perform an on page
|
||||
search. The first example of this is in the Admin dashboard, Instances panel.
|
||||
|
||||
Under The Hood
|
||||
--------------
|
||||
|
||||
JavaScript
|
||||
~~~~~~~~~~
|
||||
|
||||
In a move to provide a better user experience, Horizon has adopted AngularJS as
|
||||
the primary JavaScript framework. JavaScript is now a browser requirement to
|
||||
run the Horizon interface. More to come in Juno.
|
||||
|
||||
Plugin Architecture
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Horizon now boasts dynamic loading/disabling of dashboards, panel groups and
|
||||
panels. By merely adding a file in the ``enabled`` directory, the selection of
|
||||
items loaded into Horizon can be altered. Editing the Django settings file is
|
||||
no longer required.
|
||||
|
||||
For more information see :ref:`pluggable-settings-label`
|
||||
|
||||
Integration Test Framework
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Horizon now supports running integration tests against a working devstack system. There is a limited test suite, but this a great step forward and allows full integration testing.
|
||||
|
||||
Django 1.6 Support
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Django versions 1.4 - 1.6 are now supported by Horizon.
|
||||
|
||||
|
||||
Upgrade Information
|
||||
===================
|
||||
|
||||
Beginning with the Icehouse cycle, there is now a requirement for JavaScript
|
||||
support in browsers used with OpenStack Dashboard.
|
||||
|
||||
Page Layout Changes
|
||||
-------------------
|
||||
|
||||
The overall structure of the page layout in Horizon has been altered. Existing
|
||||
templates by 3rd parties to override page templates may require some rework.
|
||||
|
||||
Default Hypervisor Settings Changes
|
||||
-----------------------------------
|
||||
|
||||
The default for ``can_set_password`` is now ``False``. This means that unless
|
||||
the setting is explicitly set to ``True``, the option to set an
|
||||
'Admin password' for an instance will not be shown in the Launch Instance
|
||||
workflow. Not all hypervisors support this feature which created confusion with
|
||||
users.
|
||||
|
||||
The default for ``can_set_mountpoint`` is now ``False``, and should be set to
|
||||
``True`` in the settings in order to add the option to set the mount point for
|
||||
volumes in the dashboard. At this point only the Xen hypervisor supports this
|
||||
feature.
|
||||
|
||||
To change the behavior around hypervisor management in Horizon you must add the
|
||||
``OPENSTACK_HYPERVISOR_FEATURES`` setting to your ``settings.py`` or
|
||||
``local_settings.py`` file.
|
||||
|
||||
For more information see :ref:`hypervisor-settings-label`
|
||||
|
||||
Known Issues and Limitations
|
||||
============================
|
||||
|
||||
Multi-Domain Cross Service Support
|
||||
----------------------------------
|
||||
|
||||
While Horizon supports managing Identity v3 entities and authenticating in a
|
||||
multi-domain Keystone configuration, there is a v3, v2.0 token compatibility
|
||||
issue when trying to manage resources for users outside the ``default``
|
||||
domain. For this reason, v2.0 has been restored as the default API version
|
||||
for OpenStack Identity (Keystone). For a single domain environment, Keystone
|
||||
v3 API can still be used via the ``OPENSTACK_API_VERSION`` setting.
|
@ -1,180 +0,0 @@
|
||||
=====================
|
||||
Horizon 2014.2 "Juno"
|
||||
=====================
|
||||
|
||||
Release Overview
|
||||
================
|
||||
|
||||
The Juno release cycle brings a significant update to the user experience;
|
||||
numerous stability improvements; support for Sahara; and significant
|
||||
enhancements in feature support for networking, volumes, databases and images.
|
||||
The community continues to grow and gain speed. Read on for more details.
|
||||
|
||||
Highlights
|
||||
==========
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
Sahara
|
||||
~~~~~~
|
||||
|
||||
The OpenStack Data Processing project (Sahara) was formally included into the
|
||||
integrated release in Juno and Horizon includes broad support for managing your
|
||||
data processing. You can specify and build clusters to utilize several data
|
||||
types with user specified jobs while tracking the progress of those jobs.
|
||||
|
||||
Neutron
|
||||
~~~~~~~
|
||||
|
||||
Neutron added several new features in Juno, including:
|
||||
|
||||
* DVR (Distributed Virtual Routing)
|
||||
* L3 HA support
|
||||
* IPv6 subnet modes
|
||||
|
||||
Horizon provides support for these new features with the Juno release. These
|
||||
features provide much greater flexibility in specifying software defined
|
||||
networks.
|
||||
|
||||
An existing feature in Neutron that Horizon now supports is the MAC learning
|
||||
extension.
|
||||
|
||||
Glance
|
||||
~~~~~~
|
||||
|
||||
In Juno, Glance introduced the ability to manage a catalog of metadata
|
||||
definitions where users can register the metadata definitions to be used on
|
||||
various resource types including images, volumes, aggregates, and flavors.
|
||||
Support for viewing and editing the assignment of these metadata tags is
|
||||
included in Horizon.
|
||||
|
||||
Cinder
|
||||
~~~~~~
|
||||
|
||||
In a continued effort to provide more complete API support, several
|
||||
additional features of the Cinder API are now supported in Horizon in the
|
||||
Juno release.
|
||||
|
||||
Some of these features include:
|
||||
* Creating and restoring volume backups
|
||||
* Enabling resetting the state of a snapshot
|
||||
* Enabling resetting the state of a volume
|
||||
* Supporting upload-to-image
|
||||
* Volume retype
|
||||
* QoS (quality of service) support.
|
||||
|
||||
Trove
|
||||
~~~~~
|
||||
|
||||
Trove supports using multiple types of datastores, e.g., mysql, redis, mongodb.
|
||||
Users can now select from the list of datastores supported by the cloud operator
|
||||
when creating their database instances.
|
||||
|
||||
Another addition is support for utilizing and restoring from incremental
|
||||
database backups.
|
||||
|
||||
To improve support for Neutron based clouds, when creating a database instance,
|
||||
the user can now specify the NIC for the database instance on creation allowing
|
||||
direct access to the instance by the user.
|
||||
|
||||
Nova
|
||||
~~~~
|
||||
|
||||
The new Nova instance actions view provides a list of all actions taken on
|
||||
all instances in the current project allowing users to view resulting errors or
|
||||
actions taken by other users on those instances.
|
||||
|
||||
Administrators now have the ability to evacuate hosts off hypervisors which can
|
||||
aid in system maintenance by providing a mechanism to migrate all instances to
|
||||
other hosts.
|
||||
|
||||
Improved Plugin Support
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The plugin system in Horizon continued to improve in the Juno release.
|
||||
Some of those improvements:
|
||||
|
||||
* Support for adding plugin specific AngularJS modules
|
||||
* Support for adding static files, e.g., CSS, JS, images
|
||||
* Ability to add exceptions
|
||||
* Fixing ordering issues
|
||||
* Numerous other bug fixes
|
||||
|
||||
Enhanced RBAC support
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In an ongoing effort to support richer role based access control (RBAC) in
|
||||
Horizon, the views for several more services were enhanced with RBAC checks to
|
||||
determine user access to actions. The newly supported services are compute,
|
||||
network and orchestration. These changes allow operators to implement finer
|
||||
grained access control than just "member" and "admin".
|
||||
|
||||
The identity panels (domains, projects, users, roles, groups) have also been
|
||||
converted to support RBAC at the view level. The identity panels have been
|
||||
moved from the admin dashboard into their own 'Identity' dashboard and
|
||||
accessibility is determined by policies alone. This is the first step toward
|
||||
consolidating the near duplicate content of the project and admin dashboards
|
||||
into single views supporting a wide range of roles.
|
||||
|
||||
UX Changes
|
||||
~~~~~~~~~~
|
||||
|
||||
In Juno, Horizon transitioned to utilizing Bootstrap v3. Horizon had been
|
||||
pinned to an older version of Bootstrap for several releases. This change now
|
||||
allows Horizon to pick up numerous bug fixes and overall improvements in the
|
||||
Bootstrap framework. The look and feel remains mainly consistent with the
|
||||
Icehouse release.
|
||||
|
||||
Under the Hood
|
||||
--------------
|
||||
|
||||
Improved Translatability
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In an effort to improve the translations for Horizon, updates to remove
|
||||
concatenations and better handle tense were made.
|
||||
|
||||
JavaScript Libraries Extracted
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As part of the Horizon team's ongoing efforts to split the repository into more
|
||||
logical pieces, all the 3rd party JavaScript libraries that Horizon depends on
|
||||
have been removed from the Horizon code base and python xstatic packages have
|
||||
been utilized instead. The xstatic format allows for easy consumption by the
|
||||
Django framework Horizon is built on. Now JavaScript libraries are utilized
|
||||
like any other python dependency in Horizon.
|
||||
|
||||
Conversion from LESS to SCSS
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The supported stylesheets in Horizon have been converted to utilize SCSS rather
|
||||
than LESS. The change was necessary due to a prevalent lack of support for LESS
|
||||
compilers in python. This change also allowed us to upgrade to Bootstrap 3, as
|
||||
parts of the Bootstrap 3 LESS stylesheets were not supported by existing python
|
||||
based LESS compilers.
|
||||
|
||||
Known Issues and Limitations
|
||||
============================
|
||||
|
||||
Rendering issues in extensions
|
||||
------------------------------
|
||||
The conversion to utilizing Bootstrap v3 can cause content extensions written
|
||||
on top of Horizon to have rendering issues. Most of these are fixed by a simple
|
||||
CSS class name substitutions. These issues are primarily seen with buttons and
|
||||
panel content widths.
|
||||
|
||||
Online Compression
|
||||
------------------
|
||||
With the move to SCSS, there may be issues with utilizing online compression in
|
||||
non-DEBUG mode in Horizon. Offline compression continues to work as in previous
|
||||
releases.
|
||||
|
||||
https://bugs.launchpad.net/horizon/+bug/1379761
|
||||
|
||||
Neutron L3 HA
|
||||
-------------
|
||||
The HA property is updateable in the UI, however, Neutron API does not allow the
|
||||
update operation because toggling HA support does not work.
|
||||
|
||||
https://bugs.launchpad.net/horizon/+bug/1379761
|
@ -1,59 +0,0 @@
|
||||
=======================
|
||||
Horizon's tests and you
|
||||
=======================
|
||||
|
||||
How to run the tests
|
||||
====================
|
||||
|
||||
Because Horizon is composed of both the ``horizon`` app and the
|
||||
``openstack_dashboard`` reference project, there are in fact two sets of unit
|
||||
tests. While they can be run individually without problem, there is an easier
|
||||
way:
|
||||
|
||||
Included at the root of the repository is the ``run_tests.sh`` script
|
||||
which invokes both sets of tests, and optionally generates analyses on both
|
||||
components in the process. This script is what Jenkins uses to verify the
|
||||
stability of the project, so you should make sure you run it and it passes
|
||||
before you submit any pull requests/patches.
|
||||
|
||||
To run the tests::
|
||||
|
||||
$ ./run_tests.sh
|
||||
|
||||
It's also possible to :doc:`run a subset of unit tests<ref/run_tests>`.
|
||||
|
||||
.. seealso::
|
||||
|
||||
:doc:`ref/run_tests`
|
||||
Full reference for the ``run_tests.sh`` script.
|
||||
|
||||
|
||||
By default running the Selenium tests will open your Firefox browser (you have
|
||||
to install it first, else an error is raised), and you will be able to see the
|
||||
tests actions.
|
||||
If you want to run the suite headless, without being able to see them (as they
|
||||
are ran on Jenkins), you can run the tests:
|
||||
|
||||
$ ./run_tests.sh --with-selenium --selenium-headless
|
||||
|
||||
Selenium will use a virtual display in this case, instead of your own. In order
|
||||
to run the tests this way you have to install the dependency `xvfb`, like this:
|
||||
|
||||
$ sudo apt-get install xvfb
|
||||
|
||||
for a Debian OS flavour, or for Fedora/Red Hat flavours:
|
||||
|
||||
$ sudo yum install xorg-x11-server-Xvfb
|
||||
|
||||
Writing tests
|
||||
=============
|
||||
|
||||
Horizon uses Django's unit test machinery (which extends Python's ``unittest2``
|
||||
library) as the core of its test suite. As such, all tests for the Python code
|
||||
should be written as unit tests. No doctests please.
|
||||
|
||||
In general new code without unit tests will not be accepted, and every bugfix
|
||||
*must* include a regression test.
|
||||
|
||||
For a much more in-depth discussion of testing, see the :doc:`testing topic
|
||||
guide </topics/testing>`.
|
@ -1,283 +0,0 @@
|
||||
===================
|
||||
Customizing Horizon
|
||||
===================
|
||||
|
||||
Themes
|
||||
======
|
||||
|
||||
As of the Kilo release, styling for the OpenStack Dashboard can be altered
|
||||
through the use of a theme. A theme is a directory containing a
|
||||
``_variables.scss`` file to override the color codes used throughout the SCSS
|
||||
and a ``_styles.scss`` file with additional styles to load after dashboard
|
||||
styles have loaded.
|
||||
|
||||
To use a custom theme, set ``CUSTOM_THEME_PATH`` in ``local_settings.py`` to
|
||||
the directory location for the theme (e.g., ``"static/themes/blue"``). The
|
||||
path can either be relative to the ``openstack_dashboard`` directory or an
|
||||
absolute path to an accessible location on the file system. The default
|
||||
``CUSTOM_THEME_PATH`` is ``static/themes/default``.
|
||||
|
||||
Both the Dashboard custom variables and Bootstrap variables can be overridden.
|
||||
For a full list of the Dashboard SCSS variables that can be changed, see the
|
||||
variables file at ``openstack_dashboard/static/dashboard/scss/_variables.scss``.
|
||||
|
||||
Changing the Logo
|
||||
-----------------
|
||||
|
||||
There are currently two places where the OpenStack logo is pulled in
|
||||
through the stylesheets. The first is shown at the login screen and the other
|
||||
on top of the menu bar. To override the logo place your logo in your themes
|
||||
directory and set the image to use in ``_styles.scss``. For example::
|
||||
|
||||
#splash .login {
|
||||
background-image: url(/static/themes/THEME/logo-splash.png);
|
||||
}
|
||||
|
||||
.topbar {
|
||||
h1.brand a {
|
||||
background-image: url(/static/themes/THEME/logo.png);
|
||||
}
|
||||
}
|
||||
|
||||
``THEME`` should be replaced by the name of your theme directory.
|
||||
The dimensions should be ``width: 216px, height: 35px`` for a drop in
|
||||
replacement.
|
||||
|
||||
Prior to the Kilo release the images files inside of Horizon needed to be
|
||||
replaced by your images files or the Horizon stylesheets needed to be altered
|
||||
to point to the location of your image.
|
||||
|
||||
Changing the Site Title
|
||||
=======================
|
||||
|
||||
The OpenStack Dashboard Site Title branding (i.e. "**OpenStack** Dashboard")
|
||||
can be overwritten by adding the attribute ``SITE_BRANDING``
|
||||
to ``local_settings.py`` with the value being the desired name.
|
||||
|
||||
The file ``local_settings.py`` can be found at the Horizon directory path of
|
||||
``openstack_dashboard/local/local_settings.py``.
|
||||
|
||||
Changing the Brand Link
|
||||
=======================
|
||||
|
||||
The logo also acts as a hyperlink. The default behavior is to redirect to
|
||||
``horizon:user_home``. By adding the attribute ``SITE_BRANDING_LINK`` with
|
||||
the desired url target e.g., ``http://sample-company.com`` in
|
||||
``local_settings.py``, the target of the hyperlink can be changed.
|
||||
|
||||
Modifying Existing Dashboards and Panels
|
||||
========================================
|
||||
|
||||
If you wish to alter dashboards or panels which are not part of your codebase,
|
||||
you can specify a custom python module which will be loaded after the entire
|
||||
Horizon site has been initialized, but prior to the URLconf construction.
|
||||
This allows for common site-customization requirements such as:
|
||||
|
||||
* Registering or unregistering panels from an existing dashboard.
|
||||
* Changing the names of dashboards and panels.
|
||||
* Re-ordering panels within a dashboard or panel group.
|
||||
|
||||
To specify the python module containing your modifications, add the key
|
||||
``customization_module`` to your ``HORIZON_CONFIG`` dictionary in
|
||||
``local_settings.py``. The value should be a string containing the path to your
|
||||
module in dotted python path notation. Example::
|
||||
|
||||
HORIZON_CONFIG = {
|
||||
"customization_module": "my_project.overrides"
|
||||
}
|
||||
|
||||
You can do essentially anything you like in the customization module. For
|
||||
example, you could change the name of a panel::
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
# Rename "User Settings" to "User Options"
|
||||
settings = horizon.get_dashboard("settings")
|
||||
user_panel = settings.get_panel("user")
|
||||
user_panel.name = _("User Options")
|
||||
|
||||
Or get the instances panel::
|
||||
|
||||
projects_dashboard = horizon.get_dashboard("project")
|
||||
instances_panel = projects_dashboard.get_panel("instances")
|
||||
|
||||
And limit access to users with the Keystone Admin role::
|
||||
|
||||
permissions = list(getattr(instances_panel, 'permissions', []))
|
||||
permissions.append('openstack.roles.admin')
|
||||
instances_panel.permissions = tuple(permissions)
|
||||
|
||||
Or just remove it entirely::
|
||||
|
||||
projects_dashboard.unregister(instances_panel.__class__)
|
||||
|
||||
You can also override existing methods with your own versions::
|
||||
|
||||
# Disable Floating IPs
|
||||
from openstack_dashboard.dashboards.project.access_and_security import tabs
|
||||
from openstack_dashboard.dashboards.project.instances import tables
|
||||
|
||||
NO = lambda *x: False
|
||||
|
||||
tabs.FloatingIPsTab.allowed = NO
|
||||
tables.AssociateIP.allowed = NO
|
||||
tables.SimpleAssociateIP.allowed = NO
|
||||
tables.SimpleDisassociateIP.allowed = NO
|
||||
|
||||
You could also customize what columns are displayed in an existing
|
||||
table, by redefining the ``columns`` attribute of its ``Meta``
|
||||
class. This can be achieved in 3 steps:
|
||||
|
||||
#. Extend the table that you wish to modify
|
||||
#. Redefine the ``columns`` attribute under the ``Meta`` class for this
|
||||
new table
|
||||
#. Modify the ``table_class`` attribute for the related view so that it
|
||||
points to the new table
|
||||
|
||||
|
||||
For example, if you wished to remove the Admin State column from the
|
||||
:class:`~openstack_dashboard.dashboards.admin.networks.tables.NetworksTable`,
|
||||
you could do the following::
|
||||
|
||||
from openstack_dashboard.dashboards.project.networks import tables
|
||||
from openstack_dashboard.dashboards.project.networks import views
|
||||
|
||||
class MyNetworksTable(tables.NetworksTable):
|
||||
|
||||
class Meta(tables.NetworksTable.Meta):
|
||||
columns = ('name', 'subnets', 'shared', 'status')
|
||||
|
||||
views.IndexView.table_class = MyNetworksTable
|
||||
|
||||
If you want to add a column you can override the parent table in a
|
||||
similar way, add the new column definition and then use the ``Meta``
|
||||
``columns`` attribute to control the column order as needed.
|
||||
|
||||
.. NOTE::
|
||||
|
||||
``my_project.overrides`` needs to be importable by the python process running
|
||||
Horizon.
|
||||
If your module is not installed as a system-wide python package,
|
||||
you can either make it installable (e.g., with a setup.py)
|
||||
or you can adjust the python path used by your WSGI server to include its location.
|
||||
|
||||
Probably the easiest way is to add a ``python-path`` argument to
|
||||
the ``WSGIDaemonProcess`` line in Apache's Horizon config.
|
||||
|
||||
Assuming your ``my_project`` module lives in ``/opt/python/my_project``,
|
||||
you'd make it look like the following::
|
||||
|
||||
WSGIDaemonProcess [... existing options ...] python-path=/opt/python
|
||||
|
||||
|
||||
Button Icons
|
||||
============
|
||||
|
||||
Horizon uses font icons (glyphicons) from Twitter Bootstrap to add icons to buttons.
|
||||
Please see http://bootstrapdocs.com/v3.1.1/docs/components/#glyphicons for instructions
|
||||
how to use icons in the code.
|
||||
|
||||
To add icon to Table Action, use icon property. Example:
|
||||
|
||||
class CreateSnapshot(tables.LinkAction):
|
||||
name = "snapshot"
|
||||
verbose_name = _("Create Snapshot")
|
||||
icon = "camera"
|
||||
|
||||
Additionally, the site-wide default button classes can be configured by
|
||||
setting ``ACTION_CSS_CLASSES`` to a tuple of the classes you wish to appear
|
||||
on all action buttons in your ``local_settings.py`` file.
|
||||
|
||||
|
||||
Custom Stylesheets
|
||||
==================
|
||||
|
||||
It is possible to define custom stylesheets for your dashboards. Horizon's base
|
||||
template ``horizon/templates/base.html`` defines multiple blocks that
|
||||
can be overridden.
|
||||
|
||||
To define custom css files that apply only to a specific dashboard, create
|
||||
a base template in your dashboard's templates folder, which extends Horizon's
|
||||
base template e.g. ``openstack_dashboard/dashboards/my_custom_dashboard/
|
||||
templates/my_custom_dashboard/base.html``.
|
||||
|
||||
In this template, redefine ``block css``. (Don't forget to include
|
||||
``_stylesheets.html`` which includes all Horizon's default stylesheets.)::
|
||||
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block css %}
|
||||
{% include "_stylesheets.html" %}
|
||||
|
||||
{% load compress %}
|
||||
{% compress css %}
|
||||
<link href='{{ STATIC_URL }}my_custom_dashboard/scss/my_custom_dashboard.scss' type='text/scss' media='screen' rel='stylesheet' />
|
||||
{% endcompress %}
|
||||
{% endblock %}
|
||||
|
||||
The custom stylesheets then reside in the dashboard's own ``static`` folder
|
||||
``openstack_dashboard/dashboards/my_custom_dashboard/static/
|
||||
my_custom_dashboard/scss/my_custom_dashboard.scss``.
|
||||
|
||||
All dashboard's templates have to inherit from dashboard's base.html::
|
||||
|
||||
{% extends 'my_custom_dashboard/base.html' %}
|
||||
...
|
||||
|
||||
|
||||
Custom Javascript
|
||||
=================
|
||||
|
||||
Similarly to adding custom styling (see above), it is possible to include
|
||||
custom javascript files.
|
||||
|
||||
All Horizon's javascript files are listed in the ``horizon/_scripts.html``
|
||||
partial template, which is included in Horizon's base template in ``block js``.
|
||||
|
||||
To add custom javascript files, create an ``_scripts.html`` partial template in
|
||||
your dashboard ``openstack_dashboard/dashboards/my_custom_dashboard/
|
||||
templates/my_custom_dashboard/_scripts.html`` which extends
|
||||
``horizon/_scripts.html``. In this template override the
|
||||
``block custom_js_files`` including your custom javascript files::
|
||||
|
||||
{% extends 'horizon/_scripts.html' %}
|
||||
|
||||
{% block custom_js_files %}
|
||||
<script src='{{ STATIC_URL }}my_custom_dashboard/js/my_custom_js.js' type='text/javascript' charset='utf-8'></script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
In your dashboard's own base template ``openstack_dashboard/dashboards/
|
||||
my_custom_dashboard/templates/my_custom_dashboard/base.html`` override
|
||||
``block js`` with inclusion of dashboard's own ``_scripts.html``::
|
||||
|
||||
{% block js %}
|
||||
{% include "my_custom_dashboard/_scripts.html" %}
|
||||
{% endblock %}
|
||||
|
||||
The result is a single compressed js file consisting both Horizon and
|
||||
dashboard's custom scripts.
|
||||
|
||||
Additionally, some marketing and analytics scripts require you to place them
|
||||
within the page's <head> tag. To do this, place them within the
|
||||
``horizon/_custom_head_js.html`` file. Similar to the ``_scripts.html`` file
|
||||
mentioned above, you may link to an existing file::
|
||||
|
||||
<script src='{{ STATIC_URL }}/my_custom_dashboard/js/my_marketing_js.js' type='text/javascript' charset='utf-8'></script>
|
||||
|
||||
or you can paste your script directly in the file, being sure to use
|
||||
appropriate tags::
|
||||
|
||||
<script type="text/javascript">
|
||||
//some javascript
|
||||
</script>
|
||||
|
||||
|
||||
Customizing Meta Attributes
|
||||
===========================
|
||||
|
||||
To add custom metadata attributes to your project's base template, include
|
||||
them in the ``horizon/_custom_meta.html`` file. The contents of this file will be
|
||||
inserted into the page's <head> just after the default Horizon meta tags.
|
@ -1,227 +0,0 @@
|
||||
=================
|
||||
Deploying Horizon
|
||||
=================
|
||||
|
||||
This guide aims to cover some common questions, concerns and pitfalls you
|
||||
may encounter when deploying Horizon in a production environment.
|
||||
|
||||
.. seealso:: :doc:`settings`
|
||||
|
||||
.. note::
|
||||
|
||||
The Service Catalog returned by the Identity Service after a user
|
||||
has successfully authenticated determines the dashboards and panels
|
||||
that will be available within the OpenStack Dashboard. If you are not
|
||||
seeing a particular service you expected (e.g. Object Storage/Swift or
|
||||
Networking/Neutron) make sure your Service Catalog is configured correctly.
|
||||
|
||||
Prior to the Essex release of Horizon these features were controlled by
|
||||
individual settings in the ``local_settings.py`` file. This code has been
|
||||
long-since removed and those pre-Essex settings have no impact now.
|
||||
|
||||
Configure Your Identity Service Host
|
||||
====================================
|
||||
|
||||
The one thing you *must* do in order to run Horizon is to specify the
|
||||
host for your OpenStack Identity Service endpoint. To do this, set the value
|
||||
of the ``OPENSTACK_HOST`` settings in your ``local_settings.py`` file.
|
||||
|
||||
Logging
|
||||
=======
|
||||
|
||||
Logging is an important concern for production deployments, and the intricacies
|
||||
of good logging configuration go far beyond what can be covered here. However
|
||||
there are a few points worth noting about the logging included with Horizon,
|
||||
how to customize it, and where other components may take over:
|
||||
|
||||
* Horizon's logging uses Django's logging configuration mechanism, which
|
||||
can be customized in your ``local_settings.py`` file through the
|
||||
``LOGGING`` dictionary.
|
||||
* Horizon's default logging example sets the log level to ``"INFO"``, which is
|
||||
a reasonable choice for production deployments. For development, however,
|
||||
you may want to change the log level to ``"DEBUG"``.
|
||||
* Horizon also uses a number of 3rd-party clients which log separately. The
|
||||
log level for these can still be controlled through Horizon's ``LOGGING``
|
||||
config, however behaviors may vary beyond Horizon's control.
|
||||
* For more information regarding configuring logging in Horizon, please
|
||||
read the `Django logging directive`_ and the `Python logging directive`_
|
||||
documentation. Horizon is built on Python and Django.
|
||||
|
||||
.. _Django logging directive: https://docs.djangoproject.com/en/1.5/topics/logging
|
||||
.. _Python logging directive: http://docs.python.org/2/library/logging.html
|
||||
|
||||
.. warning::
|
||||
|
||||
At this time there is `a known bug in python-keystoneclient`_ where it will
|
||||
log the complete request body of any request sent to Keystone through it
|
||||
(including logging passwords in plain text) when the log level is set to
|
||||
``"DEBUG"``. If this behavior is not desired, make sure your log level is
|
||||
``"INFO"`` or higher.
|
||||
|
||||
.. _a known bug in python-keystoneclient: https://bugs.launchpad.net/keystone/+bug/1004114
|
||||
|
||||
File Uploads
|
||||
============
|
||||
|
||||
Horizon allows users to upload files via their web browser to other OpenStack
|
||||
services such as Glance and Swift. Files uploaded through this mechanism are
|
||||
first stored on the Horizon server before being forwarded on - files are not
|
||||
uploaded directly or streamed as Horizon receives them. As Horizon itself does
|
||||
not impose any restrictions on the size of file uploads, production deployments
|
||||
will want to consider configuring their server hosting the Horizon application
|
||||
to enforce such a limit to prevent large uploads exhausting system resources
|
||||
and disrupting services. Deployments using Apache2 can use the
|
||||
`LimitRequestBody directive`_ to achieve this.
|
||||
|
||||
Uploads to the Glance image store service tend to be particularly large - in
|
||||
the order of hundreds of megabytes to multiple gigabytes. Deployments are able
|
||||
to disable local image uploads through Horizon by setting
|
||||
``HORIZON_IMAGES_ALLOW_UPLOAD`` to ``False`` in your ``local_settings.py``
|
||||
file.
|
||||
|
||||
.. note::
|
||||
This will not disable image creation altogether, as this setting does not
|
||||
affect images created by specifying an image location (URL) as the image source.
|
||||
|
||||
|
||||
.. _LimitRequestBody directive: http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestbody
|
||||
|
||||
Session Storage
|
||||
===============
|
||||
|
||||
Horizon uses `Django's sessions framework`_ for handling user session data;
|
||||
however that's not the end of the story. There are numerous session backends
|
||||
available, which are controlled through the ``SESSION_ENGINE`` setting in
|
||||
your ``local_settings.py`` file. What follows is a quick discussion of the
|
||||
pros and cons of each of the common options as they pertain to deploying
|
||||
Horizon specifically.
|
||||
|
||||
.. _Django's sessions framework: https://docs.djangoproject.com/en/dev/topics/http/sessions/
|
||||
|
||||
Local Memory Cache
|
||||
------------------
|
||||
|
||||
Enabled by::
|
||||
|
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||
CACHES = {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
|
||||
}
|
||||
|
||||
Local memory storage is the quickest and easiest session backend to set up,
|
||||
as it has no external dependencies whatsoever. However, it has two significant
|
||||
drawbacks:
|
||||
|
||||
* No shared storage across processes or workers.
|
||||
* No persistence after a process terminates.
|
||||
|
||||
The local memory backend is enabled as the default for Horizon solely because
|
||||
it has no dependencies. It is not recommended for production use, or even for
|
||||
serious development work. For better options, read on.
|
||||
|
||||
Memcached
|
||||
---------
|
||||
|
||||
Enabled by::
|
||||
|
||||
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||
CACHES = {
|
||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache'
|
||||
'LOCATION': 'my_memcached_host:11211',
|
||||
}
|
||||
|
||||
External caching using an application such as memcached offers persistence
|
||||
and shared storage, and can be very useful for small-scale deployment and/or
|
||||
development. However, for distributed and high-availability scenarios
|
||||
memcached has inherent problems which are beyond the scope of this
|
||||
documentation.
|
||||
|
||||
Memcached is an extremely fast and efficient cache backend for cases where it
|
||||
fits the deployment need. But it's not appropriate for all scenarios.
|
||||
|
||||
Requirements:
|
||||
|
||||
* Memcached service running and accessible.
|
||||
* Python memcached module installed.
|
||||
|
||||
Database
|
||||
--------
|
||||
|
||||
Enabled by::
|
||||
|
||||
SESSION_ENGINE = 'django.core.cache.backends.db.DatabaseCache'
|
||||
DATABASES = {
|
||||
'default': {
|
||||
# Database configuration here
|
||||
}
|
||||
}
|
||||
|
||||
Database-backed sessions are scalable (using an appropriate database strategy),
|
||||
persistent, and can be made high-concurrency and highly-available.
|
||||
|
||||
The downside to this approach is that database-backed sessions are one of the
|
||||
slower session storages, and incur a high overhead under heavy usage. Proper
|
||||
configuration of your database deployment can also be a substantial
|
||||
undertaking and is far beyond the scope of this documentation.
|
||||
|
||||
Cached Database
|
||||
---------------
|
||||
|
||||
To mitigate the performance issues of database queries, you can also consider
|
||||
using Django's ``cached_db`` session backend which utilizes both your database
|
||||
and caching infrastructure to perform write-through caching and efficient
|
||||
retrieval. You can enable this hybrid setting by configuring both your database
|
||||
and cache as discussed above and then using::
|
||||
|
||||
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||
|
||||
Cookies
|
||||
-------
|
||||
|
||||
If you're using Django 1.4 or later, a new session backend is available to you
|
||||
which avoids server load and scaling problems: the ``signed_cookies`` backend!
|
||||
|
||||
This backend stores session data in a cookie which is stored by the
|
||||
user's browser. The backend uses a cryptographic signing technique to ensure
|
||||
session data is not tampered with during transport (**this is not the same
|
||||
as encryption, session data is still readable by an attacker**).
|
||||
|
||||
The pros of this session engine are that it doesn't require any additional
|
||||
dependencies or infrastructure overhead, and it scales indefinitely as long
|
||||
as the quantity of session data being stored fits into a normal cookie.
|
||||
|
||||
The biggest downside is that it places session data into storage on the user's
|
||||
machine and transports it over the wire. It also limits the quantity of
|
||||
session data which can be stored.
|
||||
|
||||
For a thorough discussion of the security implications of this session backend,
|
||||
please read the `Django documentation on cookie-based sessions`_.
|
||||
|
||||
.. _Django documentation on cookie-based sessions: https://docs.djangoproject.com/en/dev/topics/http/sessions/#using-cookie-based-sessions
|
||||
|
||||
Secure Site Recommendations
|
||||
---------------------------
|
||||
|
||||
When implementing Horizon for public usage, with the website served through
|
||||
HTTPS, it is recommended that the following settings are applied.
|
||||
|
||||
To help protect the session cookies from `cross-site scripting`_, add the
|
||||
following to ``local_settings.py``::
|
||||
|
||||
CSRF_COOKIE_SECURE = True
|
||||
SESSION_COOKIE_SECURE = True
|
||||
|
||||
Note that the CSRF_COOKIE_SECURE option is only available from Django 1.4. It
|
||||
does no harm to have the setting in earlier versions, but it does not take effect.
|
||||
|
||||
You can also disable `browser autocompletion`_ for the authentication form by
|
||||
modifying the ``HORIZON_CONFIG`` dictionary in ``local_settings.py`` by adding
|
||||
the key ``password_autocomplete`` with the value ``off`` as shown here::
|
||||
|
||||
HORIZON_CONFIG = {
|
||||
...
|
||||
'password_autocomplete': 'off',
|
||||
}
|
||||
|
||||
.. _cross-site scripting: https://www.owasp.org/index.php/HttpOnly
|
||||
.. _browser autocompletion: https://wiki.mozilla.org/The_autocomplete_attribute_and_web_documents_using_XHTML
|
@ -1,100 +0,0 @@
|
||||
==================
|
||||
Installing Horizon
|
||||
==================
|
||||
|
||||
This page covers the basic installation of Horizon.
|
||||
|
||||
.. _system-requirements-label:
|
||||
|
||||
System Requirements
|
||||
===================
|
||||
|
||||
* Python 2.7
|
||||
* Django 1.6 (1.4 and 1.5 are supported too)
|
||||
* Minimum required set of running OpenStack services are:
|
||||
|
||||
* Nova
|
||||
* Keystone
|
||||
* Glance
|
||||
* Neutron (unless nova-network is used)
|
||||
|
||||
* All other services are optional.
|
||||
Horizon supports the following services in Juno release.
|
||||
If Keystone endpoint for a service is configured,
|
||||
Horizon detects it and enables its support automatically.
|
||||
|
||||
* Swift
|
||||
* Cinder
|
||||
* Heat
|
||||
* Ceilometer
|
||||
* Trove
|
||||
* Sahara
|
||||
|
||||
Installation
|
||||
============
|
||||
|
||||
1. Compile translation message catalogs for internationalization.
|
||||
This step is not required if you do not need to support languages
|
||||
other than English. GNU ``gettext`` tool is required to compile
|
||||
message catalogs::
|
||||
|
||||
$ sudo apt-get install gettext
|
||||
$ ./run_tests.sh --compilemessages
|
||||
|
||||
This command compiles translation message catalogs within Python
|
||||
virtualenv named ``.venv``. After this step, you can remove
|
||||
``.venv`` directory safely.
|
||||
|
||||
2. Install Horizon python module into your system. Run the following
|
||||
in the top directory::
|
||||
|
||||
$ sudo pip install .
|
||||
|
||||
3. Create ``openstack_dashboard/local/local_settings.py``.
|
||||
It is usually a good idea to copy
|
||||
``openstack_dashboard/local/local_settings.py.example`` and edit it.
|
||||
At least we need to customize the following variables in this file.
|
||||
|
||||
* ``ALLOWED_HOSTS`` (unless ``DEBUG`` is ``True``)
|
||||
* ``OPENSTACK_KEYSTONE_URL``
|
||||
|
||||
For more details, please refer to :doc:`deployment` and :doc:`settings`.
|
||||
|
||||
4. Optional: Django has a Compressor feature that performs many enhancements
|
||||
for the delivery of static files, including standardization and
|
||||
minification/uglification. This processing can be run either online or
|
||||
offline (pre-processed). Letting the compression process occur at runtime
|
||||
will incur processing and memory use when the resources are first requested;
|
||||
doing it ahead of time removes those runtime penalties.
|
||||
|
||||
If you want the static files to be processed before server runtime, you'll
|
||||
need to configure your local_settings.py to specify
|
||||
``COMPRESS_OFFLINE = True``, then run the following commands::
|
||||
|
||||
$ ./manage.py collectstatic
|
||||
$ ./manage.py compress
|
||||
|
||||
5. Set up a web server with WSGI support.
|
||||
It is optional but recommended in production deployments.
|
||||
For example, install Apache web server on Ubuntu::
|
||||
|
||||
$ sudo apt-get install apache2 libapache2-mod-wsgi
|
||||
|
||||
Then configure the web server to host OpenStack Dashboard via WSGI.
|
||||
For apache2 web server, you may need to create
|
||||
``/etc/apache2/sites-available/horizon.conf``.
|
||||
The template in devstack is a good example of the file.
|
||||
http://git.openstack.org/cgit/openstack-dev/devstack/tree/files/apache-horizon.template
|
||||
|
||||
6. Finally, enable the above configuration and restart the web server::
|
||||
|
||||
$ sudo a2ensite horizon
|
||||
$ sudo service apache2 restart
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
* :doc:`deployment` covers some common questions, concerns and pitfalls you
|
||||
may encounter when deploying Horizon in a production environment.
|
||||
* :doc:`settings` lists the available settings for Horizon.
|
||||
* :doc:`customizing` describes how to customizing Horizon as you want.
|
@ -1,148 +0,0 @@
|
||||
============================================================
|
||||
Horizon Policy Enforcement (RBAC: Role Based Access Control)
|
||||
============================================================
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
Horizon's policy enforcement builds on the oslo-incubator policy engine.
|
||||
The basis of which is ``openstack_dashboard/openstack/common/policy.py``.
|
||||
Services in OpenStack use the oslo policy engine to define policy rules
|
||||
to limit access to APIs based primarily on role grants and resource
|
||||
ownership.
|
||||
|
||||
The Keystone v3 API provides an interface for creating/reading/updating
|
||||
policy files in the keystone database. However, at this time services
|
||||
do not load the policy files into Keystone. Thus, the implementation in
|
||||
Horizon is based on copies of policy.json files found in the service's
|
||||
source code. The long-term goal is to read/utilize/update these policy
|
||||
files in Horizon.
|
||||
|
||||
The service rules files are loaded into the policy engine to determine
|
||||
access rights to actions and service APIs.
|
||||
|
||||
Horizon Settings
|
||||
================
|
||||
|
||||
There are a few settings that must be in place for the Horizon policy
|
||||
engine to work.
|
||||
|
||||
``POLICY_FILES_PATH``
|
||||
---------------------
|
||||
|
||||
Default: ``os.path.join(ROOT_PATH, "conf")``
|
||||
|
||||
Specifies where service based policy files are located. These are used to
|
||||
define the policy rules actions are verified against. This value must contain
|
||||
the files listed in ``POLICY_FILES`` or all policy checks will pass.
|
||||
|
||||
.. note::
|
||||
|
||||
The path to deployment specific policy files can be specified in
|
||||
``local_settings.py`` to override the default location.
|
||||
|
||||
|
||||
``POLICY_FILES``
|
||||
----------------
|
||||
|
||||
Default: ``{'identity': 'keystone_policy.json', 'compute': 'nova_policy.json'}``
|
||||
|
||||
This should essentially be the mapping of the contents of ``POLICY_FILES_PATH``
|
||||
to service types. When policy.json files are added to the directory
|
||||
``POLICY_FILES_PATH``, they should be included here too. Without this mapping,
|
||||
there is no way to map service types with policy rules, thus two policy.json
|
||||
files containing a "default" rule would be ambiguous.
|
||||
|
||||
.. note::
|
||||
|
||||
Deployment specific policy files can be specified in ``local_settings.py``
|
||||
to override the default policy files. It is imperative that these policy
|
||||
files match those deployed in the target OpenStack installation. Otherwise,
|
||||
the displayed actions and the allowed action will not match.
|
||||
|
||||
``POLICY_CHECK_FUNCTION``
|
||||
-------------------------
|
||||
|
||||
Default: ``policy.check``
|
||||
|
||||
This value should not be changed, although removing it would be a means to
|
||||
bypass all policy checks.
|
||||
|
||||
|
||||
How user's roles are determined
|
||||
===============================
|
||||
|
||||
Each policy check uses information about the user stored on the request to
|
||||
determine the user's roles. This information was extracted from the scoped
|
||||
token received from Keystone when authenticating.
|
||||
|
||||
Entity ownership is also a valid role. To verify access to specific entities
|
||||
like a project, the target must be specified. See the section
|
||||
:ref:`rule targets <rule_targets>` later in this document.
|
||||
|
||||
How to Utilize RBAC
|
||||
===================
|
||||
|
||||
The primary way to add role based access control checks to panels is in the
|
||||
definition of table actions. When implementing a derived action class,
|
||||
setting the :attr:`~horizon.tables.Action.policy_rules` attribute to valid
|
||||
policy rules will force a policy check before the
|
||||
:meth:`horizon.tables.Action.allowed` method is called on the action. These
|
||||
rules are defined in the policy files pointed to by ``POLICY_PATH`` and
|
||||
``POLICY_FILES``. The rules are role based, where entity owner is also a
|
||||
role. The format for the ``policy_rules`` is a list of two item tuples. The
|
||||
first component of the tuple is the scope of the policy rule, this is the
|
||||
service type. This informs the policy engine which policy file to reference.
|
||||
The second component is the rule to enforce from the policy file specified by
|
||||
the scope. An example tuple is::
|
||||
|
||||
("identity", "identity:get_user")
|
||||
|
||||
x tuples can be added to enforce x rules.
|
||||
|
||||
.. note::
|
||||
|
||||
If a rule specified is not found in the policy file, the policy check
|
||||
will return False and the action will not be allowed.
|
||||
|
||||
The secondary way to add a role based check is to directly use the
|
||||
:meth:`~openstack_dashboard.policy.check` method. The method takes a list
|
||||
of actions, same format as the :attr:`~horizon.tables.Action.policy_rules`
|
||||
attribute detailed above; the current request object; and a dictionary of
|
||||
action targets. This is the method that :class:`horizon.tables.Action` class
|
||||
utilizes. Examples look like::
|
||||
|
||||
from openstack_dashboard import policy
|
||||
|
||||
allowed = policy.check((("identity", "identity:get_user"),
|
||||
("identity", "identity:get_project"),), request)
|
||||
|
||||
can_see = policy.check((("identity", "identity:get_user"),), request,
|
||||
target={"domain_id": domainId})
|
||||
|
||||
.. note::
|
||||
|
||||
Any time multiple rules are specified in a single `policy.check` method
|
||||
call, the result is the logical `and` of each rule check. So, if any
|
||||
rule fails verification, the result is `False`.
|
||||
|
||||
.. _rule_targets:
|
||||
|
||||
Rule Targets
|
||||
============
|
||||
|
||||
Some rules allow access if the user owns the entity. Policy check targets
|
||||
specify particular entities to check for user ownership. The target parameter
|
||||
to the :meth:`~openstack_dashboard.policy.check` method is a simple dictionary.
|
||||
For instance, the target for checking access a project looks like::
|
||||
|
||||
{"project_id": "0905760626534a74979afd3f4a9d67f1"}
|
||||
|
||||
If the value matches the ``project_id`` to which the user's token is scoped,
|
||||
then access is allowed.
|
||||
|
||||
When deriving the :class:`horizon.tables.Action` class for use in a table, if
|
||||
a policy check is desired for a particular target, the implementer should
|
||||
override the :meth:`horizon.tables.Action.get_policy_target` method. This
|
||||
allows a programmatic way to specify the target based on the current datum. The
|
||||
value returned should be the target dictionary.
|
File diff suppressed because it is too large
Load Diff
@ -1,300 +0,0 @@
|
||||
============================================
|
||||
Tutorial: Adding a complex action to a table
|
||||
============================================
|
||||
|
||||
This tutorial covers how to add a more complex action to a table, one that requires
|
||||
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
|
||||
files created there.
|
||||
|
||||
This action will create a snapshot of the instance. When the action is taken,
|
||||
it will display a form that will allow the user to enter a snapshot name,
|
||||
and will create that snapshot when the form is closed using the ``Create snapshot``
|
||||
button.
|
||||
|
||||
Defining the view
|
||||
=================
|
||||
|
||||
To define the view, we must create a view class, along with the template (``HTML``)
|
||||
file and the form class for that view.
|
||||
|
||||
The template file
|
||||
-----------------
|
||||
The template file contains the HTML that will be used to show the view.
|
||||
|
||||
Create a ``create_snapshot.html`` file under the ``mypanel/templates/mypanel``
|
||||
directory and add the following code::
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Snapshot" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create a Snapshot") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'mydashboard/mypanel/_create_snapshot.html' %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
As you can see, the main body will be defined in ``_create_snapshot.html``,
|
||||
so we must also create that file under the ``mypanel/templates/mypanel``
|
||||
directory. It should contain the following code::
|
||||
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block modal-body-right %}
|
||||
<h3>{% trans "Description:" %}</h3>
|
||||
<p>{% trans "Snapshots preserve the disk state of a running instance." %}</p>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
The form
|
||||
--------
|
||||
|
||||
Horizon provides a :class:`~horizon.forms.base.SelfHandlingForm` class which simplifies
|
||||
some of the details involved in creating a form. Our form will derive from this
|
||||
class, adding a character field to allow the user to specify a name for the
|
||||
snapshot, and handling the successful closure of the form by calling the nova
|
||||
api to create the snapshot.
|
||||
|
||||
Create the ``forms.py`` file under the ``mypanel`` directory and add the following::
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
|
||||
class CreateSnapshot(forms.SelfHandlingForm):
|
||||
instance_id = forms.CharField(label=_("Instance ID"),
|
||||
widget=forms.HiddenInput(),
|
||||
required=False)
|
||||
name = forms.CharField(max_length=255, label=_("Snapshot Name"))
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
snapshot = api.nova.snapshot_create(request,
|
||||
data['instance_id'],
|
||||
data['name'])
|
||||
return snapshot
|
||||
except Exception:
|
||||
exceptions.handle(request,
|
||||
_('Unable to create snapshot.'))
|
||||
|
||||
|
||||
The view
|
||||
--------
|
||||
|
||||
Now, the view will tie together the template and the form. Horizon provides a
|
||||
:class:`~horizon.forms.views.ModalFormView` class which simplifies the creation of a
|
||||
view that will contain a modal form.
|
||||
|
||||
Open the ``views.py`` file under the ``mypanel`` directory and add the code
|
||||
for the CreateSnapshotView and the necessary imports. The complete
|
||||
file should now look something like this::
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tabs
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
from horizon.utils import memoized
|
||||
|
||||
from openstack_dashboard import api
|
||||
|
||||
from openstack_dashboard.dashboards.mydashboard.mypanel \
|
||||
import forms as project_forms
|
||||
|
||||
from openstack_dashboard.dashboards.mydashboard.mypanel \
|
||||
import tabs as mydashboard_tabs
|
||||
|
||||
|
||||
class IndexView(tabs.TabbedTableView):
|
||||
tab_group_class = mydashboard_tabs.MypanelTabs
|
||||
# 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
|
||||
|
||||
|
||||
class CreateSnapshotView(forms.ModalFormView):
|
||||
form_class = project_forms.CreateSnapshot
|
||||
template_name = 'mydashboard/mypanel/create_snapshot.html'
|
||||
success_url = reverse_lazy("horizon:project:images:index")
|
||||
modal_id = "create_snapshot_modal"
|
||||
modal_header = _("Create Snapshot")
|
||||
submit_label = _("Create Snapshot")
|
||||
submit_url = "horizon:mydashboard:mypanel:create_snapshot"
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
try:
|
||||
return api.nova.server_get(self.request,
|
||||
self.kwargs["instance_id"])
|
||||
except Exception:
|
||||
exceptions.handle(self.request,
|
||||
_("Unable to retrieve instance."))
|
||||
|
||||
def get_initial(self):
|
||||
return {"instance_id": self.kwargs["instance_id"]}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateSnapshotView, self).get_context_data(**kwargs)
|
||||
instance_id = self.kwargs['instance_id']
|
||||
context['instance_id'] = instance_id
|
||||
context['instance'] = self.get_object()
|
||||
context['submit_url'] = reverse(self.submit_url, args=[instance_id])
|
||||
return context
|
||||
|
||||
|
||||
Adding the url
|
||||
==============
|
||||
|
||||
We must add the url for our new view. Open the ``urls.py`` file under
|
||||
the ``mypanel`` directory and add the following as a new url pattern::
|
||||
|
||||
url(r'^(?P<instance_id>[^/]+)/create_snapshot/$',
|
||||
views.CreateSnapshotView.as_view(),
|
||||
name='create_snapshot'),
|
||||
|
||||
The complete ``urls.py`` file should look like this::
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.dashboards.mydashboard.mypanel import views
|
||||
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^\?tab=mypanel_tabs_tab$',
|
||||
views.IndexView.as_view(), name='mypanel_tabs'),
|
||||
url(r'^(?P<instance_id>[^/]+)/create_snapshot/$',
|
||||
views.CreateSnapshotView.as_view(),
|
||||
name='create_snapshot'),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Define the action
|
||||
=================
|
||||
|
||||
Horizon provides a :class:`~horizon.tables.LinkAction` class which simplifies
|
||||
adding an action which can be used to display another view.
|
||||
|
||||
We will add a link action to the table that will be accessible from each row
|
||||
in the table. The action will use the view defined above to create a snapshot
|
||||
of the instance represented by the row in the table.
|
||||
|
||||
To do this, we must edit the ``tables.py`` file under the ``mypanel`` directory
|
||||
and add the following::
|
||||
|
||||
def is_deleting(instance):
|
||||
task_state = getattr(instance, "OS-EXT-STS:task_state", None)
|
||||
if not task_state:
|
||||
return False
|
||||
return task_state.lower() == "deleting"
|
||||
|
||||
|
||||
class CreateSnapshotAction(tables.LinkAction):
|
||||
name = "snapshot"
|
||||
verbose_name = _("Create Snapshot")
|
||||
url = "horizon:mydashboard:mypanel:create_snapshot"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "camera"
|
||||
|
||||
# This action should be disabled if the instance
|
||||
# is not active, or the instance is being deleted
|
||||
def allowed(self, request, instance=None):
|
||||
return instance.status in ("ACTIVE") \
|
||||
and not is_deleting(instance)
|
||||
|
||||
|
||||
We must also add our new action as a row action for the table::
|
||||
|
||||
row_actions = (CreateSnapshotAction,)
|
||||
|
||||
|
||||
The complete ``tables.py`` file should look like this::
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
|
||||
def is_deleting(instance):
|
||||
task_state = getattr(instance, "OS-EXT-STS:task_state", None)
|
||||
if not task_state:
|
||||
return False
|
||||
return task_state.lower() == "deleting"
|
||||
|
||||
|
||||
class CreateSnapshotAction(tables.LinkAction):
|
||||
name = "snapshot"
|
||||
verbose_name = _("Create Snapshot")
|
||||
url = "horizon:mydashboard:mypanel:create_snapshot"
|
||||
classes = ("ajax-modal",)
|
||||
icon = "camera"
|
||||
|
||||
def allowed(self, request, instance=None):
|
||||
return instance.status in ("ACTIVE") \
|
||||
and not is_deleting(instance)
|
||||
|
||||
|
||||
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,)
|
||||
row_actions = (CreateSnapshotAction,)
|
||||
|
||||
|
||||
Run and check the dashboard
|
||||
===========================
|
||||
|
||||
We must once again run horizon to verify our dashboard is working::
|
||||
|
||||
./run_tests.sh --runserver 0.0.0.0:8877
|
||||
|
||||
|
||||
Go to ``http://<your server>:8877`` using a browser. After login as an admin,
|
||||
display ``My Panel`` to see the ``Instances`` table. For every ``ACTIVE``
|
||||
instance in the table, there will be a ``Create Snapshot`` action on the row.
|
||||
Click on ``Create Snapshot``, enter a snapshot name in the form that is shown,
|
||||
then click to close the form. The ``Project Images`` view should be shown with
|
||||
the new snapshot added to the table.
|
||||
|
||||
|
||||
Conclusion
|
||||
==========
|
||||
|
||||
What you've learned here is the fundamentals of how to add a table action that
|
||||
requires a form for data entry. This can easily be expanded from creating a
|
||||
snapshot to other API calls that require more complex forms to gather the
|
||||
necessary information.
|
||||
|
||||
If you have feedback on how this tutorial could be improved, please feel free
|
||||
to submit a bug against ``Horizon`` in `launchpad`_.
|
||||
|
||||
.. _launchpad: https://bugs.launchpad.net/horizon
|
@ -1,387 +0,0 @@
|
||||
======================
|
||||
DataTables Topic Guide
|
||||
======================
|
||||
|
||||
Horizon provides the :mod:`horizon.tables` module to provide
|
||||
a convenient, reusable API for building data-driven displays and interfaces.
|
||||
The core components of this API fall into three categories: ``DataTables``,
|
||||
``Actions``, and ``Class-based Views``.
|
||||
|
||||
.. seealso::
|
||||
|
||||
For a detailed API information check out the :doc:`DataTables Reference
|
||||
Guide </ref/tables>`.
|
||||
|
||||
Tables
|
||||
======
|
||||
|
||||
The majority of interface in a dashboard-style interface ends up being
|
||||
tabular displays of the various resources the dashboard interacts with.
|
||||
The :class:`~horizon.tables.DataTable` class exists so you don't have to
|
||||
reinvent the wheel each time.
|
||||
|
||||
Creating your own tables
|
||||
------------------------
|
||||
|
||||
Creating a table is fairly simple:
|
||||
|
||||
#. Create a subclass of :class:`~horizon.tables.DataTable`.
|
||||
#. Define columns on it using :class:`~horizon.tables.Column`.
|
||||
#. Create an inner ``Meta`` class to contain the special options for
|
||||
this table.
|
||||
#. Define any actions for the table, and add them to
|
||||
:attr:`~horizon.tables.DataTableOptions.table_actions` or
|
||||
:attr:`~horizon.tables.DataTableOptions.row_actions`.
|
||||
|
||||
Examples of this can be found in any of the ``tables.py`` modules included
|
||||
in the reference modules under ``horizon.dashboards``.
|
||||
|
||||
Connecting a table to a view
|
||||
----------------------------
|
||||
|
||||
Once you've got your table set up the way you like it, the next step is to
|
||||
wire it up to a view. To make this as easy as possible Horizon provides the
|
||||
:class:`~horizon.tables.DataTableView` class-based view which can be subclassed
|
||||
to display your table with just a couple lines of code. At its simplest, it
|
||||
looks like this::
|
||||
|
||||
from horizon import tables
|
||||
from .tables import MyTable
|
||||
|
||||
|
||||
class MyTableView(tables.DataTableView):
|
||||
table_class = MyTable
|
||||
template_name = "my_app/my_table_view.html"
|
||||
|
||||
def get_data(self):
|
||||
return my_api.objects.list()
|
||||
|
||||
In the template you would just need to include the following to render the
|
||||
table::
|
||||
|
||||
{{ table.render }}
|
||||
|
||||
That's it! Easy, right?
|
||||
|
||||
Actions
|
||||
=======
|
||||
|
||||
Actions comprise any manipulations that might happen on the data in the table
|
||||
or the table itself. For example, this may be the standard object CRUD, linking
|
||||
to related views based on the object's id, filtering the data in the table,
|
||||
or fetching updated data when appropriate.
|
||||
|
||||
When actions get run
|
||||
--------------------
|
||||
|
||||
There are two points in the request-response cycle in which actions can
|
||||
take place; prior to data being loaded into the table, and after the data
|
||||
is loaded. When you're using one of the pre-built class-based views for
|
||||
working with your tables the pseudo-workflow looks like this:
|
||||
|
||||
#. The request enters view.
|
||||
#. The table class is instantiated without data.
|
||||
#. Any "preemptive" actions are checked to see if they should run.
|
||||
#. Data is fetched and loaded into the table.
|
||||
#. All other actions are checked to see if they should run.
|
||||
#. If none of the actions have caused an early exit from the view,
|
||||
the standard response from the view is returned (usually the
|
||||
rendered table).
|
||||
|
||||
The benefit of the multi-step table instantiation is that you can use
|
||||
preemptive actions which don't need access to the entire collection of data
|
||||
to save yourself on processing overhead, API calls, etc.
|
||||
|
||||
Basic actions
|
||||
-------------
|
||||
|
||||
At their simplest, there are three types of actions: actions which act on the
|
||||
data in the table, actions which link to related resources, and actions that
|
||||
alter which data is displayed. These correspond to
|
||||
:class:`~horizon.tables.Action`, :class:`~horizon.tables.LinkAction`, and
|
||||
:class:`~horizon.tables.FilterAction`.
|
||||
|
||||
Writing your own actions generally starts with subclassing one of those
|
||||
action classes and customizing the designated attributes and methods.
|
||||
|
||||
Shortcut actions
|
||||
----------------
|
||||
|
||||
There are several common tasks for which Horizon provides pre-built shortcut
|
||||
classes. These include :class:`~horizon.tables.BatchAction`, and
|
||||
:class:`~horizon.tables.DeleteAction`. Each of these abstracts away nearly
|
||||
all of the boilerplate associated with writing these types of actions and
|
||||
provides consistent error handling, logging, and user-facing interaction.
|
||||
|
||||
It is worth noting that ``BatchAction`` and ``DeleteAction`` are extensions
|
||||
of the standard ``Action`` class. Some ``BatchAction`` or ``DeleteAction``
|
||||
classes may cause some unrecoverable results, like deleted images or
|
||||
unrecoverable instances. It may be helpful to specify specific help_text to
|
||||
explain the concern to the user, such as "Deleted images are not recoverable".
|
||||
|
||||
Preemptive actions
|
||||
------------------
|
||||
|
||||
Action classes which have their :attr:`~horizon.tables.Action.preempt`
|
||||
attribute set to ``True`` will be evaluated before any data is loaded into
|
||||
the table. As such, you must be careful not to rely on any table methods that
|
||||
require data, such as :meth:`~horizon.tables.DataTable.get_object_display` or
|
||||
:meth:`~horizon.tables.DataTable.get_object_by_id`. The advantage of preemptive
|
||||
actions is that you can avoid having to do all the processing, API calls, etc.
|
||||
associated with loading data into the table for actions which don't require
|
||||
access to that information.
|
||||
|
||||
Policy checks on actions
|
||||
------------------------
|
||||
|
||||
The :attr:`~horizon.tables.Action.policy_rules` attribute, when set, will
|
||||
validate access to the action using the policy rules specified. The attribute
|
||||
is a list of scope/rule pairs. Where the scope is the service type defining
|
||||
the rule and the rule is a rule from the corresponding service policy.json
|
||||
file. The format of :attr:`horizon.tables.Action.policy_rules` looks like::
|
||||
|
||||
(("identity", "identity:get_user"),)
|
||||
|
||||
Multiple checks can be made for the same action by merely adding more tuples
|
||||
to the list. The policy check will use information stored in the session
|
||||
about the user and the result of
|
||||
:meth:`~horizon.tables.Action.get_policy_target` (which can be overridden in
|
||||
the derived action class) to determine if the user
|
||||
can execute the action. If the user does not have access to the action, the
|
||||
action is not added to the table.
|
||||
|
||||
If :attr:`~horizon.tables.Action.policy_rules` is not set, no policy checks
|
||||
will be made to determine if the action should be visible and will be
|
||||
displayed solely based on the result of
|
||||
:meth:`~horizon.tables.Action.allowed`.
|
||||
|
||||
For more information on policy based Role Based Access Control see:
|
||||
:doc:`Horizon Policy Enforcement (RBAC: Role Based Access Control) </topics/policy>`.
|
||||
|
||||
Table Cell filters (decorators)
|
||||
===============================
|
||||
|
||||
DataTable displays lists of objects in rows and object attributes in cell.
|
||||
How should we proceed, if we want to decorate some column, e.g. if we have
|
||||
column ``memory`` which returns a number e.g. 1024, and we want to show
|
||||
something like 1024.00 GB inside table?
|
||||
|
||||
Decorator pattern
|
||||
-----------------
|
||||
|
||||
The clear anti-pattern is defining the new attributes on object like
|
||||
``ram_float_format_2_gb`` or to tweak a DataTable in any way for displaying
|
||||
purposes.
|
||||
|
||||
The cleanest way is to use ``filters``. Filters are decorators, following GOF
|
||||
``Decorator pattern``. This way ``DataTable logic`` and ``displayed object
|
||||
logic`` are correctly separated from ``presentation logic`` of the object
|
||||
inside of the various tables. And therefore the filters are reusable in all
|
||||
tables.
|
||||
|
||||
Filter function
|
||||
---------------
|
||||
|
||||
Horizon DatablesTable takes a tuple of pointers to filter functions
|
||||
or anonymous lambda functions. When displaying a ``Cell``, ``DataTable``
|
||||
takes ``Column`` filter functions from left to right, using the returned value
|
||||
of the previous function as a parameter of the following function. Then
|
||||
displaying the returned value of the last filter function.
|
||||
|
||||
A valid filter function takes one parameter and returns the decorated value.
|
||||
So e.g. these are valid filter functions ::
|
||||
|
||||
# Filter function.
|
||||
def add_unit(v):
|
||||
return str(v) + " GB"
|
||||
|
||||
# Or filter lambda function.
|
||||
lambda v: str(v) + " GB"
|
||||
|
||||
# This is also a valid definition of course, although for the change of the
|
||||
# unit parameter, function has to be wrapped by lambda
|
||||
# (e.g. floatformat function example below).
|
||||
def add_unit(v, unit="GB"):
|
||||
return str(v) + " " + unit
|
||||
|
||||
Using filters in DataTable column
|
||||
---------------------------------
|
||||
|
||||
DataTable takes tuple of filter functions, so e.g. this is valid decorating
|
||||
of a value with float format and with unit ::
|
||||
|
||||
ram = tables.Column(
|
||||
"ram",
|
||||
verbose_name=_('Memory'),
|
||||
filters=(lambda v: floatformat(v, 2),
|
||||
add_unit))
|
||||
|
||||
It always takes tuple, so using only one filter would look like this ::
|
||||
|
||||
filters=(lambda v: floatformat(v, 2),)
|
||||
|
||||
The decorated parameter doesn't have to be only a string or number, it can
|
||||
be anything e.g. list or an object. So decorating of object, that has
|
||||
attributes value and unit would look like this ::
|
||||
|
||||
ram = tables.Column(
|
||||
"ram",
|
||||
verbose_name=_('Memory'),
|
||||
filters=(lambda x: getattr(x, 'value', '') +
|
||||
" " + getattr(x, 'unit', ''),))
|
||||
|
||||
Available filters
|
||||
-----------------
|
||||
|
||||
There are a load of filters, that can be used, defined in django already:
|
||||
https://github.com/django/django/blob/master/django/template/defaultfilters.py
|
||||
|
||||
So it's enough to just import and use them, e.g. ::
|
||||
|
||||
from django.template import defaultfilters as filters
|
||||
|
||||
# code omitted
|
||||
filters=(filters.yesno, filters.capfirst)
|
||||
|
||||
|
||||
from django.template.defaultfilters import timesince
|
||||
from django.template.defaultfilters import title
|
||||
|
||||
# code omitted
|
||||
filters=(parse_isotime, timesince)
|
||||
|
||||
|
||||
Inline editing
|
||||
==============
|
||||
|
||||
Table cells can be easily upgraded with in-line editing. With use of
|
||||
django.form.Field, we are able to run validations of the field and correctly
|
||||
parse the data. The updating process is fully encapsulated into table
|
||||
functionality, communication with the server goes through AJAX in JSON format.
|
||||
The javacript wrapper for inline editing allows each table cell that has
|
||||
in-line editing available to:
|
||||
|
||||
#. Refresh itself with new data from the server.
|
||||
#. Display in edit mod.
|
||||
#. Send changed data to server.
|
||||
#. Display validation errors.
|
||||
|
||||
There are basically 3 things that need to be defined in the table in order
|
||||
to enable in-line editing.
|
||||
|
||||
Fetching the row data
|
||||
---------------------
|
||||
|
||||
Defining an ``get_data`` method in a class inherited from ``tables.Row``.
|
||||
This method takes care of fetching the row data. This class has to be then
|
||||
defined in the table Meta class as ``row_class = UpdateRow``.
|
||||
|
||||
Example::
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
# this method is also used for automatic update of the row
|
||||
ajax = True
|
||||
|
||||
def get_data(self, request, project_id):
|
||||
# getting all data of all row cells
|
||||
project_info = api.keystone.tenant_get(request, project_id,
|
||||
admin=True)
|
||||
return project_info
|
||||
|
||||
Updating changed cell data
|
||||
--------------------------
|
||||
|
||||
Define an ``update_cell`` method in the class inherited from
|
||||
``tables.UpdateAction``. This method takes care of saving the data of the
|
||||
table cell. There can be one class for every cell thanks to the
|
||||
``cell_name`` parameter. This class is then defined in tables column as
|
||||
``update_action=UpdateCell``, so each column can have its own updating
|
||||
method.
|
||||
|
||||
Example::
|
||||
|
||||
class UpdateCell(tables.UpdateAction):
|
||||
def allowed(self, request, project, cell):
|
||||
# Determines whether given cell or row will be inline editable
|
||||
# for signed in user.
|
||||
return api.keystone.keystone_can_edit_project()
|
||||
|
||||
def update_cell(self, request, project_id, cell_name, new_cell_value):
|
||||
# in-line update project info
|
||||
try:
|
||||
project_obj = datum
|
||||
# updating changed value by new value
|
||||
setattr(project_obj, cell_name, new_cell_value)
|
||||
|
||||
# sending new attributes back to API
|
||||
api.keystone.tenant_update(
|
||||
request,
|
||||
project_id,
|
||||
name=project_obj.name,
|
||||
description=project_obj.description,
|
||||
enabled=project_obj.enabled)
|
||||
|
||||
except Conflict:
|
||||
# Validation error for naming conflict, raised when user
|
||||
# choose the existing name. We will raise a
|
||||
# ValidationError, that will be sent back to the client
|
||||
# browser and shown inside of the table cell.
|
||||
message = _("This name is already taken.")
|
||||
raise ValidationError(message)
|
||||
except:
|
||||
# Other exception of the API just goes through standard
|
||||
# channel
|
||||
exceptions.handle(request, ignore=True)
|
||||
return False
|
||||
return True
|
||||
|
||||
Defining a form_field for each Column that we want to be in-line edited.
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Form field should be ``django.form.Field`` instance, so we can use django
|
||||
validations and parsing of the values sent by POST (in example validation
|
||||
``required=True`` and correct parsing of the checkbox value from the POST
|
||||
data).
|
||||
|
||||
Form field can be also ``django.form.Widget`` class, if we need to just
|
||||
display the form widget in the table and we don't need Field functionality.
|
||||
|
||||
Then connecting ``UpdateRow`` and ``UpdateCell`` classes to the table.
|
||||
|
||||
Example::
|
||||
|
||||
class TenantsTable(tables.DataTable):
|
||||
# Adding html text input for inline editing, with required validation.
|
||||
# HTML form input will have a class attribute tenant-name-input, we
|
||||
# can define here any HTML attribute we need.
|
||||
name = tables.Column('name', verbose_name=_('Name'),
|
||||
form_field=forms.CharField(required=True),
|
||||
form_field_attributes={'class':'tenant-name-input'},
|
||||
update_action=UpdateCell)
|
||||
|
||||
# Adding html textarea without required validation.
|
||||
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
||||
verbose_name=_('Description'),
|
||||
form_field=forms.CharField(
|
||||
widget=forms.Textarea(),
|
||||
required=False),
|
||||
update_action=UpdateCell)
|
||||
|
||||
# Id will not be inline edited.
|
||||
id = tables.Column('id', verbose_name=_('Project ID'))
|
||||
|
||||
# Adding html checkbox, that will be shown inside of the table cell with
|
||||
# label
|
||||
enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True,
|
||||
form_field=forms.BooleanField(
|
||||
label=_('Enabled'),
|
||||
required=False),
|
||||
update_action=UpdateCell)
|
||||
|
||||
class Meta:
|
||||
name = "tenants"
|
||||
verbose_name = _("Projects")
|
||||
# Connection to UpdateRow, so table can fetch row data based on
|
||||
# their primary key.
|
||||
row_class = UpdateRow
|
||||
|
@ -1,276 +0,0 @@
|
||||
===================
|
||||
Testing Topic Guide
|
||||
===================
|
||||
|
||||
Having good tests in place is absolutely critical for ensuring a stable,
|
||||
maintainable codebase. Hopefully that doesn't need any more explanation.
|
||||
|
||||
However, what defines a "good" test is not always obvious, and there are
|
||||
a lot of common pitfalls that can easily shoot your test suite in the
|
||||
foot.
|
||||
|
||||
If you already know everything about testing but are fed up with trying to
|
||||
debug why a specific test failed, you can skip the intro and jump
|
||||
straight to :ref:`debugging_unit_tests`.
|
||||
|
||||
An overview of testing
|
||||
======================
|
||||
|
||||
There are three main types of tests, each with their associated pros and cons:
|
||||
|
||||
Unit tests
|
||||
----------
|
||||
|
||||
These are isolated, stand-alone tests with no external dependencies. They are
|
||||
written from the perspective of "knowing the code", and test the assumptions
|
||||
of the codebase and the developer.
|
||||
|
||||
Pros:
|
||||
|
||||
* Generally lightweight and fast.
|
||||
* Can be run anywhere, anytime since they have no external dependencies.
|
||||
|
||||
Cons:
|
||||
|
||||
* Easy to be lax in writing them, or lazy in constructing them.
|
||||
* Can't test interactions with live external services.
|
||||
|
||||
Functional tests
|
||||
----------------
|
||||
|
||||
These are generally also isolated tests, though sometimes they may interact
|
||||
with other services running locally. The key difference between functional
|
||||
tests and unit tests, however, is that functional tests are written from the
|
||||
perspective of the user (who knows nothing about the code) and only knows
|
||||
what they put in and what they get back. Essentially this is a higher-level
|
||||
testing of "does the result match the spec?".
|
||||
|
||||
Pros:
|
||||
|
||||
* Ensures that your code *always* meets the stated functional requirements.
|
||||
* Verifies things from an "end user" perspective, which helps to ensure
|
||||
a high-quality experience.
|
||||
* Designing your code with a functional testing perspective in mind helps
|
||||
keep a higher-level viewpoint in mind.
|
||||
|
||||
Cons:
|
||||
|
||||
* Requires an additional layer of thinking to define functional requirements
|
||||
in terms of inputs and outputs.
|
||||
* Often requires writing a separate set of tests and/or using a different
|
||||
testing framework from your unit tests.
|
||||
* Doesn't offer any insight into the quality or status of the underlying code,
|
||||
only verifies that it works or it doesn't.
|
||||
|
||||
Integration Tests
|
||||
-----------------
|
||||
|
||||
This layer of testing involves testing all of the components that your
|
||||
codebase interacts with or relies on in conjunction. This is equivalent to
|
||||
"live" testing, but in a repeatable manner.
|
||||
|
||||
Pros:
|
||||
|
||||
* Catches *many* bugs that unit and functional tests will not.
|
||||
* Doesn't rely on assumptions about the inputs and outputs.
|
||||
* Will warn you when changes in external components break your code.
|
||||
|
||||
Cons:
|
||||
|
||||
* Difficult and time-consuming to create a repeatable test environment.
|
||||
* Did I mention that setting it up is a pain?
|
||||
|
||||
So what should I write?
|
||||
-----------------------
|
||||
|
||||
A few simple guidelines:
|
||||
|
||||
#. Every bug fix should have a regression test. Period.
|
||||
|
||||
#. When writing a new feature, think about writing unit tests to verify
|
||||
the behavior step-by-step as you write the feature. Every time you'd
|
||||
go to run your code by hand and verify it manually, think "could I
|
||||
write a test to do this instead?". That way when the feature is done
|
||||
and you're ready to commit it you've already got a whole set of tests
|
||||
that are more thorough than anything you'd write after the fact.
|
||||
|
||||
#. Write tests that hit every view in your application. Even if they
|
||||
don't assert a single thing about the code, it tells you that your
|
||||
users aren't getting fatal errors just by interacting with your code.
|
||||
|
||||
What makes a good unit test?
|
||||
============================
|
||||
|
||||
Limiting our focus just to unit tests, there are a number of things you can
|
||||
do to make your unit tests as useful, maintainable, and unburdensome as
|
||||
possible.
|
||||
|
||||
Test data
|
||||
---------
|
||||
|
||||
Use a single, consistent set of test data. Grow it over time, but do everything
|
||||
you can not to fragment it. It quickly becomes unmaintainable and perniciously
|
||||
out-of-sync with reality.
|
||||
|
||||
Make your test data as accurate to reality as possible. Supply *all* the
|
||||
attributes of an object, provide objects in all the various states you may want
|
||||
to test.
|
||||
|
||||
If you do the first suggestion above *first* it makes the second one far less
|
||||
painful. Write once, use everywhere.
|
||||
|
||||
To make your life even easier, if your codebase doesn't have a built-in
|
||||
ORM-like function to manage your test data you can consider building (or
|
||||
borrowing) one yourself. Being able to do simple retrieval queries on your
|
||||
test data is incredibly valuable.
|
||||
|
||||
Mocking
|
||||
-------
|
||||
|
||||
Mocking is the practice of providing stand-ins for objects or pieces of code
|
||||
you don't need to test. While convenient, they should be used with *extreme*
|
||||
caution.
|
||||
|
||||
Why? Because overuse of mocks can rapidly land you in a situation where you're
|
||||
not testing any real code. All you've done is verified that your mocking
|
||||
framework returns what you tell it to. This problem can be very tricky to
|
||||
recognize, since you may be mocking things in ``setUp`` methods, other modules,
|
||||
etc.
|
||||
|
||||
A good rule of thumb is to mock as close to the source as possible. If you have
|
||||
a function call that calls an external API in a view , mock out the external
|
||||
API, not the whole function. If you mock the whole function you've suddenly
|
||||
lost test coverage for an entire chunk of code *inside* your codebase. Cut the
|
||||
ties cleanly right where your system ends and the external world begins.
|
||||
|
||||
Similarly, don't mock return values when you could construct a real return
|
||||
value of the correct type with the correct attributes. You're just adding
|
||||
another point of potential failure by exercising your mocking framework instead
|
||||
of real code. Following the suggestions for testing above will make this a lot
|
||||
less burdensome.
|
||||
|
||||
Assertions and verification
|
||||
---------------------------
|
||||
|
||||
Think long and hard about what you really want to verify in your unit test. In
|
||||
particular, think about what custom logic your code executes.
|
||||
|
||||
A common pitfall is to take a known test object, pass it through your code,
|
||||
and then verify the properties of that object on the output. This is all well
|
||||
and good, except if you're verifying properties that were untouched by your
|
||||
code. What you want to check are the pieces that were *changed*, *added*, or
|
||||
*removed*. Don't check the object's id attribute unless you have reason to
|
||||
suspect it's not the object you started with. But if you added a new attribute
|
||||
to it, be damn sure you verify that came out right.
|
||||
|
||||
It's also very common to avoid testing things you really care about because
|
||||
it's more difficult. Verifying that the proper messages were displayed to the
|
||||
user after an action, testing for form errors, making sure exception handling
|
||||
is tested... these types of things aren't always easy, but they're extremely
|
||||
necessary.
|
||||
|
||||
To that end, Horizon includes several custom assertions to make these tasks
|
||||
easier. :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors`,
|
||||
:meth:`~horizon.test.helpers.TestCase.assertMessageCount`, and
|
||||
:meth:`~horizon.test.helpers.TestCase.assertNoMessages` all exist for exactly
|
||||
these purposes. Moreover, they provide useful output when things go wrong so
|
||||
you're not left scratching your head wondering why your view test didn't
|
||||
redirect as expected when you posted a form.
|
||||
|
||||
.. _debugging_unit_tests:
|
||||
|
||||
Debugging Unit Tests
|
||||
====================
|
||||
|
||||
Tips and tricks
|
||||
---------------
|
||||
|
||||
#. Use :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors`
|
||||
immediately after your ``client.post`` call for tests that handle form views.
|
||||
This will immediately fail if your form POST failed due to a validation error
|
||||
and tell you what the error was.
|
||||
|
||||
#. Use :meth:`~horizon.test.helpers.TestCase.assertMessageCount` and
|
||||
:meth:`~horizon.test.helpers.TestCase.assertNoMessages` when a piece of code
|
||||
is failing inexplicably. Since the core error handlers attach user-facing
|
||||
error messages (and since the core logging is silenced during test runs)
|
||||
these methods give you the dual benefit of verifying the output you expect
|
||||
while clearly showing you the problematic error message if they fail.
|
||||
|
||||
#. Use Python's ``pdb`` module liberally. Many people don't realize it works
|
||||
just as well in a test case as it does in a live view. Simply inserting
|
||||
``import pdb; pdb.set_trace()`` anywhere in your codebase will drop the
|
||||
interpreter into an interactive shell so you can explore your test
|
||||
environment and see which of your assumptions about the code isn't,
|
||||
in fact, flawlessly correct.
|
||||
|
||||
Common pitfalls
|
||||
---------------
|
||||
|
||||
There are a number of typical (and non-obvious) ways to break the unit tests.
|
||||
Some common things to look for:
|
||||
|
||||
#. Make sure you stub out the method exactly as it's called in the code
|
||||
being tested. For example, if your real code calls
|
||||
``api.keystone.tenant_get``, stubbing out ``api.tenant_get`` (available
|
||||
for legacy reasons) will fail.
|
||||
|
||||
#. When defining the expected input to a stubbed call, make sure the
|
||||
arguments are *identical*, this includes ``str`` vs. ``int`` differences.
|
||||
|
||||
#. Make sure your test data are completely in line with the expected inputs.
|
||||
Again, ``str`` vs. ``int`` or missing properties on test objects will
|
||||
kill your tests.
|
||||
|
||||
#. Make sure there's nothing amiss in your templates (particularly the
|
||||
``{% url %}`` tag and its arguments). This often comes up when refactoring
|
||||
views or renaming context variables. It can easily result in errors that
|
||||
you might not stumble across while clicking around the development server.
|
||||
|
||||
#. Make sure you're not redirecting to views that no longer exist, e.g.
|
||||
the ``index`` view for a panel that got combined (such as instances &
|
||||
volumes).
|
||||
|
||||
#. Make sure your mock calls are in order before calling ``mox.ReplayAll``.
|
||||
The order matters.
|
||||
|
||||
#. Make sure you repeat any stubbed out method calls that happen more than
|
||||
once. They don't automatically repeat, you have to explicitly define them.
|
||||
While this is a nuisance, it makes you acutely aware of how many API
|
||||
calls are involved in a particular function.
|
||||
|
||||
Understanding the output from ``mox``
|
||||
-------------------------------------
|
||||
|
||||
Horizon uses ``mox`` as its mocking framework of choice, and while it
|
||||
offers many nice features, its output when a test fails can be quite
|
||||
mysterious.
|
||||
|
||||
Unexpected Method Call
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This occurs when you stubbed out a piece of code, and it was subsequently
|
||||
called in a way that you didn't specify it would be. There are two reasons
|
||||
this tends to come up:
|
||||
|
||||
#. You defined the expected call, but a subtle difference crept in. This
|
||||
may be a string versus integer difference, a string versus unicode
|
||||
difference, a slightly off date/time, or passing a name instead of an id.
|
||||
|
||||
#. The method is actually being called *multiple times*. Since mox uses
|
||||
a call stack internally, it simply pops off the expected method calls to
|
||||
verify them. That means once a call is used once, it's gone. An easy way
|
||||
to see if this is the case is simply to copy and paste your method call a
|
||||
second time to see if the error changes. If it does, that means your method
|
||||
is being called more times than you think it is.
|
||||
|
||||
Expected Method Never Called
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This one is the opposite of the unexpected method call. This one means you
|
||||
told mox to expect a call and it didn't happen. This is almost always the
|
||||
result of an error in the conditions of the test. Using the
|
||||
:meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors` and
|
||||
:meth:`~horizon.test.helpers.TestCase.assertMessageCount` will make it readily
|
||||
apparent what the problem is in the majority of cases. If not, then use ``pdb``
|
||||
and start interrupting the code flow to see where things are getting off track.
|
@ -1,629 +0,0 @@
|
||||
============================================
|
||||
Tutorial: Building a Dashboard using Horizon
|
||||
============================================
|
||||
|
||||
This tutorial covers how to use the various components in Horizon to build
|
||||
an example dashboard and a panel with a tab which has a table containing data
|
||||
from the back end.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
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
|
||||
with the :doc:`Horizon quickstart guide </quickstart>` or the
|
||||
`Django tutorial`_.
|
||||
|
||||
.. _Django tutorial: https://docs.djangoproject.com/en/1.6/intro/tutorial01/
|
||||
|
||||
|
||||
Creating a dashboard
|
||||
====================
|
||||
|
||||
The quick version
|
||||
-----------------
|
||||
|
||||
Horizon provides a custom management command to create a typical base
|
||||
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::
|
||||
|
||||
mkdir openstack_dashboard/dashboards/mydashboard
|
||||
|
||||
./run_tests.sh -m startdash mydashboard \
|
||||
--target openstack_dashboard/dashboards/mydashboard
|
||||
|
||||
mkdir openstack_dashboard/dashboards/mydashboard/mypanel
|
||||
|
||||
./run_tests.sh -m startpanel mypanel \
|
||||
--dashboard=openstack_dashboard.dashboards.mydashboard \
|
||||
--target=openstack_dashboard/dashboards/mydashboard/mypanel
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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
|
||||
├── models.py
|
||||
├── mypanel
|
||||
│ ├── __init__.py
|
||||
│ ├── models.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
|
||||
|
||||
|
||||
For this tutorial, we will not deal with the static directory, the ``models.py``
|
||||
file and tests.py file. Leave them as they are.
|
||||
|
||||
With the rest of the files and directories in place, we can move on to add our
|
||||
own dashboard.
|
||||
|
||||
|
||||
Defining a dashboard
|
||||
--------------------
|
||||
|
||||
Open the ``dashboard.py`` file. You will notice the following code has been
|
||||
automatically generated::
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
|
||||
class Mydashboard(horizon.Dashboard):
|
||||
name = _("Mydashboard")
|
||||
slug = "mydashboard"
|
||||
panels = () # Add your panels here.
|
||||
default_panel = '' # Specify the slug of the dashboard's default panel.
|
||||
|
||||
|
||||
horizon.register(Mydashboard)
|
||||
|
||||
|
||||
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`` ::
|
||||
|
||||
name = _("My Dashboard")
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Creating a panel
|
||||
================
|
||||
|
||||
We'll create a panel and call it ``My Panel``.
|
||||
|
||||
Structure
|
||||
---------
|
||||
|
||||
As described above, the ``mypanel`` directory under
|
||||
``openstack_dashboard/dashboards/mydashboard`` should look like the following::
|
||||
|
||||
mypanel
|
||||
├── __init__.py
|
||||
├── models.py
|
||||
├── panel.py
|
||||
├── templates
|
||||
│ └── mypanel
|
||||
│ └── index.html
|
||||
├── tests.py
|
||||
├── urls.py
|
||||
└── views.py
|
||||
|
||||
|
||||
Defining a panel
|
||||
----------------
|
||||
|
||||
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).
|
||||
|
||||
Open the ``panel.py`` file, you will have the following auto-generated code::
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
|
||||
from openstack_dashboard.dashboards.mydashboard import dashboard
|
||||
|
||||
|
||||
class Mypanel(horizon.Panel):
|
||||
name = _("Mypanel")
|
||||
slug = "mypanel"
|
||||
|
||||
|
||||
dashboard.Mydashboard.register(Mypanel)
|
||||
|
||||
|
||||
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``::
|
||||
|
||||
name = _("My Panel")
|
||||
|
||||
|
||||
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``::
|
||||
|
||||
class Mygroup(horizon.PanelGroup):
|
||||
slug = "mygroup"
|
||||
name = _("My Group")
|
||||
panels = ('mypanel',)
|
||||
|
||||
|
||||
Modify the ``Mydashboard`` class to include ``Mygroup`` and add ``mypanel`` as
|
||||
the default panel::
|
||||
|
||||
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.
|
||||
|
||||
|
||||
The completed ``dashboard.py`` file should look like
|
||||
the following::
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
||||
Tables, Tabs, and Views
|
||||
-----------------------
|
||||
|
||||
We'll start with the table, combine that with the tabs, and then build our
|
||||
view from the pieces.
|
||||
|
||||
Defining a table
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Horizon provides a :class:`~horizon.forms.SelfHandlingForm` :class:`~horizon.tables.DataTable` class which simplifies
|
||||
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.
|
||||
|
||||
Create a ``tables.py`` file under the ``mypanel`` directory and add the
|
||||
following code::
|
||||
|
||||
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")
|
||||
|
||||
|
||||
There are several things going on here... we created a table subclass,
|
||||
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.
|
||||
|
||||
Lastly, we added a ``Meta`` class which indicates the meta object that describes
|
||||
the ``instances`` table.
|
||||
|
||||
.. note::
|
||||
|
||||
This is a slight simplification from the reality of how the instance
|
||||
object is actually structured. In reality, accessing other attributes
|
||||
requires an additional step.
|
||||
|
||||
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,)
|
||||
|
||||
|
||||
Defining tabs
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
So we have a table, ready to receive our data. We could go straight to a view
|
||||
from here, but in this case we're also going to use Horizon's
|
||||
:class:`~horizon.tabs.TabGroup` class.
|
||||
|
||||
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::
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Lastly, this code introduces the concept of error handling in Horizon.
|
||||
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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There are lots of pre-built class-based views in Horizon. We try to provide
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
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::
|
||||
|
||||
class IndexView(tabs.TabbedTableView):
|
||||
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
|
||||
|
||||
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
|
||||
----
|
||||
The auto-generated ``urls.py`` file is like::
|
||||
|
||||
from django.conf.urls import patterns
|
||||
from django.conf.urls import url
|
||||
|
||||
from openstack_dashboard.dashboards.mydashboard.mypanel.views \
|
||||
import IndexView
|
||||
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
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
|
||||
|
||||
|
||||
Replace the existing ``url`` pattern with the following line::
|
||||
|
||||
url(r'^$',
|
||||
views.IndexView.as_view(), name='index'),
|
||||
|
||||
|
||||
The completed ``urls.py`` file should look like the following::
|
||||
|
||||
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'),
|
||||
)
|
||||
|
||||
|
||||
The template
|
||||
~~~~~~~~~~~~
|
||||
|
||||
Open the ``index.html`` file in the ``mydashboard/mypanel/templates/mypanel``
|
||||
directory, the auto-generated code is like the following::
|
||||
|
||||
{% 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 %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
The ``main`` block must be modified to insert the following code::
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
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::
|
||||
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "My Panel" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("My Panel") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
This gives us a custom page title, a header, and renders our tab group provided
|
||||
by the view.
|
||||
|
||||
With all our code in place, the only thing left to do is to integrate it into
|
||||
our OpenStack Dashboard site.
|
||||
|
||||
|
||||
.. note::
|
||||
|
||||
For more information about Django views, URLs and templates, please refer
|
||||
to the `Django documentation`_.
|
||||
|
||||
.. _Django documentation: https://docs.djangoproject.com/en/1.6/
|
||||
|
||||
|
||||
Enable and show the dashboard
|
||||
=============================
|
||||
|
||||
In order to make ``My Dashboard`` show up along with the existing dashboards
|
||||
like ``Project`` or ``Admin`` on Horizon, you need to create a file called
|
||||
``_50_mydashboard.py`` under ``openstack_dashboard/enabled`` and add the
|
||||
following::
|
||||
|
||||
# The name of the dashboard to be added to HORIZON['dashboards']. Required.
|
||||
DASHBOARD = 'mydashboard'
|
||||
|
||||
# If set to True, this dashboard will not be added to the settings.
|
||||
DISABLED = False
|
||||
|
||||
# A list of applications to be added to INSTALLED_APPS.
|
||||
ADD_INSTALLED_APPS = [
|
||||
'openstack_dashboard.dashboards.mydashboard',
|
||||
]
|
||||
|
||||
|
||||
Run and check the dashboard
|
||||
===========================
|
||||
|
||||
Everything is in place, now run ``Horizon`` on the different port::
|
||||
|
||||
./run_tests.sh --runserver 0.0.0.0:8877
|
||||
|
||||
|
||||
Go to ``http://<your server>:8877`` using a browser. After login as an admin
|
||||
you should be able see ``My Dashboard`` shows up at the left side on Horizon.
|
||||
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.
|
||||
|
||||
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,
|
||||
``crioos-0.3.1-x86_64-uec`` , click ``Launch`` and enter an ``Instance Name``,
|
||||
click the button ``Launch``. It should create an instance if the openstack or
|
||||
devstack is correctly set up. Once the creation of an instance is successful, go
|
||||
to ``My Dashboard`` again to check the data.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Conclusion
|
||||
==========
|
||||
|
||||
What you've learned here is the fundamentals of how to write interfaces for
|
||||
your own project based on the components Horizon provides.
|
||||
|
||||
If you have feedback on how this tutorial could be improved, please feel free
|
||||
to submit a bug against ``Horizon`` in `launchpad`_.
|
||||
|
||||
.. _launchpad: https://bugs.launchpad.net/horizon
|
@ -1,134 +0,0 @@
|
||||
======================
|
||||
Workflows Topic Guide
|
||||
======================
|
||||
|
||||
One of the most challenging aspects of building a compelling user experience
|
||||
is crafting complex multi-part workflows. Horizon's ``workflows`` module
|
||||
aims to bring that capability within everyday reach.
|
||||
|
||||
.. seealso::
|
||||
|
||||
For a detailed API information check out the :doc:`Workflows Reference
|
||||
Guide </ref/workflows>`.
|
||||
|
||||
Workflows
|
||||
=========
|
||||
|
||||
Workflows are complex forms with tabs, each workflow must consist of classes
|
||||
extending the :class:`~horizon.workflows.Workflow`,
|
||||
:class:`~horizon.workflows.Step` and :class:`~horizon.workflows.Action`
|
||||
|
||||
Complex example of workflow
|
||||
----------------------------
|
||||
|
||||
The following is a complex example of how data are exchanged between
|
||||
urls, views, workflows and templates:
|
||||
|
||||
#. In ``urls.py``, we have the named parameter. E.g. ``resource_class_id``. ::
|
||||
|
||||
RESOURCE_CLASS = r'^(?P<resource_class_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(RESOURCE_CLASS % 'update', UpdateView.as_view(), name='update'))
|
||||
|
||||
#. In ``views.py``, we pass data to the template and to the action(form)
|
||||
(action can also pass data to the ``get_context_data`` method and to the
|
||||
template). ::
|
||||
|
||||
class UpdateView(workflows.WorkflowView):
|
||||
workflow_class = UpdateResourceClass
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
# Data from URL are always in self.kwargs, here we pass the data
|
||||
# to the template.
|
||||
context["resource_class_id"] = self.kwargs['resource_class_id']
|
||||
# Data contributed by Workflow's Steps are in the
|
||||
# context['workflow'].context list. We can use that in the
|
||||
# template too.
|
||||
return context
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
# Data from URL are always in self.kwargs, we can use them here
|
||||
# to load our object of interest.
|
||||
resource_class_id = self.kwargs['resource_class_id']
|
||||
# Code omitted, this method should return some object obtained
|
||||
# from API.
|
||||
|
||||
def get_initial(self):
|
||||
resource_class = self._get_object()
|
||||
# This data will be available in the Action's methods and
|
||||
# Workflow's handle method.
|
||||
# But only if the steps will depend on them.
|
||||
return {'resource_class_id': resource_class.id,
|
||||
'name': resource_class.name,
|
||||
'service_type': resource_class.service_type}
|
||||
|
||||
#. In ``workflows.py`` we process the data, it is just more complex django
|
||||
form. ::
|
||||
|
||||
class ResourcesAction(workflows.Action):
|
||||
# The name field will be automatically available in all action's
|
||||
# methods.
|
||||
# If we want this field to be used in the another Step or Workflow,
|
||||
# it has to be contributed by this step, then depend on in another
|
||||
# step.
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Testing Name"),
|
||||
help_text="",
|
||||
required=True)
|
||||
|
||||
def handle(self, request, data):
|
||||
pass
|
||||
# If we want to use some data from the URL, the Action's step
|
||||
# has to depend on them. It's then available in
|
||||
# self.initial['resource_class_id'] or data['resource_class_id'].
|
||||
# In other words, resource_class_id has to be passed by view's
|
||||
# get_initial and listed in step's depends_on list.
|
||||
|
||||
# We can also use here the data from the other steps. If we want
|
||||
# the data from the other step, the step needs to contribute the
|
||||
# data and the steps needs to be ordered properly.
|
||||
|
||||
class UpdateResources(workflows.Step):
|
||||
# This passes data from Workflow context to action methods
|
||||
# (handle, clean). Workflow context consists of URL data and data
|
||||
# contributed by other steps.
|
||||
depends_on = ("resource_class_id",)
|
||||
|
||||
# By contributing, the data on these indexes will become available to
|
||||
# Workflow and to other Steps (if they will depend on them). Notice,
|
||||
# that the resources_object_ids key has to be manually added in
|
||||
# contribute method first.
|
||||
contributes = ("resources_object_ids", "name")
|
||||
|
||||
def contribute(self, data, context):
|
||||
# We can obtain the http request from workflow.
|
||||
request = self.workflow.request
|
||||
if data:
|
||||
# Only fields defined in Action are automatically
|
||||
# available for contribution. If we want to contribute
|
||||
# something else, We need to override the contribute method
|
||||
# and manually add it to the dictionary.
|
||||
context["resources_object_ids"] =\
|
||||
request.POST.getlist("resources_object_ids")
|
||||
|
||||
# We have to merge new context with the passed data or let
|
||||
# the superclass do this.
|
||||
context.update(data)
|
||||
return context
|
||||
|
||||
class UpdateResourceClass(workflows.Workflow):
|
||||
default_steps = (UpdateResources,)
|
||||
|
||||
def handle(self, request, data):
|
||||
pass
|
||||
# This method is called as last (after all Action's handle
|
||||
# methods). All data that are listed in step's 'contributes='
|
||||
# and 'depends_on=' are available here.
|
||||
# It can be easier to have the saving logic only here if steps
|
||||
# are heavily connected or complex.
|
||||
|
||||
# data["resources_object_ids"], data["name"] and
|
||||
# data["resources_class_id"] are available here.
|
@ -69,7 +69,7 @@ function all_install
|
||||
install_rpm_by_yum "daisy"
|
||||
|
||||
write_install_log "install daisy dashboard rpm"
|
||||
install_rpm_by_daisy_yum "python-django-horizon-doc"
|
||||
install_rpm_by_daisy_yum "python-django-horizon"
|
||||
install_rpm_by_yum "daisy-dashboard"
|
||||
|
||||
write_install_log "install clustershell rpm"
|
||||
|
@ -19,7 +19,7 @@ function uninstall_daisy
|
||||
# 先停止所有服务
|
||||
echo "stop all service..."
|
||||
stop_service_all
|
||||
remove_rpms_by_yum "openstack-keystone python-django-horizon python-keystoneclient python-keystone python-keystonemiddleware python-django-horizon-doc daisy-dashboard"
|
||||
remove_rpms_by_yum "openstack-keystone python-django-horizon python-keystoneclient python-keystone python-keystonemiddleware daisy-dashboard"
|
||||
remove_rpms_by_yum "daisy python-daisyclient python-daisy"
|
||||
remove_rpms_by_yum "openstack-ironic-api openstack-ironic-common openstack-ironic-conductor python-ironicclient"
|
||||
remove_rpms_by_yum "openstack-ironic-discoverd python-ironic-discoverd"
|
||||
|
@ -32,7 +32,7 @@ function get_daisy_services
|
||||
python-ironic-discoverd
|
||||
pxe_server_install
|
||||
pxe_docker_install
|
||||
python-django-horizon-doc
|
||||
python-django-horizon
|
||||
daisy-dashboard
|
||||
"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user