From f1a8e0c54ffa96b98c632e3fde5bb9be8345fde9 Mon Sep 17 00:00:00 2001 From: Clark Boylan Date: Fri, 8 Jun 2012 12:14:46 -0700 Subject: Move docs to doc. To better facilitate the building and publishing of sphinx documentation by Jenkins we are moving all openstack projects with sphinx documentation to a common doc tree structure. Documentation goes in project/doc and build results go in project/doc/build. Change-Id: Ic523a716e4113198b777d6dc3db8bb8a729e7696 --- .gitignore | 4 +- MANIFEST.in | 6 +- README.rst | 2 +- doc/Makefile | 153 +++++++++ doc/source/_static/.gitignore | 0 doc/source/_static/basic.css | 416 +++++++++++++++++++++++++ doc/source/_static/default.css | 230 ++++++++++++++ doc/source/_static/header-line.gif | Bin 0 -> 48 bytes doc/source/_static/header_bg.jpg | Bin 0 -> 3738 bytes doc/source/_static/jquery.tweet.js | 154 ++++++++++ doc/source/_static/openstack_logo.png | Bin 0 -> 3670 bytes doc/source/_static/tweaks.css | 93 ++++++ doc/source/_templates/.placeholder | 0 doc/source/_theme/layout.html | 83 +++++ doc/source/_theme/nature.css_t | 245 +++++++++++++++ doc/source/_theme/theme.conf | 4 + doc/source/conf.py | 417 +++++++++++++++++++++++++ doc/source/contributing.rst | 204 ++++++++++++ doc/source/faq.rst | 37 +++ doc/source/glossary.rst | 24 ++ doc/source/index.rst | 128 ++++++++ doc/source/intro.rst | 124 ++++++++ doc/source/quickstart.rst | 194 ++++++++++++ doc/source/ref/context_processors.rst | 6 + doc/source/ref/decorators.rst | 6 + doc/source/ref/exceptions.rst | 6 + doc/source/ref/forms.rst | 17 + doc/source/ref/horizon.rst | 45 +++ doc/source/ref/middleware.rst | 6 + doc/source/ref/run_tests.rst | 205 +++++++++++++ doc/source/ref/tables.rst | 82 +++++ doc/source/ref/tabs.rst | 45 +++ doc/source/ref/test.rst | 17 + doc/source/ref/users.rst | 6 + doc/source/ref/views.rst | 12 + doc/source/ref/workflows.rst | 33 ++ doc/source/releases/2012_1.rst | 148 +++++++++ doc/source/testing.rst | 39 +++ doc/source/topics/customizing.rst | 94 ++++++ doc/source/topics/deployment.rst | 147 +++++++++ doc/source/topics/tables.rst | 129 ++++++++ doc/source/topics/testing.rst | 276 +++++++++++++++++ doc/source/topics/tutorial.rst | 545 +++++++++++++++++++++++++++++++++ docs/Makefile | 153 --------- docs/source/_static/.gitignore | 0 docs/source/_static/basic.css | 416 ------------------------- docs/source/_static/default.css | 230 -------------- docs/source/_static/header-line.gif | Bin 48 -> 0 bytes docs/source/_static/header_bg.jpg | Bin 3738 -> 0 bytes docs/source/_static/jquery.tweet.js | 154 ---------- docs/source/_static/openstack_logo.png | Bin 3670 -> 0 bytes docs/source/_static/tweaks.css | 93 ------ docs/source/_templates/.placeholder | 0 docs/source/_theme/layout.html | 83 ----- docs/source/_theme/nature.css_t | 245 --------------- docs/source/_theme/theme.conf | 4 - docs/source/conf.py | 417 ------------------------- docs/source/contributing.rst | 204 ------------ docs/source/faq.rst | 37 --- docs/source/glossary.rst | 24 -- docs/source/index.rst | 128 -------- docs/source/intro.rst | 124 -------- docs/source/quickstart.rst | 194 ------------ docs/source/ref/context_processors.rst | 6 - docs/source/ref/decorators.rst | 6 - docs/source/ref/exceptions.rst | 6 - docs/source/ref/forms.rst | 17 - docs/source/ref/horizon.rst | 45 --- docs/source/ref/middleware.rst | 6 - docs/source/ref/run_tests.rst | 205 ------------- docs/source/ref/tables.rst | 82 ----- docs/source/ref/tabs.rst | 45 --- docs/source/ref/test.rst | 17 - docs/source/ref/users.rst | 6 - docs/source/ref/views.rst | 12 - docs/source/ref/workflows.rst | 33 -- docs/source/releases/2012_1.rst | 148 --------- docs/source/testing.rst | 39 --- docs/source/topics/customizing.rst | 94 ------ docs/source/topics/deployment.rst | 147 --------- docs/source/topics/tables.rst | 129 -------- docs/source/topics/testing.rst | 276 ----------------- docs/source/topics/tutorial.rst | 545 --------------------------------- run_tests.sh | 2 +- setup.cfg | 5 + 85 files changed, 4382 insertions(+), 4377 deletions(-) create mode 100644 doc/Makefile create mode 100644 doc/source/_static/.gitignore create mode 100644 doc/source/_static/basic.css create mode 100644 doc/source/_static/default.css create mode 100644 doc/source/_static/header-line.gif create mode 100644 doc/source/_static/header_bg.jpg create mode 100644 doc/source/_static/jquery.tweet.js create mode 100644 doc/source/_static/openstack_logo.png create mode 100644 doc/source/_static/tweaks.css create mode 100644 doc/source/_templates/.placeholder create mode 100644 doc/source/_theme/layout.html create mode 100644 doc/source/_theme/nature.css_t create mode 100644 doc/source/_theme/theme.conf create mode 100644 doc/source/conf.py create mode 100644 doc/source/contributing.rst create mode 100644 doc/source/faq.rst create mode 100644 doc/source/glossary.rst create mode 100644 doc/source/index.rst create mode 100644 doc/source/intro.rst create mode 100644 doc/source/quickstart.rst create mode 100644 doc/source/ref/context_processors.rst create mode 100644 doc/source/ref/decorators.rst create mode 100644 doc/source/ref/exceptions.rst create mode 100644 doc/source/ref/forms.rst create mode 100644 doc/source/ref/horizon.rst create mode 100644 doc/source/ref/middleware.rst create mode 100644 doc/source/ref/run_tests.rst create mode 100644 doc/source/ref/tables.rst create mode 100644 doc/source/ref/tabs.rst create mode 100644 doc/source/ref/test.rst create mode 100644 doc/source/ref/users.rst create mode 100644 doc/source/ref/views.rst create mode 100644 doc/source/ref/workflows.rst create mode 100644 doc/source/releases/2012_1.rst create mode 100644 doc/source/testing.rst create mode 100644 doc/source/topics/customizing.rst create mode 100644 doc/source/topics/deployment.rst create mode 100644 doc/source/topics/tables.rst create mode 100644 doc/source/topics/testing.rst create mode 100644 doc/source/topics/tutorial.rst delete mode 100644 docs/Makefile delete mode 100644 docs/source/_static/.gitignore delete mode 100644 docs/source/_static/basic.css delete mode 100644 docs/source/_static/default.css delete mode 100644 docs/source/_static/header-line.gif delete mode 100644 docs/source/_static/header_bg.jpg delete mode 100644 docs/source/_static/jquery.tweet.js delete mode 100644 docs/source/_static/openstack_logo.png delete mode 100644 docs/source/_static/tweaks.css delete mode 100644 docs/source/_templates/.placeholder delete mode 100644 docs/source/_theme/layout.html delete mode 100644 docs/source/_theme/nature.css_t delete mode 100644 docs/source/_theme/theme.conf delete mode 100644 docs/source/conf.py delete mode 100644 docs/source/contributing.rst delete mode 100644 docs/source/faq.rst delete mode 100644 docs/source/glossary.rst delete mode 100644 docs/source/index.rst delete mode 100644 docs/source/intro.rst delete mode 100644 docs/source/quickstart.rst delete mode 100644 docs/source/ref/context_processors.rst delete mode 100644 docs/source/ref/decorators.rst delete mode 100644 docs/source/ref/exceptions.rst delete mode 100644 docs/source/ref/forms.rst delete mode 100644 docs/source/ref/horizon.rst delete mode 100644 docs/source/ref/middleware.rst delete mode 100644 docs/source/ref/run_tests.rst delete mode 100644 docs/source/ref/tables.rst delete mode 100644 docs/source/ref/tabs.rst delete mode 100644 docs/source/ref/test.rst delete mode 100644 docs/source/ref/users.rst delete mode 100644 docs/source/ref/views.rst delete mode 100644 docs/source/ref/workflows.rst delete mode 100644 docs/source/releases/2012_1.rst delete mode 100644 docs/source/testing.rst delete mode 100644 docs/source/topics/customizing.rst delete mode 100644 docs/source/topics/deployment.rst delete mode 100644 docs/source/topics/tables.rst delete mode 100644 docs/source/topics/testing.rst delete mode 100644 docs/source/topics/tutorial.rst diff --git a/.gitignore b/.gitignore index 40caa430..3fedf1d0 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,8 @@ pylint.txt reports horizon.egg-info openstack_dashboard/local/local_settings.py -docs/build/ -docs/source/sourcecode +doc/build/ +doc/source/sourcecode /static/ .venv .tox diff --git a/MANIFEST.in b/MANIFEST.in index 30b41ca2..56996a06 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ -recursive-include docs *.py *.rst *.css *.js *.html *.conf *.jpg *.gif *.png *.css_t +recursive-include doc *.py *.rst *.css *.js *.html *.conf *.jpg *.gif *.png *.css_t recursive-include horizon *.html *.css *.js *.csv *.template *.tmpl *.mo *.po recursive-include openstack_dashboard *.html *.js *.css *.less *.csv *.template *.mo *.po *.example *.eot *.svg *.ttf *.woff *.png *.gif *.ico *.wsgi recursive-include tools *.py *.sh @@ -10,8 +10,8 @@ include Makefile include manage.py include README.rst include run_tests.sh -include docs/Makefile -include docs/source/_templates/.placeholder +include doc/Makefile +include doc/source/_templates/.placeholder include tools/pip-requires include tools/test-requires diff --git a/README.rst b/README.rst index ed1c4dda..edda2b77 100644 --- a/README.rst +++ b/README.rst @@ -106,7 +106,7 @@ Building Contributor Documentation This documentation is written by contributors, for contributors. -The source is maintained in the ``docs/source`` folder using +The source is maintained in the ``doc/source`` folder using `reStructuredText`_ and built by `Sphinx`_ .. _reStructuredText: http://docutils.sourceforge.net/rst.html diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 00000000..986ad3df --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,153 @@ +# 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 ' where 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." diff --git a/doc/source/_static/.gitignore b/doc/source/_static/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/_static/basic.css b/doc/source/_static/basic.css new file mode 100644 index 00000000..d909ce37 --- /dev/null +++ b/doc/source/_static/basic.css @@ -0,0 +1,416 @@ +/** + * Sphinx stylesheet -- basic theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 0; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +/* -- other body styles ----------------------------------------------------- */ + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlight { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} diff --git a/doc/source/_static/default.css b/doc/source/_static/default.css new file mode 100644 index 00000000..c8091ecb --- /dev/null +++ b/doc/source/_static/default.css @@ -0,0 +1,230 @@ +/** + * Sphinx stylesheet -- default theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: sans-serif; + font-size: 100%; + background-color: #11303d; + color: #000; + margin: 0; + padding: 0; +} + +div.document { + background-color: #1c4e63; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { +} + +div.sphinxsidebar h3 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.4em; + font-weight: normal; + margin: 0; + padding: 0; +} + +div.sphinxsidebar h3 a { + color: #ffffff; +} + +div.sphinxsidebar h4 { + font-family: 'Trebuchet MS', sans-serif; + color: #ffffff; + font-size: 1.3em; + font-weight: normal; + margin: 5px 0 0 0; + padding: 0; +} + +div.sphinxsidebar p { + color: #ffffff; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; + color: #ffffff; +} + +div.sphinxsidebar a { + color: #98dbcc; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #355f7c; + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +div.body p, div.body dd, div.body li { + text-align: left; + line-height: 130%; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: left; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} diff --git a/doc/source/_static/header-line.gif b/doc/source/_static/header-line.gif new file mode 100644 index 00000000..3601730e Binary files /dev/null and b/doc/source/_static/header-line.gif differ diff --git a/doc/source/_static/header_bg.jpg b/doc/source/_static/header_bg.jpg new file mode 100644 index 00000000..f788c41c Binary files /dev/null and b/doc/source/_static/header_bg.jpg differ diff --git a/doc/source/_static/jquery.tweet.js b/doc/source/_static/jquery.tweet.js new file mode 100644 index 00000000..79bf0bdb --- /dev/null +++ b/doc/source/_static/jquery.tweet.js @@ -0,0 +1,154 @@ +(function($) { + + $.fn.tweet = function(o){ + var s = { + username: ["seaofclouds"], // [string] required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"] + list: null, //[string] optional name of list belonging to username + avatar_size: null, // [integer] height and width of avatar if displayed (48px max) + count: 3, // [integer] how many tweets to display? + intro_text: null, // [string] do you want text BEFORE your your tweets? + outro_text: null, // [string] do you want text AFTER your tweets? + join_text: null, // [string] optional text in between date and tweet, try setting to "auto" + auto_join_text_default: "i said,", // [string] auto text for non verb: "i said" bullocks + auto_join_text_ed: "i", // [string] auto text for past tense: "i" surfed + auto_join_text_ing: "i am", // [string] auto tense for present tense: "i was" surfing + auto_join_text_reply: "i replied to", // [string] auto tense for replies: "i replied to" @someone "with" + auto_join_text_url: "i was looking at", // [string] auto tense for urls: "i was looking at" http:... + loading_text: null, // [string] optional loading text, displayed while tweets load + query: null // [string] optional search query + }; + + if(o) $.extend(s, o); + + $.fn.extend({ + linkUrl: function() { + var returning = []; + var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi; + this.each(function() { + returning.push(this.replace(regexp,"$1")); + }); + return $(returning); + }, + linkUser: function() { + var returning = []; + var regexp = /[\@]+([A-Za-z0-9-_]+)/gi; + this.each(function() { + returning.push(this.replace(regexp,"@$1")); + }); + return $(returning); + }, + linkHash: function() { + var returning = []; + var regexp = / [\#]+([A-Za-z0-9-_]+)/gi; + this.each(function() { + returning.push(this.replace(regexp, ' #$1')); + }); + return $(returning); + }, + capAwesome: function() { + var returning = []; + this.each(function() { + returning.push(this.replace(/\b(awesome)\b/gi, '$1')); + }); + return $(returning); + }, + capEpic: function() { + var returning = []; + this.each(function() { + returning.push(this.replace(/\b(epic)\b/gi, '$1')); + }); + return $(returning); + }, + makeHeart: function() { + var returning = []; + this.each(function() { + returning.push(this.replace(/(<)+[3]/gi, "")); + }); + return $(returning); + } + }); + + function relative_time(time_value) { + var parsed_date = Date.parse(time_value); + var relative_to = (arguments.length > 1) ? arguments[1] : new Date(); + var delta = parseInt((relative_to.getTime() - parsed_date) / 1000); + var pluralize = function (singular, n) { + return '' + n + ' ' + singular + (n == 1 ? '' : 's'); + }; + if(delta < 60) { + return 'less than a minute ago'; + } else if(delta < (45*60)) { + return 'about ' + pluralize("minute", parseInt(delta / 60)) + ' ago'; + } else if(delta < (24*60*60)) { + return 'about ' + pluralize("hour", parseInt(delta / 3600)) + ' ago'; + } else { + return 'about ' + pluralize("day", parseInt(delta / 86400)) + ' ago'; + } + } + + function build_url() { + var proto = ('https:' == document.location.protocol ? 'https:' : 'http:'); + if (s.list) { + return proto+"//api.twitter.com/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?"; + } else if (s.query == null && s.username.length == 1) { + return proto+'//twitter.com/status/user_timeline/'+s.username[0]+'.json?count='+s.count+'&callback=?'; + } else { + var query = (s.query || 'from:'+s.username.join('%20OR%20from:')); + return proto+'//search.twitter.com/search.json?&q='+query+'&rpp='+s.count+'&callback=?'; + } + } + + return this.each(function(){ + var list = $('
    ').appendTo(this); + var intro = '

    '+s.intro_text+'

    '; + var outro = '

    '+s.outro_text+'

    '; + var loading = $('

    '+s.loading_text+'

    '); + + if(typeof(s.username) == "string"){ + s.username = [s.username]; + } + + if (s.loading_text) $(this).append(loading); + $.getJSON(build_url(), function(data){ + if (s.loading_text) loading.remove(); + if (s.intro_text) list.before(intro); + $.each((data.results || data), function(i,item){ + // auto join text based on verb tense and content + if (s.join_text == "auto") { + if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) { + var join_text = s.auto_join_text_reply; + } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) { + var join_text = s.auto_join_text_url; + } else if (item.text.match(/^((\w+ed)|just) .*/im)) { + var join_text = s.auto_join_text_ed; + } else if (item.text.match(/^(\w*ing) .*/i)) { + var join_text = s.auto_join_text_ing; + } else { + var join_text = s.auto_join_text_default; + } + } else { + var join_text = s.join_text; + }; + + var from_user = item.from_user || item.user.screen_name; + var profile_image_url = item.profile_image_url || item.user.profile_image_url; + var join_template = ' '+join_text+' '; + var join = ((s.join_text) ? join_template : ' '); + var avatar_template = ''+from_user+'\'s avatar'; + var avatar = (s.avatar_size ? avatar_template : ''); + var date = ''+relative_time(item.created_at)+''; + var text = '' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ ''; + + // until we create a template option, arrange the items below to alter a tweet's display. + list.append('
  • ' + avatar + date + join + text + '
  • '); + + list.children('li:first').addClass('tweet_first'); + list.children('li:odd').addClass('tweet_even'); + list.children('li:even').addClass('tweet_odd'); + }); + if (s.outro_text) list.after(outro); + }); + + }); + }; +})(jQuery); \ No newline at end of file diff --git a/doc/source/_static/openstack_logo.png b/doc/source/_static/openstack_logo.png new file mode 100644 index 00000000..146faec5 Binary files /dev/null and b/doc/source/_static/openstack_logo.png differ diff --git a/doc/source/_static/tweaks.css b/doc/source/_static/tweaks.css new file mode 100644 index 00000000..8ed4c9db --- /dev/null +++ b/doc/source/_static/tweaks.css @@ -0,0 +1,93 @@ +body { + background: #fff url(../_static/header_bg.jpg) top left no-repeat; +} + +#header { + width: 950px; + margin: 0 auto; + height: 102px; +} + +#header h1#logo { + background: url(../_static/openstack_logo.png) top left no-repeat; + display: block; + float: left; + text-indent: -9999px; + width: 175px; + height: 55px; +} + +#navigation { + background: url(../_static/header-line.gif) repeat-x 0 bottom; + display: block; + float: left; + margin: 27px 0 0 25px; + padding: 0; +} + +#navigation li{ + float: left; + display: block; + margin-right: 25px; +} + +#navigation li a { + display: block; + font-weight: normal; + text-decoration: none; + background-position: 50% 0; + padding: 20px 0 5px; + color: #353535; + font-size: 14px; +} + +#navigation li a.current, #navigation li a.section { + border-bottom: 3px solid #cf2f19; + color: #cf2f19; +} + +div.related { + background-color: #cde2f8; + border: 1px solid #b0d3f8; +} + +div.related a { + color: #4078ba; + text-shadow: none; +} + +div.sphinxsidebarwrapper { + padding-top: 0; +} + +pre { + color: #555; +} + +div.documentwrapper h1, div.documentwrapper h2, div.documentwrapper h3, div.documentwrapper h4, div.documentwrapper h5, div.documentwrapper h6 { + font-family: 'PT Sans', sans-serif !important; + color: #264D69; + border-bottom: 1px dotted #C5E2EA; + padding: 0; + background: none; + padding-bottom: 5px; +} + +div.documentwrapper h3 { + color: #CF2F19; +} + +a.headerlink { + color: #fff !important; + margin-left: 5px; + background: #CF2F19 !important; +} + +div.body { + margin-top: -25px; +} + +div.document { + width: 960px; + margin: 0 auto; +} \ No newline at end of file diff --git a/doc/source/_templates/.placeholder b/doc/source/_templates/.placeholder new file mode 100644 index 00000000..e69de29b diff --git a/doc/source/_theme/layout.html b/doc/source/_theme/layout.html new file mode 100644 index 00000000..750b7822 --- /dev/null +++ b/doc/source/_theme/layout.html @@ -0,0 +1,83 @@ +{% extends "basic/layout.html" %} +{% set css_files = css_files + ['_static/tweaks.css'] %} +{% set script_files = script_files + ['_static/jquery.tweet.js'] %} + +{%- macro sidebar() %} + {%- if not embedded %}{% if not theme_nosidebar|tobool %} +
    +
    + {%- block sidebarlogo %} + {%- if logo %} + + {%- endif %} + {%- endblock %} + {%- block sidebartoc %} + {%- if display_toc %} +

    {{ _('Table Of Contents') }}

    + {{ toc }} + {%- endif %} + {%- endblock %} + {%- block sidebarrel %} + {%- if prev %} +

    {{ _('Previous topic') }}

    +

    {{ prev.title }}

    + {%- endif %} + {%- if next %} +

    {{ _('Next topic') }}

    +

    {{ next.title }}

    + {%- endif %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- if show_source and has_source and sourcename %} +

    {{ _('This Page') }}

    + + {%- endif %} + {%- endblock %} + {%- if customsidebar %} + {% include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- if pagename != "search" %} + + + {%- endif %} + {%- endblock %} +
    +
    + {%- endif %}{% endif %} +{%- endmacro %} + +{% block relbar1 %}{% endblock relbar1 %} + +{% block header %} + +{% endblock %} \ No newline at end of file diff --git a/doc/source/_theme/nature.css_t b/doc/source/_theme/nature.css_t new file mode 100644 index 00000000..a98bd420 --- /dev/null +++ b/doc/source/_theme/nature.css_t @@ -0,0 +1,245 @@ +/* + * nature.css_t + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- nature theme. + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Arial, sans-serif; + font-size: 100%; + background-color: #111; + color: #555; + margin: 0; + padding: 0; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 {{ theme_sidebarwidth|toint }}px; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.document { + background-color: #eee; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 0 30px 30px 30px; + font-size: 0.9em; +} + +div.footer { + color: #555; + width: 100%; + padding: 13px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #444; + text-decoration: underline; +} + +div.related { + background-color: #6BA81E; + line-height: 32px; + color: #fff; + text-shadow: 0px 1px 0 #444; + font-size: 0.9em; +} + +div.related a { + color: #E2F3CC; +} + +div.sphinxsidebar { + font-size: 0.75em; + line-height: 1.5em; +} + +div.sphinxsidebarwrapper{ + padding: 20px 0; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Arial, sans-serif; + color: #222; + font-size: 1.2em; + font-weight: normal; + margin: 0; + padding: 5px 10px; + background-color: #ddd; + text-shadow: 1px 1px 0 white +} + +div.sphinxsidebar h4{ + font-size: 1.1em; +} + +div.sphinxsidebar h3 a { + color: #444; +} + + +div.sphinxsidebar p { + color: #888; + padding: 5px 20px; +} + +div.sphinxsidebar p.topless { +} + +div.sphinxsidebar ul { + margin: 10px 20px; + padding: 0; + color: #000; +} + +div.sphinxsidebar a { + color: #444; +} + +div.sphinxsidebar input { + border: 1px solid #ccc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar input[type=text]{ + margin-left: 20px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #005B81; + text-decoration: none; +} + +a:hover { + color: #E32E00; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Arial, sans-serif; + background-color: #BED4EB; + font-weight: normal; + color: #212224; + margin: 30px 0px 10px 0px; + padding: 5px 0 5px 10px; + text-shadow: 0px 1px 0 white +} + +div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 150%; background-color: #C8D5E3; } +div.body h3 { font-size: 120%; background-color: #D8DEE3; } +div.body h4 { font-size: 110%; background-color: #D8DEE3; } +div.body h5 { font-size: 100%; background-color: #D8DEE3; } +div.body h6 { font-size: 100%; background-color: #D8DEE3; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + line-height: 1.5em; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.highlight{ + background-color: white; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 10px; + background-color: White; + color: #222; + line-height: 1.2em; + border: 1px solid #C6C9CB; + font-size: 1.1em; + margin: 1.5em 0 1.5em 0; + -webkit-box-shadow: 1px 1px 1px #d8d8d8; + -moz-box-shadow: 1px 1px 1px #d8d8d8; +} + +tt { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ + font-size: 1.1em; + font-family: monospace; +} + +.viewcode-back { + font-family: Arial, sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/doc/source/_theme/theme.conf b/doc/source/_theme/theme.conf new file mode 100644 index 00000000..1cc40044 --- /dev/null +++ b/doc/source/_theme/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = nature.css +pygments_style = tango diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100644 index 00000000..1fa2d706 --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,417 @@ +# -*- coding: utf-8 -*- +# +# 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. + +import sys +import os + +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['DJANGO_SETTINGS_MODULE'] = 'openstack_dashboard.settings' + +import horizon.version + + +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.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.tests', 'openstack_dashboard.tests',) + 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("=================\n") + INDEXOUT.write("Source Code Index\n") + INDEXOUT.write("=================\n") + + for modulename, path in SRCS.items(): + 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.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.pngmath', + 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +if os.getenv('HUDSON_PUBLISH_DOCS'): + templates_path = ['_ga', '_templates'] +else: + 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, LLC' + +# 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.canonical_version_string() +# The full version, including alpha/beta/rc tags. +release = horizon.version.canonical_version_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 +# " v 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 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, LLC', '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 a 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 + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'python': ('http://docs.python.org/', None), + 'django': ('http://docs.djangoproject.com/en/dev/_objects/'), + 'nova': ('http://nova.openstack.org', None), + 'swift': ('http://swift.openstack.org', None), + 'keystone': ('http://keystone.openstack.org', None), + 'glance': ('http://glance.openstack.org', None)} diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst new file mode 100644 index 00000000..e884d854 --- /dev/null +++ b/doc/source/contributing.rst @@ -0,0 +1,204 @@ +================== +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 +* Jenkins build status: https://jenkins.openstack.org/view/Horizon/ +* 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. +#. Request to join the `OpenStack Contributors`_ team on Launchpad. +#. Join the `Horizon Developers`_ team on Launchpad. +#. Follow the `instructions for setting up git-review`_ in your + development environment. + +Whew! Got that 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! + +.. _`bug tracker`: https://bugs.launchpad.net/horizon + +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 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://wiki.openstack.org/GerritWorkflow + +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. + +Code Style +========== + +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 +---------- + +As a project, Horizon adheres to code quality standards for our JavaScript +just as we do for our Python. To that end we recommend (but do not strictly +enforce) the use of JSLint_ to validate some general best practices. + +The default options are mostly good, but the following accommodate some +allowances we make: + +* Set ``Indentation`` to ``2``. +* Enable the ``Assume console, alert, ...`` option. +* Enable the ``Assume a browser`` option. +* Enable the ``Tolerate missing 'use strict' pragma`` option. +* Clear the ``Maximum number of errors`` field. +* Add ``horizon,$`` to the ``Predefined`` list. + +.. _JSLint: http://jslint.com/ + +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. + +HTML +---- + +Again, readability is paramount; however be conscientous 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. diff --git a/doc/source/faq.rst b/doc/source/faq.rst new file mode 100644 index 00000000..26836ef6 --- /dev/null +++ b/doc/source/faq.rst @@ -0,0 +1,37 @@ +========================== +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. + diff --git a/doc/source/glossary.rst b/doc/source/glossary.rst new file mode 100644 index 00000000..36bf635e --- /dev/null +++ b/doc/source/glossary.rst @@ -0,0 +1,24 @@ +======== +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. "syspanel") + 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. diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 00000000..111a1d31 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,128 @@ +.. + Copyright 2012 OpenStack, LLC + 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 +`_, 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 `. + +To learn what you need to know to get going, see the :doc:`quickstart`. + +Getting Started With Horizon +============================ + +How to use Horizon in your own projects. + +.. toctree:: + :maxdepth: 1 + + intro + quickstart + topics/tutorial + topics/deployment + 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 + + 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/tables + topics/testing + +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/users + ref/forms + ref/views + ref/middleware + ref/context_processors + ref/decorators + ref/exceptions + ref/test + +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` diff --git a/doc/source/intro.rst b/doc/source/intro.rst new file mode 100644 index 00000000..a546ce99 --- /dev/null +++ b/doc/source/intro.rst @@ -0,0 +1,124 @@ +=================== +Introducing Horizon +=================== + +.. contents:: Contents: + :local: + +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 ` + A short guide to getting started with using Horizon. + + :doc:`Frequently Asked Questions ` + Common questions and answers. + + :doc:`Glossary ` + Common terms and their definitions. diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst new file mode 100644 index 00000000..c8c8644a --- /dev/null +++ b/doc/source/quickstart.rst @@ -0,0 +1,194 @@ +================== +Horizon Quickstart +================== + +Setup +===== + +To setup an Horizon development environment simply clone the Horizon git +repository at http://github.com/openstack/horizon and execute the +``run_tests.sh`` script from the root folder (see :doc:`ref/run_tests`). + +Horizon assumes a single end-point for OpenStack services which defaults to +the local host (127.0.0.1). If this is not the case change the +``OPENSTACK_HOST`` setting in the ``local_settings.py`` file, located in the +``openstack_dashboard/local`` folder, to the actual IP address of the +OpenStack end-point Horizon should use. + +To start the Horizon development server use the Django ``manage.py`` utility +with the context of the virtual environment:: + + > tools/with_venv.sh ./manage.py runserver + +Alternately specify the listen IP and port:: + + > tools/with_venv.sh ./manage.py runserver 0.0.0.0:8080 + +Once the Horizon server is running point a web browser to http://localhost:8000 +or to the IP and port the server is listening. + +.. note:: + + The ``DevStack`` project (http://devstack.org/) can be used to install + an OpenStack development environment from scratch. + +.. note:: + + The minimum required set of OpenStack services running includes the + following: + + * Nova (compute, api, scheduler, network, *and* volume services) + * Glance + * Keystone + + Optional support is provided for Swift. + +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 ``tools/pip-requires``. + + .. 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 `. + +Project +======= + +INSTALLED_APPS +-------------- + +At the project level you add Horizon and any desired dashboards to your +``settings.INSTALLED_APPS``:: + + INSTALLED_APPS = ( + 'django', + ... + 'horizon', + 'horizon.dash', + 'horizon.syspanel', + ) + +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 %} + + + +And in your ``sidebar.html`` you might have:: + + {% load horizon %} + + + +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 syspanel as +an example):: + + syspanel/ + |---__init__.py + |---dashboard.py <-----Registers the app with Horizon and sets dashboard properties + |---templates/ + |---templatetags/ + |---overview/ + |---services/ + |---images/ + |---__init__.py + |---panel.py <-----Registers the panel in the app and defines panel properties + |---urls.py + |---views.py + |---forms.py + |---tests.py + |---api.py <-------Optional additional API methods for non-core services + |---templates/ + ... + ... + +Dashboard Classes +----------------- + +Inside of ``dashboard.py`` you would have a class definition and the registration +process:: + + import horizon + + + class Syspanel(horizon.Dashboard): + name = "Syspanel" # Appears in navigation + slug = 'syspanel' # Appears in url + panels = ('overview', 'services', 'instances', 'flavors', 'images', + 'tenants', 'users', 'quotas',) + default_panel = 'overview' + roles = ('admin',) # Provides RBAC at the dashboard-level + ... + + + horizon.register(Syspanel) + +Panel Classes +------------- + +To connect a :class:`~horizon.Panel` with a :class:`~horizon.Dashboard` class +you register it in a ``panels.py`` file like so:: + + import horizon + + from horizon.dashboard.syspanel import dashboard + + + class Images(horizon.Panel): + name = "Images" + slug = 'images' + roles = ('admin', 'my_other_role',) # Fine-grained RBAC per-panel + + + # You could also register your panel with another application's dashboard + dashboard.Syspanel.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. diff --git a/doc/source/ref/context_processors.rst b/doc/source/ref/context_processors.rst new file mode 100644 index 00000000..b34c0109 --- /dev/null +++ b/doc/source/ref/context_processors.rst @@ -0,0 +1,6 @@ +========================== +Horizon Context Processors +========================== + +.. automodule:: horizon.context_processors + :members: diff --git a/doc/source/ref/decorators.rst b/doc/source/ref/decorators.rst new file mode 100644 index 00000000..777afbe5 --- /dev/null +++ b/doc/source/ref/decorators.rst @@ -0,0 +1,6 @@ +================== +Horizon Decorators +================== + +.. automodule:: horizon.decorators + :members: diff --git a/doc/source/ref/exceptions.rst b/doc/source/ref/exceptions.rst new file mode 100644 index 00000000..4151f18f --- /dev/null +++ b/doc/source/ref/exceptions.rst @@ -0,0 +1,6 @@ +================== +Horizon Exceptions +================== + +.. automodule:: horizon.exceptions + :members: diff --git a/doc/source/ref/forms.rst b/doc/source/ref/forms.rst new file mode 100644 index 00000000..9b30cb8e --- /dev/null +++ b/doc/source/ref/forms.rst @@ -0,0 +1,17 @@ +============= +Horizon Forms +============= + +Horizon ships with a number of form classes, some generic and some specific. + +Generic Forms +============= + +.. automodule:: horizon.forms + :members: + +Auth Forms +========== + +.. automodule:: horizon.views.auth_forms + :members: diff --git a/doc/source/ref/horizon.rst b/doc/source/ref/horizon.rst new file mode 100644 index 00000000..cc4b2d70 --- /dev/null +++ b/doc/source/ref/horizon.rst @@ -0,0 +1,45 @@ +====================== +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: diff --git a/doc/source/ref/middleware.rst b/doc/source/ref/middleware.rst new file mode 100644 index 00000000..fcca5ff0 --- /dev/null +++ b/doc/source/ref/middleware.rst @@ -0,0 +1,6 @@ +================== +Horizon Middleware +================== + +.. automodule:: horizon.middleware + :members: diff --git a/doc/source/ref/run_tests.rst b/doc/source/ref/run_tests.rst new file mode 100644 index 00000000..b74aa1c5 --- /dev/null +++ b/doc/source/ref/run_tests.rst @@ -0,0 +1,205 @@ +=========================== +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 + ``openstack-dashboard/tools/install_venv.py``. + #. Run the tests for both ``horizon`` and ``openstack-dashboard`` using + their respective environments and verify that evreything 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 quicker test run you can skip the Selenium +tests by using the ``--skip-selenium`` flag:: + + ./run_tests.sh --skip-selenium + +This isn't recommended, but can be a timesaver when you only need to run +the code tests and not the frontend tests during development. + +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 + +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 --dashboard= + +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.). +* --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``. + +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:: + + ./openstack-dashboard/tools/with_venv.sh ./openstack-dashboard/dashboard/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 incrementing an +``environment_version`` integer at the top of ``run_tests.sh``. + +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 as well. diff --git a/doc/source/ref/tables.rst b/doc/source/ref/tables.rst new file mode 100644 index 00000000..4b74d5e3 --- /dev/null +++ b/doc/source/ref/tables.rst @@ -0,0 +1,82 @@ +================== +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! + +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: + +Table Components +================ + +.. autoclass:: Column + :members: + +.. autoclass:: Row + :members: + +Actions +======= + +.. autoclass:: Action + :members: + +.. autoclass:: LinkAction + :members: + +.. autoclass:: FilterAction + :members: + +.. autoclass:: BatchAction + :members: + +.. autoclass:: DeleteAction + :members: + +Class-Based Views +================= + +Several class-based views are provided to make working with DataTables +easier in your UI. + +.. autoclass:: DataTableView + +.. autoclass:: MultiTableView diff --git a/doc/source/ref/tabs.rst b/doc/source/ref/tabs.rst new file mode 100644 index 00000000..807385ca --- /dev/null +++ b/doc/source/ref/tabs.rst @@ -0,0 +1,45 @@ +========================== +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: diff --git a/doc/source/ref/test.rst b/doc/source/ref/test.rst new file mode 100644 index 00000000..ccf3f046 --- /dev/null +++ b/doc/source/ref/test.rst @@ -0,0 +1,17 @@ +======================== +Horizon TestCase Classes +======================== + +.. module:: horizon.test + +Horizon provides several useful base classes for testing both views and +API functions. + +.. autoclass:: TestCase + :members: + +.. autoclass:: APITestCase + :members: + +.. autoclass:: BaseAdminViewTests + :members: diff --git a/doc/source/ref/users.rst b/doc/source/ref/users.rst new file mode 100644 index 00000000..857358d1 --- /dev/null +++ b/doc/source/ref/users.rst @@ -0,0 +1,6 @@ +================= +Horizon User APIs +================= + +.. automodule:: horizon.users + :members: diff --git a/doc/source/ref/views.rst b/doc/source/ref/views.rst new file mode 100644 index 00000000..970609ca --- /dev/null +++ b/doc/source/ref/views.rst @@ -0,0 +1,12 @@ +============= +Horizon Views +============= + +Horizon ships with a number of pre-built views which are used within +Horizon and can also be reused in your applications. + +Auth +==== + +.. automodule:: horizon.views.auth + :members: diff --git a/doc/source/ref/workflows.rst b/doc/source/ref/workflows.rst new file mode 100644 index 00000000..c4077667 --- /dev/null +++ b/doc/source/ref/workflows.rst @@ -0,0 +1,33 @@ +================= +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. + +Workflows +========= + +.. autoclass:: Workflow + :members: + +Steps +===== + +.. autoclass:: Step + :members: + +Actions +======= + +.. autoclass:: Action + :members: + +WorkflowView +============ + +.. autoclass:: WorkflowView + :members: diff --git a/doc/source/releases/2012_1.rst b/doc/source/releases/2012_1.rst new file mode 100644 index 00000000..e024bf41 --- /dev/null +++ b/doc/source/releases/2012_1.rst @@ -0,0 +1,148 @@ +====================== +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 extensability, 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 +encompasse 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 elegently 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 userful 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 compatibliity. diff --git a/doc/source/testing.rst b/doc/source/testing.rst new file mode 100644 index 00000000..b3b15038 --- /dev/null +++ b/doc/source/testing.rst @@ -0,0 +1,39 @@ +======================= +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 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 + +.. seealso:: + + :doc:`ref/run_tests` + Full reference for the ``run_tests.sh`` script. + +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 `. diff --git a/doc/source/topics/customizing.rst b/doc/source/topics/customizing.rst new file mode 100644 index 00000000..040319a2 --- /dev/null +++ b/doc/source/topics/customizing.rst @@ -0,0 +1,94 @@ +=================== +Customizing Horizon +=================== + +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 +``horizon/openstack-dashboard/local/local_settings.py``. + +Changing the Logo +================= + +The OpenStack Logo is pulled in through ``style.css``:: + + #splash .modal { + background: #fff url(../images/logo.png) no-repeat center 35px; + + h1.brand a { + background: url(../images/logo.png) top left no-repeat; + +To override the OpenStack Logo image, replace the image at the directory path +``horizon/openstack-dashboard/dashboard/static/dashboard/images/logo.png``. + +The dimensions should be ``width: 108px, height: 121px``. + +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 ``settings.HORIZON_CONFIG`` dictionary. +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" + } + + +Button Icons +============ + +Horizon provides hooks for customizing the look and feel of each class of +button on the site. The following classes are used to identify each type of +button: + +* Generic Classes + * btn-search + * btn-delete + * btn-upload + * btn-download + * btn-create + * btn-edit + * btn-list + * btn-copy + * btn-camera + * btn-stats + * btn-enable + * btn-disable + +* Floating IP-specific Classes + * btn-allocate + * btn-release + * btn-associate + * btn-disassociate + +* Instance-specific Classes + * btn-launch + * btn-terminate + * btn-reboot + * btn-pause + * btn-suspend + * btn-console + * btn-log + +* Volume-specific classes + * btn-detach + +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. diff --git a/doc/source/topics/deployment.rst b/doc/source/topics/deployment.rst new file mode 100644 index 00000000..16f4e105 --- /dev/null +++ b/doc/source/topics/deployment.rst @@ -0,0 +1,147 @@ +================= +Deploying Horizon +================= + +This guide aims to cover some common questions, concerns and pitfalls you +may encounter when deploying Horizon in a production environment. + +Logging +======= + +Logging is an important concern for prouction 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. + +.. 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 + +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 depooyment 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': { + # Databe 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 diff --git a/doc/source/topics/tables.rst b/doc/source/topics/tables.rst new file mode 100644 index 00000000..b59dffb2 --- /dev/null +++ b/doc/source/topics/tables.rst @@ -0,0 +1,129 @@ +====================== +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 `. + +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 it's 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. + +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. diff --git a/doc/source/topics/testing.rst b/doc/source/topics/testing.rst new file mode 100644 index 00000000..5c36ac82 --- /dev/null +++ b/doc/source/topics/testing.rst @@ -0,0 +1,276 @@ +=================== +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 +stright 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 a 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. +* Don'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 buidling (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:`~horizon.test.TestCase.assertNoFormErrors`, +:meth:`~horizon.test.TestCase.assertMessageCount`, and +:meth:`~horizon.test.TestCase.asertNoMessages` 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:`~horizon.test.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.TestCase.assertMessageCount` and + :meth:`~horizon.test.TestCase.asertNoMessages` 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 +tol 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:`~horizon.test.TestCase.assertNoFormErrors` and +:meth:`~horizon.test.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. diff --git a/doc/source/topics/tutorial.rst b/doc/source/topics/tutorial.rst new file mode 100644 index 00000000..1a6f48da --- /dev/null +++ b/doc/source/topics/tutorial.rst @@ -0,0 +1,545 @@ +=================== +Building on Horizon +=================== + +This tutorial covers how to use the various components in Horizon to build +an example dashboard and panel with a data table and tabs. + +As an example, we'll build on the Nova instances API to create a new and novel +"visualizations" dashboard with a "flocking" panel that presents the instance +data in a different manner. + +You can find a reference implementation of the code being described here +on github at https://github.com/gabrielhurley/horizon_demo. + +.. note:: + + There are a variety of other resources which may be helpful to read first, + since this is a more advanced tutorial. For example, you may want to start + with the :doc:`Horizon quickstart guide ` or the + `Django tutorial`_. + + .. _Django tutorial: https://docs.djangoproject.com/en/1.4/intro/tutorial01/ + + +Creating a dashboard +==================== + +.. note:: + + It is perfectly valid to create a panel without a dashboard, and + incorporate it into an existing dashboard. See the section + :ref:`overrides ` later in this document. + +The quick version +----------------- + +Horizon provides a custom management command to create a typical base +dashboard structure for you. The following command generates most of the +boilerplate code explained below:: + + ./run_tests.sh -m startdash visualizations + +It's still recommended that you read the rest of this section to understand +what that command creates and why. + +Structure +--------- + +The recommended structure for a dashboard (or panel) follows suit with the +typical Django application layout. We'll name our dashboard "visualizations":: + + visualizations + |--__init__.py + |--dashboard.py + |--templates/ + |--static/ + +The ``dashboard.py`` module will contain our dashboard class for use by +Horizon; the ``templates`` and ``static`` directories give us homes for our +Django template files and static media respectively. + +Within the ``static`` and ``templates`` directories it's generally good to +namespace your files like so:: + + templates/ + |--visualizations/ + static/ + |--visualizations/ + |--css/ + |--js/ + |--img/ + +With those files and directories in place, we can move on to writing our +dashboard class. + + +Defining a dashboard +-------------------- + +A dashboard class can be incredibly simple (about 3 lines at minimum), +defining nothing more than a name and a slug:: + + import horizon + + class VizDash(horizon.Dashboard): + name = _("Visualizations") + slug = "visualizations" + +In practice, a dashboard class will usually contain more information, such +as a list of panels, which panel is the default, and any roles required to +access this dashboard:: + + class VizDash(horizon.Dashboard): + name = _("Visualizations") + slug = "visualizations" + panels = ('flocking',) + default_panel = 'flocking' + roles = ('admin',) + +Building from that previous example we may also want to define a grouping of +panels which share a common theme and have a sub-heading in the navigation:: + + class InstanceVisualizations(horizon.PanelGroup): + slug = "instance_visualizations" + name = _("Instance Visualizations") + panels = ('flocking',) + + + class VizDash(horizon.Dashboard): + name = _("Visualizations") + slug = "visualizations" + panels = (InstanceVisualizations,) + default_panel = 'flocking' + roles = ('admin',) + +The ``PanelGroup`` can be added to the dashboard class' ``panels`` list +just like the slug of the panel can. + +Once our dashboard class is complete, all we need to do is register it:: + + horizon.register(VizDash) + +The typical place for that would be the bottom of the ``dashboard.py`` file, +but it could also go elsewhere, such as in an override file (see below). + + +Creating a panel +================ + +Now that we have our dashboard written, we can also create our panel. We'll +call it "flocking". + +.. note:: + + You don't need to write a custom dashboard to add a panel. The structure + here is for the sake of completeness in the tutorial. + +The quick version +----------------- + +Horizon provides a custom management command to create a typical base +panel structure for you. The following command generates most of the +boilerplate code explained below:: + + ./run_tests.sh -m startpanel flocking --dashboard=visualizations --target=auto + +The ``dashboard`` argument is required, and tells the command which dashboard +this panel will be registered with. The ``target`` argument is optional, and +respects ``auto`` as a special value which means that the files for the panel +should be created inside the dashboard module as opposed to the current +directory (the default). + +It's still recommended that you read the rest of this section to understand +what that command creates and why. + +Structure +--------- + +A panel is a relatively flat structure with the exception that templates +for a panel in a dashboard live in the dashboard's ``templates`` directory +rather than in the panel's ``templates`` directory. Continuing our +vizulaization/flocking example, let's see what the looks like:: + + # stand-alone panel structure + flocking/ + |--__init__.py + |--panel.py + |--urls.py + |--views.py + |--templates/ + |--flocking/ + |--index.html + + # panel-in-a-dashboard structure + visualizations/ + |--__init__.py + |--dashboard.py + |--flocking/ + |--__init__.py + |--panel.py + |--urls.py + |--views.py + |--templates/ + |--visualizations/ + |--flocking/ + |--index.html + +That follows standard Django namespacing conventions for apps and submodules +within apps. It also works cleanly with Django's automatic template discovery +in both cases. + +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 ``panel.py`` file +in a corresponding directory (the details are a bit magical, but have been +thoroughly vetted in Django's admin codebase). + +Inside the ``panel.py`` module we define our ``Panel`` class:: + + class Flocking(horizon.Panel): + name = _("Flocking") + slug = 'flocking' + +Simple, right? Once we've defined it, we register it with the dashboard:: + + from visualizations import dashboard + + dashboard.VizDash.register(Flocking) + +Easy! There are more options you can set to customize the ``Panel`` class, but +it makes some intelligent guesses about what the defaults should be. + +URLs +---- + +One of the intelligent assumptions the ``Panel`` class makes is that it can +find a ``urls.py`` file in your panel directory which will define a view named +``index`` that handles the default view for that panel. This is what your +``urls.py`` file might look like:: + + from django.conf.urls.defaults import patterns, url + from .views import IndexView + + urlpatterns = patterns('', + url(r'^$', IndexView.as_view(), name='index') + ) + +There's nothing there that isn't 100% standard Django code. This example +(and Horizon in general) uses the class-based views introduced in Django 1.3 +to make code more reusable. Hence the view class is imported in the example +above, and the ``as_view()`` method is called in the URL pattern. + +This, of course, presumes you have a view class, and takes us into the meat +of writing a ``Panel``. + + +Tables, Tabs, and Views +----------------------- + +Now we get to the really exciting parts; everything before this was structural. + +Starting with the high-level view, our end goal is to create a view (our +``IndexView`` class referenced above) which uses Horizon's ``DataTable`` +class to display data and Horizon's ``TabGroup`` class to give us a +user-friendly tabbed interface in the browser. + +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.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. + +In this case, we're going to be presenting data about tables, so let's start +defining our table (and a ``tables.py`` module:: + + from horizon import tables + + class FlockingInstancesTable(tables.DataTable): + host = tables.Column("OS-EXT-SRV-ATTR:host", verbose_name=_("Host")) + tenant = tables.Column('tenant_name', verbose_name=_("Tenant")) + user = tables.Column('user_name', verbose_name=_("user")) + vcpus = tables.Column('flavor_vcpus', verbose_name=_("VCPUs")) + memory = tables.Column('flavor_memory', verbose_name=_("Memory")) + age = tables.Column('age', verbose_name=_("Age")) + + class Meta: + name = "instances" + verbose_name = _("Instances") + +There are several things going on here... we created a table subclass, +and defined six columns on it. 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 defines some properties about our +table, notably it's (translatable) verbose name, and a semi-unique "slug"-like +name to identify it. + +.. note:: + + This is a slight simplification from the reality of how the instance + object is actually structured. In reality, accessing the flavor, tenant, + and user attributes on it requires an additional step. This code can be + seen in the example code available on github. + +Defining tabs +~~~~~~~~~~~~~ + +So we have a table, ready to receive our data. We could go straight to a view +from here, but we can think bigger. In this case we're also going to use +Horizon's :class:`~horizon.tabs.TabGroup` class. This gives us a clean, +no-fuss tabbed interface to display both our visualization and, optionally, +our data table. + +First off, let's make a tab for our visualization:: + + class VizTab(tabs.Tab): + name = _("Visualization") + slug = "viz" + template_name = "visualizations/flocking/_flocking.html" + + def get_context_data(self, request): + return None + +This is about as simple as you can get. Since our visualization will +ultiimately use AJAX to load it's data we don't need to pass any context +to the template, and all we need to define is the name and which template +it should use. + +Now, we also need a tab for our data table:: + + from .tables import FlockingInstancesTable + + class DataTab(tabs.TableTab): + name = _("Data") + slug = "data" + table_classes = (FlockingInstancesTable,) + template_name = "horizon/common/_detail_table.html" + preload = False + + def get_instances_data(self): + try: + instances = utils.get_instances_data(self.tab_group.request) + except: + instances = [] + exceptions.handle(self.tab_group.request, + _('Unable to retrieve instance list.')) + return instances + +This tab gets a little more complicated. Foremost, it's a special type of +tab--one that 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. + +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 +starting points for all the common combinations of components. + +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. Let's see what the code would look like:: + + from .tables import FlockingInstancesTable + from .tabs import FlockingTabs + + class IndexView(tabs.TabbedTableView): + tab_group_class = FlockingTabs + table_class = FlockingInstancesTable + template_name = 'visualizations/flocking/index.html' + +That would get us 100% of the way to what we need if this particular +demo didn't involve an extra AJAX call to fetch back our visualization +data via AJAX. Because of that we need to override the class' ``get()`` +method to return the right data for an AJAX call:: + + from .tables import FlockingInstancesTable + from .tabs import FlockingTabs + + class IndexView(tabs.TabbedTableView): + tab_group_class = FlockingTabs + table_class = FlockingInstancesTable + template_name = 'visualizations/flocking/index.html' + + def get(self, request, *args, **kwargs): + if self.request.is_ajax() and self.request.GET.get("json", False): + try: + instances = utils.get_instances_data(self.request) + except: + instances = [] + exceptions.handle(request, + _('Unable to retrieve instance list.')) + data = json.dumps([i._apiresource._info for i in instances]) + return http.HttpResponse(data) + else: + return super(IndexView, self).get(request, *args, **kwargs) + +In this instance, we override the ``get()`` method such that if it's an +AJAX request and has the GET parameter we're looking for, it returns our +instance data in JSON format; otherwise it simply returns the view function +as per the usual. + +The template +~~~~~~~~~~~~ + +We need three templates here: one for the view, and one for each of our two +tabs. The view template (in this case) can inherit from one of the other +dashboards:: + + {% extends 'syspanel/base.html' %} + {% load i18n %} + {% block title %}{% trans "Flocking" %}{% endblock %} + + {% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Flocking") %} + {% endblock page_header %} + + {% block syspanel_main %} +
    +
    + {{ tab_group.render }} +
    +
    + {% endblock %} + +This gives us a custom page title, a header, and render our tab group provided +by the view. + +For the tabs, the one using the table is handled by a reusable template, +``"horizon/common/_detail_table.html"``. This is appropriate for any tab that +only displays a single table. + +The second tab is a bit of secret sauce for the visualization, but it's still +quite simple and can be investigated in the github example. + +The takeaway here is that each tab needs a template associated with it. + +With all our code in place, the only thing left to do is to integrated it into +our OpenStack Dashboard site. + +Setting up a project +==================== + +The vast majority of people will just customize the OpenStack Dashboard +example project that ships with Horizon. As such, this tutorial will +start from that and just illustrate the bits that can be customized. + +Structure +--------- + +A site built on Horizon takes the form of a very typical Django project:: + + site/ + |--__init__.py + |--manage.py + |--demo_dashboard/ + |--__init__.py + |--models.py # required for Django even if unused + |--settings.py + |--templates/ + |--static/ + +The key bits here are that ``demo_dashboard`` is on our python path, and that +the `settings.py`` file here will contain our customized Horizon config. + +The settings file +----------------- + +There are several key things you will generally want to customiz in your +site's settings file: specifying custom dashboards and panels, catching your +client's exception classes, and (possibly) specifying a file for advanced +overrides. + +Specifying dashboards +~~~~~~~~~~~~~~~~~~~~~ + +The most basic thing to do is to add your own custom dashboard using the +``HORIZON_CONFIG`` dictionary in the settings file:: + + HORIZON_CONFIG = { + 'dashboards': ('nova', 'syspanel', 'visualizations', 'settings',), + } + +In this case, we've taken the default Horizon ``'dashboards'`` config and +added our ``visualizations`` dashboard to it. Note that the name here is the +name of the dashboard's module on the python path. It will find our +``dashboard.py`` file inside of it and load both the dashboard and its panels +automatically from there. + +Error handling +~~~~~~~~~~~~~~ + +Adding custom error handler for your API client is quite easy. While it's not +necessary for this example, it would be done by customizing the +``'exceptions'`` value in the ``HORIZON_CONFIG`` dictionary:: + + import my_api.exceptions as my_api + + 'exceptions': {'recoverable': [my_api.Error, + my_api.ClientConnectionError], + 'not_found': [my_api.NotFound], + 'unauthorized': [my_api.NotAuthorized]}, + +.. _overrides: + +Override file +~~~~~~~~~~~~~ + +The override file is the "god-mode" dashboard editor. The hook for this file +sits right between the automatic discovery mechanisms and the final setup +routines for the entire site. By specifying an override file you can alter +any behavior you like in existing code. This tutorial won't go in-depth, +but let's just say that with great power comes great responsibility. + +To specify am override file, you set the ``'customization_module'`` value in +the ``HORIZON_CONFIG`` dictionary to the dotted python path of your +override module:: + + HORIZON_CONFIG = { + 'customization_module': 'demo_dashboard.overrides' + } + +This file is capable of adding dashboards, adding panels to existing +dashboards, renaming existing dashboards and panels (or altering other +attributes on them), removing panels from existing dashboards, and so on. + +We could say more, but it only gets more dangerous... + +Conclusion +========== + +Sadly, the cake was a lie. The information in this "tutorial" was never +meant to leave you with a working dashboard. It's close. But there's +waaaaaay too much javascript involved in the visualization to cover it all +here, and it'd be irrelevant to Horizon anyway. + +If you want to see the finished product, check out the github example +referenced at the beginning of this tutorial. + +Clone the repository and simply run ``./run_tests.sh --runserver``. That'll +give you a 100% working dashboard that uses every technique in this tutorial. + +What you've learned here, however, is the fundamentals of almost everything +you need to know to start writing interfaces for your own project based on the +components Horizon provides. + +If you have questions, or feedback on how this tutorial could be improved, +please feel free to pass them along! diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 986ad3df..00000000 --- a/docs/Makefile +++ /dev/null @@ -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 ' where 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." diff --git a/docs/source/_static/.gitignore b/docs/source/_static/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/source/_static/basic.css b/docs/source/_static/basic.css deleted file mode 100644 index d909ce37..00000000 --- a/docs/source/_static/basic.css +++ /dev/null @@ -1,416 +0,0 @@ -/** - * Sphinx stylesheet -- basic theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -img { - border: 0; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -/* -- general body styles --------------------------------------------------- */ - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.field-list ul { - padding-left: 1em; -} - -.first { -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 0; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -/* -- other body styles ----------------------------------------------------- */ - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlight { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.refcount { - color: #060; -} - -.optional { - font-size: 1.3em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -tt.descclassname { - background-color: transparent; -} - -tt.xref, a tt { - background-color: transparent; - font-weight: bold; -} - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - background-color: transparent; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} diff --git a/docs/source/_static/default.css b/docs/source/_static/default.css deleted file mode 100644 index c8091ecb..00000000 --- a/docs/source/_static/default.css +++ /dev/null @@ -1,230 +0,0 @@ -/** - * Sphinx stylesheet -- default theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: sans-serif; - font-size: 100%; - background-color: #11303d; - color: #000; - margin: 0; - padding: 0; -} - -div.document { - background-color: #1c4e63; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 230px; -} - -div.body { - background-color: #ffffff; - color: #000000; - padding: 0 20px 30px 20px; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #133f52; - line-height: 30px; - color: #ffffff; -} - -div.related a { - color: #ffffff; -} - -div.sphinxsidebar { -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; -} - -div.sphinxsidebar h3 a { - color: #ffffff; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #ffffff; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; -} - -div.sphinxsidebar p { - color: #ffffff; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 10px; - padding: 0; - color: #ffffff; -} - -div.sphinxsidebar a { - color: #98dbcc; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -div.body p, div.body dd, div.body li { - text-align: left; - line-height: 130%; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - text-align: left; - line-height: 130%; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: #eeffcc; - color: #333333; - line-height: 120%; - border: 1px solid #ac9; - border-left: none; - border-right: none; -} - -tt { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 0.95em; -} - -.warning tt { - background: #efc2c2; -} - -.note tt { - background: #d6d6d6; -} diff --git a/docs/source/_static/header-line.gif b/docs/source/_static/header-line.gif deleted file mode 100644 index 3601730e..00000000 Binary files a/docs/source/_static/header-line.gif and /dev/null differ diff --git a/docs/source/_static/header_bg.jpg b/docs/source/_static/header_bg.jpg deleted file mode 100644 index f788c41c..00000000 Binary files a/docs/source/_static/header_bg.jpg and /dev/null differ diff --git a/docs/source/_static/jquery.tweet.js b/docs/source/_static/jquery.tweet.js deleted file mode 100644 index 79bf0bdb..00000000 --- a/docs/source/_static/jquery.tweet.js +++ /dev/null @@ -1,154 +0,0 @@ -(function($) { - - $.fn.tweet = function(o){ - var s = { - username: ["seaofclouds"], // [string] required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"] - list: null, //[string] optional name of list belonging to username - avatar_size: null, // [integer] height and width of avatar if displayed (48px max) - count: 3, // [integer] how many tweets to display? - intro_text: null, // [string] do you want text BEFORE your your tweets? - outro_text: null, // [string] do you want text AFTER your tweets? - join_text: null, // [string] optional text in between date and tweet, try setting to "auto" - auto_join_text_default: "i said,", // [string] auto text for non verb: "i said" bullocks - auto_join_text_ed: "i", // [string] auto text for past tense: "i" surfed - auto_join_text_ing: "i am", // [string] auto tense for present tense: "i was" surfing - auto_join_text_reply: "i replied to", // [string] auto tense for replies: "i replied to" @someone "with" - auto_join_text_url: "i was looking at", // [string] auto tense for urls: "i was looking at" http:... - loading_text: null, // [string] optional loading text, displayed while tweets load - query: null // [string] optional search query - }; - - if(o) $.extend(s, o); - - $.fn.extend({ - linkUrl: function() { - var returning = []; - var regexp = /((ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?)/gi; - this.each(function() { - returning.push(this.replace(regexp,"$1")); - }); - return $(returning); - }, - linkUser: function() { - var returning = []; - var regexp = /[\@]+([A-Za-z0-9-_]+)/gi; - this.each(function() { - returning.push(this.replace(regexp,"@$1")); - }); - return $(returning); - }, - linkHash: function() { - var returning = []; - var regexp = / [\#]+([A-Za-z0-9-_]+)/gi; - this.each(function() { - returning.push(this.replace(regexp, ' #$1')); - }); - return $(returning); - }, - capAwesome: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/\b(awesome)\b/gi, '$1')); - }); - return $(returning); - }, - capEpic: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/\b(epic)\b/gi, '$1')); - }); - return $(returning); - }, - makeHeart: function() { - var returning = []; - this.each(function() { - returning.push(this.replace(/(<)+[3]/gi, "")); - }); - return $(returning); - } - }); - - function relative_time(time_value) { - var parsed_date = Date.parse(time_value); - var relative_to = (arguments.length > 1) ? arguments[1] : new Date(); - var delta = parseInt((relative_to.getTime() - parsed_date) / 1000); - var pluralize = function (singular, n) { - return '' + n + ' ' + singular + (n == 1 ? '' : 's'); - }; - if(delta < 60) { - return 'less than a minute ago'; - } else if(delta < (45*60)) { - return 'about ' + pluralize("minute", parseInt(delta / 60)) + ' ago'; - } else if(delta < (24*60*60)) { - return 'about ' + pluralize("hour", parseInt(delta / 3600)) + ' ago'; - } else { - return 'about ' + pluralize("day", parseInt(delta / 86400)) + ' ago'; - } - } - - function build_url() { - var proto = ('https:' == document.location.protocol ? 'https:' : 'http:'); - if (s.list) { - return proto+"//api.twitter.com/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+s.count+"&callback=?"; - } else if (s.query == null && s.username.length == 1) { - return proto+'//twitter.com/status/user_timeline/'+s.username[0]+'.json?count='+s.count+'&callback=?'; - } else { - var query = (s.query || 'from:'+s.username.join('%20OR%20from:')); - return proto+'//search.twitter.com/search.json?&q='+query+'&rpp='+s.count+'&callback=?'; - } - } - - return this.each(function(){ - var list = $('
      ').appendTo(this); - var intro = '

      '+s.intro_text+'

      '; - var outro = '

      '+s.outro_text+'

      '; - var loading = $('

      '+s.loading_text+'

      '); - - if(typeof(s.username) == "string"){ - s.username = [s.username]; - } - - if (s.loading_text) $(this).append(loading); - $.getJSON(build_url(), function(data){ - if (s.loading_text) loading.remove(); - if (s.intro_text) list.before(intro); - $.each((data.results || data), function(i,item){ - // auto join text based on verb tense and content - if (s.join_text == "auto") { - if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) { - var join_text = s.auto_join_text_reply; - } else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) { - var join_text = s.auto_join_text_url; - } else if (item.text.match(/^((\w+ed)|just) .*/im)) { - var join_text = s.auto_join_text_ed; - } else if (item.text.match(/^(\w*ing) .*/i)) { - var join_text = s.auto_join_text_ing; - } else { - var join_text = s.auto_join_text_default; - } - } else { - var join_text = s.join_text; - }; - - var from_user = item.from_user || item.user.screen_name; - var profile_image_url = item.profile_image_url || item.user.profile_image_url; - var join_template = ' '+join_text+' '; - var join = ((s.join_text) ? join_template : ' '); - var avatar_template = ''+from_user+'\'s avatar'; - var avatar = (s.avatar_size ? avatar_template : ''); - var date = ''+relative_time(item.created_at)+''; - var text = '' +$([item.text]).linkUrl().linkUser().linkHash().makeHeart().capAwesome().capEpic()[0]+ ''; - - // until we create a template option, arrange the items below to alter a tweet's display. - list.append('
    • ' + avatar + date + join + text + '
    • '); - - list.children('li:first').addClass('tweet_first'); - list.children('li:odd').addClass('tweet_even'); - list.children('li:even').addClass('tweet_odd'); - }); - if (s.outro_text) list.after(outro); - }); - - }); - }; -})(jQuery); \ No newline at end of file diff --git a/docs/source/_static/openstack_logo.png b/docs/source/_static/openstack_logo.png deleted file mode 100644 index 146faec5..00000000 Binary files a/docs/source/_static/openstack_logo.png and /dev/null differ diff --git a/docs/source/_static/tweaks.css b/docs/source/_static/tweaks.css deleted file mode 100644 index 8ed4c9db..00000000 --- a/docs/source/_static/tweaks.css +++ /dev/null @@ -1,93 +0,0 @@ -body { - background: #fff url(../_static/header_bg.jpg) top left no-repeat; -} - -#header { - width: 950px; - margin: 0 auto; - height: 102px; -} - -#header h1#logo { - background: url(../_static/openstack_logo.png) top left no-repeat; - display: block; - float: left; - text-indent: -9999px; - width: 175px; - height: 55px; -} - -#navigation { - background: url(../_static/header-line.gif) repeat-x 0 bottom; - display: block; - float: left; - margin: 27px 0 0 25px; - padding: 0; -} - -#navigation li{ - float: left; - display: block; - margin-right: 25px; -} - -#navigation li a { - display: block; - font-weight: normal; - text-decoration: none; - background-position: 50% 0; - padding: 20px 0 5px; - color: #353535; - font-size: 14px; -} - -#navigation li a.current, #navigation li a.section { - border-bottom: 3px solid #cf2f19; - color: #cf2f19; -} - -div.related { - background-color: #cde2f8; - border: 1px solid #b0d3f8; -} - -div.related a { - color: #4078ba; - text-shadow: none; -} - -div.sphinxsidebarwrapper { - padding-top: 0; -} - -pre { - color: #555; -} - -div.documentwrapper h1, div.documentwrapper h2, div.documentwrapper h3, div.documentwrapper h4, div.documentwrapper h5, div.documentwrapper h6 { - font-family: 'PT Sans', sans-serif !important; - color: #264D69; - border-bottom: 1px dotted #C5E2EA; - padding: 0; - background: none; - padding-bottom: 5px; -} - -div.documentwrapper h3 { - color: #CF2F19; -} - -a.headerlink { - color: #fff !important; - margin-left: 5px; - background: #CF2F19 !important; -} - -div.body { - margin-top: -25px; -} - -div.document { - width: 960px; - margin: 0 auto; -} \ No newline at end of file diff --git a/docs/source/_templates/.placeholder b/docs/source/_templates/.placeholder deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/source/_theme/layout.html b/docs/source/_theme/layout.html deleted file mode 100644 index 750b7822..00000000 --- a/docs/source/_theme/layout.html +++ /dev/null @@ -1,83 +0,0 @@ -{% extends "basic/layout.html" %} -{% set css_files = css_files + ['_static/tweaks.css'] %} -{% set script_files = script_files + ['_static/jquery.tweet.js'] %} - -{%- macro sidebar() %} - {%- if not embedded %}{% if not theme_nosidebar|tobool %} -
      -
      - {%- block sidebarlogo %} - {%- if logo %} - - {%- endif %} - {%- endblock %} - {%- block sidebartoc %} - {%- if display_toc %} -

      {{ _('Table Of Contents') }}

      - {{ toc }} - {%- endif %} - {%- endblock %} - {%- block sidebarrel %} - {%- if prev %} -

      {{ _('Previous topic') }}

      -

      {{ prev.title }}

      - {%- endif %} - {%- if next %} -

      {{ _('Next topic') }}

      -

      {{ next.title }}

      - {%- endif %} - {%- endblock %} - {%- block sidebarsourcelink %} - {%- if show_source and has_source and sourcename %} -

      {{ _('This Page') }}

      - - {%- endif %} - {%- endblock %} - {%- if customsidebar %} - {% include customsidebar %} - {%- endif %} - {%- block sidebarsearch %} - {%- if pagename != "search" %} - - - {%- endif %} - {%- endblock %} -
      -
      - {%- endif %}{% endif %} -{%- endmacro %} - -{% block relbar1 %}{% endblock relbar1 %} - -{% block header %} - -{% endblock %} \ No newline at end of file diff --git a/docs/source/_theme/nature.css_t b/docs/source/_theme/nature.css_t deleted file mode 100644 index a98bd420..00000000 --- a/docs/source/_theme/nature.css_t +++ /dev/null @@ -1,245 +0,0 @@ -/* - * nature.css_t - * ~~~~~~~~~~~~ - * - * Sphinx stylesheet -- nature theme. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -/* -- page layout ----------------------------------------------------------- */ - -body { - font-family: Arial, sans-serif; - font-size: 100%; - background-color: #111; - color: #555; - margin: 0; - padding: 0; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 {{ theme_sidebarwidth|toint }}px; -} - -hr { - border: 1px solid #B1B4B6; -} - -div.document { - background-color: #eee; -} - -div.body { - background-color: #ffffff; - color: #3E4349; - padding: 0 30px 30px 30px; - font-size: 0.9em; -} - -div.footer { - color: #555; - width: 100%; - padding: 13px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #444; - text-decoration: underline; -} - -div.related { - background-color: #6BA81E; - line-height: 32px; - color: #fff; - text-shadow: 0px 1px 0 #444; - font-size: 0.9em; -} - -div.related a { - color: #E2F3CC; -} - -div.sphinxsidebar { - font-size: 0.75em; - line-height: 1.5em; -} - -div.sphinxsidebarwrapper{ - padding: 20px 0; -} - -div.sphinxsidebar h3, -div.sphinxsidebar h4 { - font-family: Arial, sans-serif; - color: #222; - font-size: 1.2em; - font-weight: normal; - margin: 0; - padding: 5px 10px; - background-color: #ddd; - text-shadow: 1px 1px 0 white -} - -div.sphinxsidebar h4{ - font-size: 1.1em; -} - -div.sphinxsidebar h3 a { - color: #444; -} - - -div.sphinxsidebar p { - color: #888; - padding: 5px 20px; -} - -div.sphinxsidebar p.topless { -} - -div.sphinxsidebar ul { - margin: 10px 20px; - padding: 0; - color: #000; -} - -div.sphinxsidebar a { - color: #444; -} - -div.sphinxsidebar input { - border: 1px solid #ccc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar input[type=text]{ - margin-left: 20px; -} - -/* -- body styles ----------------------------------------------------------- */ - -a { - color: #005B81; - text-decoration: none; -} - -a:hover { - color: #E32E00; - text-decoration: underline; -} - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: Arial, sans-serif; - background-color: #BED4EB; - font-weight: normal; - color: #212224; - margin: 30px 0px 10px 0px; - padding: 5px 0 5px 10px; - text-shadow: 0px 1px 0 white -} - -div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 150%; background-color: #C8D5E3; } -div.body h3 { font-size: 120%; background-color: #D8DEE3; } -div.body h4 { font-size: 110%; background-color: #D8DEE3; } -div.body h5 { font-size: 100%; background-color: #D8DEE3; } -div.body h6 { font-size: 100%; background-color: #D8DEE3; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - line-height: 1.5em; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.highlight{ - background-color: white; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 10px; - background-color: White; - color: #222; - line-height: 1.2em; - border: 1px solid #C6C9CB; - font-size: 1.1em; - margin: 1.5em 0 1.5em 0; - -webkit-box-shadow: 1px 1px 1px #d8d8d8; - -moz-box-shadow: 1px 1px 1px #d8d8d8; -} - -tt { - background-color: #ecf0f3; - color: #222; - /* padding: 1px 2px; */ - font-size: 1.1em; - font-family: monospace; -} - -.viewcode-back { - font-family: Arial, sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} diff --git a/docs/source/_theme/theme.conf b/docs/source/_theme/theme.conf deleted file mode 100644 index 1cc40044..00000000 --- a/docs/source/_theme/theme.conf +++ /dev/null @@ -1,4 +0,0 @@ -[theme] -inherit = basic -stylesheet = nature.css -pygments_style = tango diff --git a/docs/source/conf.py b/docs/source/conf.py deleted file mode 100644 index 1fa2d706..00000000 --- a/docs/source/conf.py +++ /dev/null @@ -1,417 +0,0 @@ -# -*- coding: utf-8 -*- -# -# 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. - -import sys -import os - -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['DJANGO_SETTINGS_MODULE'] = 'openstack_dashboard.settings' - -import horizon.version - - -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.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.tests', 'openstack_dashboard.tests',) - 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("=================\n") - INDEXOUT.write("Source Code Index\n") - INDEXOUT.write("=================\n") - - for modulename, path in SRCS.items(): - 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.intersphinx', - 'sphinx.ext.todo', - 'sphinx.ext.coverage', - 'sphinx.ext.pngmath', - 'sphinx.ext.viewcode'] - -# Add any paths that contain templates here, relative to this directory. -if os.getenv('HUDSON_PUBLISH_DOCS'): - templates_path = ['_ga', '_templates'] -else: - 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, LLC' - -# 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.canonical_version_string() -# The full version, including alpha/beta/rc tags. -release = horizon.version.canonical_version_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 -# " v 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 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, LLC', '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 a 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 - - -# Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('http://docs.python.org/', None), - 'django': ('http://docs.djangoproject.com/en/dev/_objects/'), - 'nova': ('http://nova.openstack.org', None), - 'swift': ('http://swift.openstack.org', None), - 'keystone': ('http://keystone.openstack.org', None), - 'glance': ('http://glance.openstack.org', None)} diff --git a/docs/source/contributing.rst b/docs/source/contributing.rst deleted file mode 100644 index e884d854..00000000 --- a/docs/source/contributing.rst +++ /dev/null @@ -1,204 +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 -* Jenkins build status: https://jenkins.openstack.org/view/Horizon/ -* 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. -#. Request to join the `OpenStack Contributors`_ team on Launchpad. -#. Join the `Horizon Developers`_ team on Launchpad. -#. Follow the `instructions for setting up git-review`_ in your - development environment. - -Whew! Got that 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! - -.. _`bug tracker`: https://bugs.launchpad.net/horizon - -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 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://wiki.openstack.org/GerritWorkflow - -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. - -Code Style -========== - -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 ----------- - -As a project, Horizon adheres to code quality standards for our JavaScript -just as we do for our Python. To that end we recommend (but do not strictly -enforce) the use of JSLint_ to validate some general best practices. - -The default options are mostly good, but the following accommodate some -allowances we make: - -* Set ``Indentation`` to ``2``. -* Enable the ``Assume console, alert, ...`` option. -* Enable the ``Assume a browser`` option. -* Enable the ``Tolerate missing 'use strict' pragma`` option. -* Clear the ``Maximum number of errors`` field. -* Add ``horizon,$`` to the ``Predefined`` list. - -.. _JSLint: http://jslint.com/ - -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. - -HTML ----- - -Again, readability is paramount; however be conscientous 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. diff --git a/docs/source/faq.rst b/docs/source/faq.rst deleted file mode 100644 index 26836ef6..00000000 --- a/docs/source/faq.rst +++ /dev/null @@ -1,37 +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. - diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst deleted file mode 100644 index 36bf635e..00000000 --- a/docs/source/glossary.rst +++ /dev/null @@ -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. "syspanel") - 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. diff --git a/docs/source/index.rst b/docs/source/index.rst deleted file mode 100644 index 111a1d31..00000000 --- a/docs/source/index.rst +++ /dev/null @@ -1,128 +0,0 @@ -.. - Copyright 2012 OpenStack, LLC - 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 -`_, 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 `. - -To learn what you need to know to get going, see the :doc:`quickstart`. - -Getting Started With Horizon -============================ - -How to use Horizon in your own projects. - -.. toctree:: - :maxdepth: 1 - - intro - quickstart - topics/tutorial - topics/deployment - 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 - - 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/tables - topics/testing - -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/users - ref/forms - ref/views - ref/middleware - ref/context_processors - ref/decorators - ref/exceptions - ref/test - -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` diff --git a/docs/source/intro.rst b/docs/source/intro.rst deleted file mode 100644 index a546ce99..00000000 --- a/docs/source/intro.rst +++ /dev/null @@ -1,124 +0,0 @@ -=================== -Introducing Horizon -=================== - -.. contents:: Contents: - :local: - -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 ` - A short guide to getting started with using Horizon. - - :doc:`Frequently Asked Questions ` - Common questions and answers. - - :doc:`Glossary ` - Common terms and their definitions. diff --git a/docs/source/quickstart.rst b/docs/source/quickstart.rst deleted file mode 100644 index c8c8644a..00000000 --- a/docs/source/quickstart.rst +++ /dev/null @@ -1,194 +0,0 @@ -================== -Horizon Quickstart -================== - -Setup -===== - -To setup an Horizon development environment simply clone the Horizon git -repository at http://github.com/openstack/horizon and execute the -``run_tests.sh`` script from the root folder (see :doc:`ref/run_tests`). - -Horizon assumes a single end-point for OpenStack services which defaults to -the local host (127.0.0.1). If this is not the case change the -``OPENSTACK_HOST`` setting in the ``local_settings.py`` file, located in the -``openstack_dashboard/local`` folder, to the actual IP address of the -OpenStack end-point Horizon should use. - -To start the Horizon development server use the Django ``manage.py`` utility -with the context of the virtual environment:: - - > tools/with_venv.sh ./manage.py runserver - -Alternately specify the listen IP and port:: - - > tools/with_venv.sh ./manage.py runserver 0.0.0.0:8080 - -Once the Horizon server is running point a web browser to http://localhost:8000 -or to the IP and port the server is listening. - -.. note:: - - The ``DevStack`` project (http://devstack.org/) can be used to install - an OpenStack development environment from scratch. - -.. note:: - - The minimum required set of OpenStack services running includes the - following: - - * Nova (compute, api, scheduler, network, *and* volume services) - * Glance - * Keystone - - Optional support is provided for Swift. - -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 ``tools/pip-requires``. - - .. 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 `. - -Project -======= - -INSTALLED_APPS --------------- - -At the project level you add Horizon and any desired dashboards to your -``settings.INSTALLED_APPS``:: - - INSTALLED_APPS = ( - 'django', - ... - 'horizon', - 'horizon.dash', - 'horizon.syspanel', - ) - -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 %} - - - -And in your ``sidebar.html`` you might have:: - - {% load horizon %} - - - -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 syspanel as -an example):: - - syspanel/ - |---__init__.py - |---dashboard.py <-----Registers the app with Horizon and sets dashboard properties - |---templates/ - |---templatetags/ - |---overview/ - |---services/ - |---images/ - |---__init__.py - |---panel.py <-----Registers the panel in the app and defines panel properties - |---urls.py - |---views.py - |---forms.py - |---tests.py - |---api.py <-------Optional additional API methods for non-core services - |---templates/ - ... - ... - -Dashboard Classes ------------------ - -Inside of ``dashboard.py`` you would have a class definition and the registration -process:: - - import horizon - - - class Syspanel(horizon.Dashboard): - name = "Syspanel" # Appears in navigation - slug = 'syspanel' # Appears in url - panels = ('overview', 'services', 'instances', 'flavors', 'images', - 'tenants', 'users', 'quotas',) - default_panel = 'overview' - roles = ('admin',) # Provides RBAC at the dashboard-level - ... - - - horizon.register(Syspanel) - -Panel Classes -------------- - -To connect a :class:`~horizon.Panel` with a :class:`~horizon.Dashboard` class -you register it in a ``panels.py`` file like so:: - - import horizon - - from horizon.dashboard.syspanel import dashboard - - - class Images(horizon.Panel): - name = "Images" - slug = 'images' - roles = ('admin', 'my_other_role',) # Fine-grained RBAC per-panel - - - # You could also register your panel with another application's dashboard - dashboard.Syspanel.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. diff --git a/docs/source/ref/context_processors.rst b/docs/source/ref/context_processors.rst deleted file mode 100644 index b34c0109..00000000 --- a/docs/source/ref/context_processors.rst +++ /dev/null @@ -1,6 +0,0 @@ -========================== -Horizon Context Processors -========================== - -.. automodule:: horizon.context_processors - :members: diff --git a/docs/source/ref/decorators.rst b/docs/source/ref/decorators.rst deleted file mode 100644 index 777afbe5..00000000 --- a/docs/source/ref/decorators.rst +++ /dev/null @@ -1,6 +0,0 @@ -================== -Horizon Decorators -================== - -.. automodule:: horizon.decorators - :members: diff --git a/docs/source/ref/exceptions.rst b/docs/source/ref/exceptions.rst deleted file mode 100644 index 4151f18f..00000000 --- a/docs/source/ref/exceptions.rst +++ /dev/null @@ -1,6 +0,0 @@ -================== -Horizon Exceptions -================== - -.. automodule:: horizon.exceptions - :members: diff --git a/docs/source/ref/forms.rst b/docs/source/ref/forms.rst deleted file mode 100644 index 9b30cb8e..00000000 --- a/docs/source/ref/forms.rst +++ /dev/null @@ -1,17 +0,0 @@ -============= -Horizon Forms -============= - -Horizon ships with a number of form classes, some generic and some specific. - -Generic Forms -============= - -.. automodule:: horizon.forms - :members: - -Auth Forms -========== - -.. automodule:: horizon.views.auth_forms - :members: diff --git a/docs/source/ref/horizon.rst b/docs/source/ref/horizon.rst deleted file mode 100644 index cc4b2d70..00000000 --- a/docs/source/ref/horizon.rst +++ /dev/null @@ -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: diff --git a/docs/source/ref/middleware.rst b/docs/source/ref/middleware.rst deleted file mode 100644 index fcca5ff0..00000000 --- a/docs/source/ref/middleware.rst +++ /dev/null @@ -1,6 +0,0 @@ -================== -Horizon Middleware -================== - -.. automodule:: horizon.middleware - :members: diff --git a/docs/source/ref/run_tests.rst b/docs/source/ref/run_tests.rst deleted file mode 100644 index 579d7d48..00000000 --- a/docs/source/ref/run_tests.rst +++ /dev/null @@ -1,205 +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 - ``openstack-dashboard/tools/install_venv.py``. - #. Run the tests for both ``horizon`` and ``openstack-dashboard`` using - their respective environments and verify that evreything 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 quicker test run you can skip the Selenium -tests by using the ``--skip-selenium`` flag:: - - ./run_tests.sh --skip-selenium - -This isn't recommended, but can be a timesaver when you only need to run -the code tests and not the frontend tests during development. - -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 - -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 --dashboard= - -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.). -* --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``. - -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:: - - ./openstack-dashboard/tools/with_venv.sh ./openstack-dashboard/dashboard/manage.py runserver - -Generating the documentation -============================ - -You can build Horizon's documentation automatically by running:: - - ./run_tests.sh --docs - -The output is stored in ``./docs/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 incrementing an -``environment_version`` integer at the top of ``run_tests.sh``. - -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 as well. diff --git a/docs/source/ref/tables.rst b/docs/source/ref/tables.rst deleted file mode 100644 index 4b74d5e3..00000000 --- a/docs/source/ref/tables.rst +++ /dev/null @@ -1,82 +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! - -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: - -Table Components -================ - -.. autoclass:: Column - :members: - -.. autoclass:: Row - :members: - -Actions -======= - -.. autoclass:: Action - :members: - -.. autoclass:: LinkAction - :members: - -.. autoclass:: FilterAction - :members: - -.. autoclass:: BatchAction - :members: - -.. autoclass:: DeleteAction - :members: - -Class-Based Views -================= - -Several class-based views are provided to make working with DataTables -easier in your UI. - -.. autoclass:: DataTableView - -.. autoclass:: MultiTableView diff --git a/docs/source/ref/tabs.rst b/docs/source/ref/tabs.rst deleted file mode 100644 index 807385ca..00000000 --- a/docs/source/ref/tabs.rst +++ /dev/null @@ -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: diff --git a/docs/source/ref/test.rst b/docs/source/ref/test.rst deleted file mode 100644 index ccf3f046..00000000 --- a/docs/source/ref/test.rst +++ /dev/null @@ -1,17 +0,0 @@ -======================== -Horizon TestCase Classes -======================== - -.. module:: horizon.test - -Horizon provides several useful base classes for testing both views and -API functions. - -.. autoclass:: TestCase - :members: - -.. autoclass:: APITestCase - :members: - -.. autoclass:: BaseAdminViewTests - :members: diff --git a/docs/source/ref/users.rst b/docs/source/ref/users.rst deleted file mode 100644 index 857358d1..00000000 --- a/docs/source/ref/users.rst +++ /dev/null @@ -1,6 +0,0 @@ -================= -Horizon User APIs -================= - -.. automodule:: horizon.users - :members: diff --git a/docs/source/ref/views.rst b/docs/source/ref/views.rst deleted file mode 100644 index 970609ca..00000000 --- a/docs/source/ref/views.rst +++ /dev/null @@ -1,12 +0,0 @@ -============= -Horizon Views -============= - -Horizon ships with a number of pre-built views which are used within -Horizon and can also be reused in your applications. - -Auth -==== - -.. automodule:: horizon.views.auth - :members: diff --git a/docs/source/ref/workflows.rst b/docs/source/ref/workflows.rst deleted file mode 100644 index c4077667..00000000 --- a/docs/source/ref/workflows.rst +++ /dev/null @@ -1,33 +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. - -Workflows -========= - -.. autoclass:: Workflow - :members: - -Steps -===== - -.. autoclass:: Step - :members: - -Actions -======= - -.. autoclass:: Action - :members: - -WorkflowView -============ - -.. autoclass:: WorkflowView - :members: diff --git a/docs/source/releases/2012_1.rst b/docs/source/releases/2012_1.rst deleted file mode 100644 index e024bf41..00000000 --- a/docs/source/releases/2012_1.rst +++ /dev/null @@ -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 extensability, 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 -encompasse 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 elegently 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 userful 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 compatibliity. diff --git a/docs/source/testing.rst b/docs/source/testing.rst deleted file mode 100644 index b3b15038..00000000 --- a/docs/source/testing.rst +++ /dev/null @@ -1,39 +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 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 - -.. seealso:: - - :doc:`ref/run_tests` - Full reference for the ``run_tests.sh`` script. - -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 `. diff --git a/docs/source/topics/customizing.rst b/docs/source/topics/customizing.rst deleted file mode 100644 index 040319a2..00000000 --- a/docs/source/topics/customizing.rst +++ /dev/null @@ -1,94 +0,0 @@ -=================== -Customizing Horizon -=================== - -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 -``horizon/openstack-dashboard/local/local_settings.py``. - -Changing the Logo -================= - -The OpenStack Logo is pulled in through ``style.css``:: - - #splash .modal { - background: #fff url(../images/logo.png) no-repeat center 35px; - - h1.brand a { - background: url(../images/logo.png) top left no-repeat; - -To override the OpenStack Logo image, replace the image at the directory path -``horizon/openstack-dashboard/dashboard/static/dashboard/images/logo.png``. - -The dimensions should be ``width: 108px, height: 121px``. - -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 ``settings.HORIZON_CONFIG`` dictionary. -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" - } - - -Button Icons -============ - -Horizon provides hooks for customizing the look and feel of each class of -button on the site. The following classes are used to identify each type of -button: - -* Generic Classes - * btn-search - * btn-delete - * btn-upload - * btn-download - * btn-create - * btn-edit - * btn-list - * btn-copy - * btn-camera - * btn-stats - * btn-enable - * btn-disable - -* Floating IP-specific Classes - * btn-allocate - * btn-release - * btn-associate - * btn-disassociate - -* Instance-specific Classes - * btn-launch - * btn-terminate - * btn-reboot - * btn-pause - * btn-suspend - * btn-console - * btn-log - -* Volume-specific classes - * btn-detach - -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. diff --git a/docs/source/topics/deployment.rst b/docs/source/topics/deployment.rst deleted file mode 100644 index 16f4e105..00000000 --- a/docs/source/topics/deployment.rst +++ /dev/null @@ -1,147 +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. - -Logging -======= - -Logging is an important concern for prouction 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. - -.. 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 - -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 depooyment 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': { - # Databe 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 diff --git a/docs/source/topics/tables.rst b/docs/source/topics/tables.rst deleted file mode 100644 index b59dffb2..00000000 --- a/docs/source/topics/tables.rst +++ /dev/null @@ -1,129 +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 `. - -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 it's 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. - -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. diff --git a/docs/source/topics/testing.rst b/docs/source/topics/testing.rst deleted file mode 100644 index 5c36ac82..00000000 --- a/docs/source/topics/testing.rst +++ /dev/null @@ -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 -stright 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 a 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. -* Don'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 buidling (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:`~horizon.test.TestCase.assertNoFormErrors`, -:meth:`~horizon.test.TestCase.assertMessageCount`, and -:meth:`~horizon.test.TestCase.asertNoMessages` 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:`~horizon.test.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.TestCase.assertMessageCount` and - :meth:`~horizon.test.TestCase.asertNoMessages` 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 -tol 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:`~horizon.test.TestCase.assertNoFormErrors` and -:meth:`~horizon.test.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. diff --git a/docs/source/topics/tutorial.rst b/docs/source/topics/tutorial.rst deleted file mode 100644 index 1a6f48da..00000000 --- a/docs/source/topics/tutorial.rst +++ /dev/null @@ -1,545 +0,0 @@ -=================== -Building on Horizon -=================== - -This tutorial covers how to use the various components in Horizon to build -an example dashboard and panel with a data table and tabs. - -As an example, we'll build on the Nova instances API to create a new and novel -"visualizations" dashboard with a "flocking" panel that presents the instance -data in a different manner. - -You can find a reference implementation of the code being described here -on github at https://github.com/gabrielhurley/horizon_demo. - -.. note:: - - There are a variety of other resources which may be helpful to read first, - since this is a more advanced tutorial. For example, you may want to start - with the :doc:`Horizon quickstart guide ` or the - `Django tutorial`_. - - .. _Django tutorial: https://docs.djangoproject.com/en/1.4/intro/tutorial01/ - - -Creating a dashboard -==================== - -.. note:: - - It is perfectly valid to create a panel without a dashboard, and - incorporate it into an existing dashboard. See the section - :ref:`overrides ` later in this document. - -The quick version ------------------ - -Horizon provides a custom management command to create a typical base -dashboard structure for you. The following command generates most of the -boilerplate code explained below:: - - ./run_tests.sh -m startdash visualizations - -It's still recommended that you read the rest of this section to understand -what that command creates and why. - -Structure ---------- - -The recommended structure for a dashboard (or panel) follows suit with the -typical Django application layout. We'll name our dashboard "visualizations":: - - visualizations - |--__init__.py - |--dashboard.py - |--templates/ - |--static/ - -The ``dashboard.py`` module will contain our dashboard class for use by -Horizon; the ``templates`` and ``static`` directories give us homes for our -Django template files and static media respectively. - -Within the ``static`` and ``templates`` directories it's generally good to -namespace your files like so:: - - templates/ - |--visualizations/ - static/ - |--visualizations/ - |--css/ - |--js/ - |--img/ - -With those files and directories in place, we can move on to writing our -dashboard class. - - -Defining a dashboard --------------------- - -A dashboard class can be incredibly simple (about 3 lines at minimum), -defining nothing more than a name and a slug:: - - import horizon - - class VizDash(horizon.Dashboard): - name = _("Visualizations") - slug = "visualizations" - -In practice, a dashboard class will usually contain more information, such -as a list of panels, which panel is the default, and any roles required to -access this dashboard:: - - class VizDash(horizon.Dashboard): - name = _("Visualizations") - slug = "visualizations" - panels = ('flocking',) - default_panel = 'flocking' - roles = ('admin',) - -Building from that previous example we may also want to define a grouping of -panels which share a common theme and have a sub-heading in the navigation:: - - class InstanceVisualizations(horizon.PanelGroup): - slug = "instance_visualizations" - name = _("Instance Visualizations") - panels = ('flocking',) - - - class VizDash(horizon.Dashboard): - name = _("Visualizations") - slug = "visualizations" - panels = (InstanceVisualizations,) - default_panel = 'flocking' - roles = ('admin',) - -The ``PanelGroup`` can be added to the dashboard class' ``panels`` list -just like the slug of the panel can. - -Once our dashboard class is complete, all we need to do is register it:: - - horizon.register(VizDash) - -The typical place for that would be the bottom of the ``dashboard.py`` file, -but it could also go elsewhere, such as in an override file (see below). - - -Creating a panel -================ - -Now that we have our dashboard written, we can also create our panel. We'll -call it "flocking". - -.. note:: - - You don't need to write a custom dashboard to add a panel. The structure - here is for the sake of completeness in the tutorial. - -The quick version ------------------ - -Horizon provides a custom management command to create a typical base -panel structure for you. The following command generates most of the -boilerplate code explained below:: - - ./run_tests.sh -m startpanel flocking --dashboard=visualizations --target=auto - -The ``dashboard`` argument is required, and tells the command which dashboard -this panel will be registered with. The ``target`` argument is optional, and -respects ``auto`` as a special value which means that the files for the panel -should be created inside the dashboard module as opposed to the current -directory (the default). - -It's still recommended that you read the rest of this section to understand -what that command creates and why. - -Structure ---------- - -A panel is a relatively flat structure with the exception that templates -for a panel in a dashboard live in the dashboard's ``templates`` directory -rather than in the panel's ``templates`` directory. Continuing our -vizulaization/flocking example, let's see what the looks like:: - - # stand-alone panel structure - flocking/ - |--__init__.py - |--panel.py - |--urls.py - |--views.py - |--templates/ - |--flocking/ - |--index.html - - # panel-in-a-dashboard structure - visualizations/ - |--__init__.py - |--dashboard.py - |--flocking/ - |--__init__.py - |--panel.py - |--urls.py - |--views.py - |--templates/ - |--visualizations/ - |--flocking/ - |--index.html - -That follows standard Django namespacing conventions for apps and submodules -within apps. It also works cleanly with Django's automatic template discovery -in both cases. - -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 ``panel.py`` file -in a corresponding directory (the details are a bit magical, but have been -thoroughly vetted in Django's admin codebase). - -Inside the ``panel.py`` module we define our ``Panel`` class:: - - class Flocking(horizon.Panel): - name = _("Flocking") - slug = 'flocking' - -Simple, right? Once we've defined it, we register it with the dashboard:: - - from visualizations import dashboard - - dashboard.VizDash.register(Flocking) - -Easy! There are more options you can set to customize the ``Panel`` class, but -it makes some intelligent guesses about what the defaults should be. - -URLs ----- - -One of the intelligent assumptions the ``Panel`` class makes is that it can -find a ``urls.py`` file in your panel directory which will define a view named -``index`` that handles the default view for that panel. This is what your -``urls.py`` file might look like:: - - from django.conf.urls.defaults import patterns, url - from .views import IndexView - - urlpatterns = patterns('', - url(r'^$', IndexView.as_view(), name='index') - ) - -There's nothing there that isn't 100% standard Django code. This example -(and Horizon in general) uses the class-based views introduced in Django 1.3 -to make code more reusable. Hence the view class is imported in the example -above, and the ``as_view()`` method is called in the URL pattern. - -This, of course, presumes you have a view class, and takes us into the meat -of writing a ``Panel``. - - -Tables, Tabs, and Views ------------------------ - -Now we get to the really exciting parts; everything before this was structural. - -Starting with the high-level view, our end goal is to create a view (our -``IndexView`` class referenced above) which uses Horizon's ``DataTable`` -class to display data and Horizon's ``TabGroup`` class to give us a -user-friendly tabbed interface in the browser. - -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.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. - -In this case, we're going to be presenting data about tables, so let's start -defining our table (and a ``tables.py`` module:: - - from horizon import tables - - class FlockingInstancesTable(tables.DataTable): - host = tables.Column("OS-EXT-SRV-ATTR:host", verbose_name=_("Host")) - tenant = tables.Column('tenant_name', verbose_name=_("Tenant")) - user = tables.Column('user_name', verbose_name=_("user")) - vcpus = tables.Column('flavor_vcpus', verbose_name=_("VCPUs")) - memory = tables.Column('flavor_memory', verbose_name=_("Memory")) - age = tables.Column('age', verbose_name=_("Age")) - - class Meta: - name = "instances" - verbose_name = _("Instances") - -There are several things going on here... we created a table subclass, -and defined six columns on it. 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 defines some properties about our -table, notably it's (translatable) verbose name, and a semi-unique "slug"-like -name to identify it. - -.. note:: - - This is a slight simplification from the reality of how the instance - object is actually structured. In reality, accessing the flavor, tenant, - and user attributes on it requires an additional step. This code can be - seen in the example code available on github. - -Defining tabs -~~~~~~~~~~~~~ - -So we have a table, ready to receive our data. We could go straight to a view -from here, but we can think bigger. In this case we're also going to use -Horizon's :class:`~horizon.tabs.TabGroup` class. This gives us a clean, -no-fuss tabbed interface to display both our visualization and, optionally, -our data table. - -First off, let's make a tab for our visualization:: - - class VizTab(tabs.Tab): - name = _("Visualization") - slug = "viz" - template_name = "visualizations/flocking/_flocking.html" - - def get_context_data(self, request): - return None - -This is about as simple as you can get. Since our visualization will -ultiimately use AJAX to load it's data we don't need to pass any context -to the template, and all we need to define is the name and which template -it should use. - -Now, we also need a tab for our data table:: - - from .tables import FlockingInstancesTable - - class DataTab(tabs.TableTab): - name = _("Data") - slug = "data" - table_classes = (FlockingInstancesTable,) - template_name = "horizon/common/_detail_table.html" - preload = False - - def get_instances_data(self): - try: - instances = utils.get_instances_data(self.tab_group.request) - except: - instances = [] - exceptions.handle(self.tab_group.request, - _('Unable to retrieve instance list.')) - return instances - -This tab gets a little more complicated. Foremost, it's a special type of -tab--one that 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. - -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 -starting points for all the common combinations of components. - -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. Let's see what the code would look like:: - - from .tables import FlockingInstancesTable - from .tabs import FlockingTabs - - class IndexView(tabs.TabbedTableView): - tab_group_class = FlockingTabs - table_class = FlockingInstancesTable - template_name = 'visualizations/flocking/index.html' - -That would get us 100% of the way to what we need if this particular -demo didn't involve an extra AJAX call to fetch back our visualization -data via AJAX. Because of that we need to override the class' ``get()`` -method to return the right data for an AJAX call:: - - from .tables import FlockingInstancesTable - from .tabs import FlockingTabs - - class IndexView(tabs.TabbedTableView): - tab_group_class = FlockingTabs - table_class = FlockingInstancesTable - template_name = 'visualizations/flocking/index.html' - - def get(self, request, *args, **kwargs): - if self.request.is_ajax() and self.request.GET.get("json", False): - try: - instances = utils.get_instances_data(self.request) - except: - instances = [] - exceptions.handle(request, - _('Unable to retrieve instance list.')) - data = json.dumps([i._apiresource._info for i in instances]) - return http.HttpResponse(data) - else: - return super(IndexView, self).get(request, *args, **kwargs) - -In this instance, we override the ``get()`` method such that if it's an -AJAX request and has the GET parameter we're looking for, it returns our -instance data in JSON format; otherwise it simply returns the view function -as per the usual. - -The template -~~~~~~~~~~~~ - -We need three templates here: one for the view, and one for each of our two -tabs. The view template (in this case) can inherit from one of the other -dashboards:: - - {% extends 'syspanel/base.html' %} - {% load i18n %} - {% block title %}{% trans "Flocking" %}{% endblock %} - - {% block page_header %} - {% include "horizon/common/_page_header.html" with title=_("Flocking") %} - {% endblock page_header %} - - {% block syspanel_main %} -
      -
      - {{ tab_group.render }} -
      -
      - {% endblock %} - -This gives us a custom page title, a header, and render our tab group provided -by the view. - -For the tabs, the one using the table is handled by a reusable template, -``"horizon/common/_detail_table.html"``. This is appropriate for any tab that -only displays a single table. - -The second tab is a bit of secret sauce for the visualization, but it's still -quite simple and can be investigated in the github example. - -The takeaway here is that each tab needs a template associated with it. - -With all our code in place, the only thing left to do is to integrated it into -our OpenStack Dashboard site. - -Setting up a project -==================== - -The vast majority of people will just customize the OpenStack Dashboard -example project that ships with Horizon. As such, this tutorial will -start from that and just illustrate the bits that can be customized. - -Structure ---------- - -A site built on Horizon takes the form of a very typical Django project:: - - site/ - |--__init__.py - |--manage.py - |--demo_dashboard/ - |--__init__.py - |--models.py # required for Django even if unused - |--settings.py - |--templates/ - |--static/ - -The key bits here are that ``demo_dashboard`` is on our python path, and that -the `settings.py`` file here will contain our customized Horizon config. - -The settings file ------------------ - -There are several key things you will generally want to customiz in your -site's settings file: specifying custom dashboards and panels, catching your -client's exception classes, and (possibly) specifying a file for advanced -overrides. - -Specifying dashboards -~~~~~~~~~~~~~~~~~~~~~ - -The most basic thing to do is to add your own custom dashboard using the -``HORIZON_CONFIG`` dictionary in the settings file:: - - HORIZON_CONFIG = { - 'dashboards': ('nova', 'syspanel', 'visualizations', 'settings',), - } - -In this case, we've taken the default Horizon ``'dashboards'`` config and -added our ``visualizations`` dashboard to it. Note that the name here is the -name of the dashboard's module on the python path. It will find our -``dashboard.py`` file inside of it and load both the dashboard and its panels -automatically from there. - -Error handling -~~~~~~~~~~~~~~ - -Adding custom error handler for your API client is quite easy. While it's not -necessary for this example, it would be done by customizing the -``'exceptions'`` value in the ``HORIZON_CONFIG`` dictionary:: - - import my_api.exceptions as my_api - - 'exceptions': {'recoverable': [my_api.Error, - my_api.ClientConnectionError], - 'not_found': [my_api.NotFound], - 'unauthorized': [my_api.NotAuthorized]}, - -.. _overrides: - -Override file -~~~~~~~~~~~~~ - -The override file is the "god-mode" dashboard editor. The hook for this file -sits right between the automatic discovery mechanisms and the final setup -routines for the entire site. By specifying an override file you can alter -any behavior you like in existing code. This tutorial won't go in-depth, -but let's just say that with great power comes great responsibility. - -To specify am override file, you set the ``'customization_module'`` value in -the ``HORIZON_CONFIG`` dictionary to the dotted python path of your -override module:: - - HORIZON_CONFIG = { - 'customization_module': 'demo_dashboard.overrides' - } - -This file is capable of adding dashboards, adding panels to existing -dashboards, renaming existing dashboards and panels (or altering other -attributes on them), removing panels from existing dashboards, and so on. - -We could say more, but it only gets more dangerous... - -Conclusion -========== - -Sadly, the cake was a lie. The information in this "tutorial" was never -meant to leave you with a working dashboard. It's close. But there's -waaaaaay too much javascript involved in the visualization to cover it all -here, and it'd be irrelevant to Horizon anyway. - -If you want to see the finished product, check out the github example -referenced at the beginning of this tutorial. - -Clone the repository and simply run ``./run_tests.sh --runserver``. That'll -give you a 100% working dashboard that uses every technique in this tutorial. - -What you've learned here, however, is the fundamentals of almost everything -you need to know to start writing interfaces for your own project based on the -components Horizon provides. - -If you have questions, or feedback on how this tutorial could be improved, -please feel free to pass them along! diff --git a/run_tests.sh b/run_tests.sh index 2995c21d..103a5654 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -141,7 +141,7 @@ function run_pep8 { function run_sphinx { echo "Building sphinx..." export DJANGO_SETTINGS_MODULE=openstack_dashboard.settings - ${command_wrapper} sphinx-build -b html docs/source docs/build/html + ${command_wrapper} sphinx-build -b html doc/source doc/build/html echo "Build complete." } diff --git a/setup.cfg b/setup.cfg index c4c71f1e..79034b3c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,3 +1,8 @@ +[build_sphinx] +all_files = 1 +build-dir = doc/build +source-dir = doc/source + [nosetests] verbosity=2 detailed-errors=1 -- cgit v1.2.1