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:
luyao 2016-07-20 20:07:44 +08:00 committed by Yao Lu
parent cacc1dc749
commit 64f76b55a9
40 changed files with 3 additions and 7632 deletions

View File

@ -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."

View File

@ -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

View File

@ -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.

View File

@ -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>`_.

View File

@ -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.

View File

@ -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`

View File

@ -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.

View File

@ -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.

View File

@ -1,6 +0,0 @@
==========================
Horizon Context Processors
==========================
.. automodule:: horizon.context_processors
:members:

View File

@ -1,6 +0,0 @@
==================
Horizon Decorators
==================
.. automodule:: horizon.decorators
:members:

View File

@ -1,6 +0,0 @@
==================
Horizon Exceptions
==================
.. automodule:: horizon.exceptions
:members:

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -1,6 +0,0 @@
==================
Horizon Middleware
==================
.. automodule:: horizon.middleware
:members:

View File

@ -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``.

View File

@ -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

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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>`.

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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"

View File

@ -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"

View File

@ -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
"
}