Reorganise the tuskar files

The dashboards/infrastructure were moved to tuskar_ui, so were the
Tuskar API helpers. Non-tuskar files were removed.

The Infrastructure dashboard will become a Django Application available
in the tuskar_ui package.

Change-Id: I49c5d46cc16696c4c719ee2c6d65c42b03183ba9
Signed-off-by: Tomas Sedovic <tsedovic@redhat.com>
This commit is contained in:
Tomas Sedovic 2013-08-07 15:20:53 +02:00
parent 8ca06d8ce6
commit dbfa71f490
1100 changed files with 151 additions and 184585 deletions

View File

@ -1,153 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
clean:
-rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Horizon.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Horizon.qhc"
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/Horizon"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Horizon"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."

View File

@ -1,426 +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',
'oslo.sphinx',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Horizon'
copyright = u'2012, OpenStack, 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.version_info.version_string()
# The full version, including alpha/beta/rc tags.
release = horizon.version.version_info.release_string()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['**/#*', '**~', '**/#*#']
# The reST default role (used for this markup: `text`)
# to use for all documents.
#default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
primary_domain = 'py'
nitpicky = False
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# html_theme_path = ['.']
# html_theme = '_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
"nosidebar": "false"
}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
html_last_updated_fmt = os.popen(git_cmd).read()
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Horizondoc'
# -- Options for LaTeX output -------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index', 'Horizon.tex', u'Horizon Documentation',
u'OpenStack, 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 an ISBN number
# or the project homepage.
#epub_identifier = ''
# A unique identification for the text.
#epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
#epub_cover = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
#epub_post_files = []
# A list of files that should not be packed into the epub file.
#epub_exclude_files = []
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
# Allow duplicate toc entries.
#epub_tocdup = True
# 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)}

View File

@ -1,203 +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.
#. 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.

View File

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

View File

@ -1,24 +0,0 @@
========
Glossary
========
Horizon
The OpenStack dashboard project. Also the name of the top-level
Python object which handles registration for the app.
Dashboard
A Python class representing a top-level navigation item (e.g. "project")
which provides a consistent API for Horizon-compatible applications.
Panel
A Python class representing a sub-navigation item (e.g. "instances")
which contains all the necessary logic (views, forms, tests, etc.) for
that interface.
Project
Used in user-facing text in place of the term "Tenant" which is Keystone's
word.

View File

@ -1,127 +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
<https://github.com/openstack/horizon>`_, which provides a web based user
interface to OpenStack services including Nova, Swift, Keystone, etc.
For a more in-depth look at Horizon and its architecture, see the
:doc:`Introduction to Horizon <intro>`.
To learn what you need to know to get going, see the :doc:`quickstart`.
Getting Started With Horizon
============================
How to use Horizon in your own projects.
.. toctree::
:maxdepth: 1
intro
quickstart
topics/tutorial
topics/deployment
topics/settings
topics/customizing
Developer Docs
==============
For those wishing to develop Horizon itself, or go in-depth with building
your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes,
the following documentation is provided.
General information
-------------------
Brief guides to areas of interest and importance when developing Horizon.
.. toctree::
:maxdepth: 1
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/forms
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`

View File

@ -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 <quickstart>`
A short guide to getting started with using Horizon.
:doc:`Frequently Asked Questions <faq>`
Common questions and answers.
:doc:`Glossary <glossary>`
Common terms and their definitions.

View File

@ -1,214 +0,0 @@
==================
Horizon Quickstart
==================
Setup
=====
To setup an Horizon development environment simply clone the Horizon git
repository from http://github.com/openstack/horizon and execute the
``run_tests.sh`` script from the root folder (see :doc:`ref/run_tests`)::
> git clone https://github.com/openstack/horizon.git
> cd horizon
> ./run_tests.sh
Next you will need to setup your Django application config by copying ``openstack_dashboard/local/local_settings.py.example`` to ``openstack_dashboard/local_settings.py``. To do this quickly you can use the following command::
> cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
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 ``openstack_dashboard/local/local_settings.py`` file, 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
.. note::
If you would like to run commands without the prefix of ``tools/with_venv.sh`` you may source your environment directly. This will remain active as long as your shell session stays open::
> source .venv/bin/activate
Once the Horizon server is running point a web browser to http://localhost:8000
or to the IP and port the server is listening for.
.. note::
The ``DevStack`` project (http://devstack.org/) can be used to install
an OpenStack development environment from scratch.
.. note::
The minimum required set of OpenStack services running includes the
following:
* Nova (compute, api, scheduler, and network)
* 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 ``requirements.txt``.
.. important::
If you do anything which changes the environment (adding new dependencies
or renaming directories are both great examples) be sure to increment the
``environment_version`` counter in :doc:`run_tests.sh <ref/run_tests>`.
Project
=======
INSTALLED_APPS
--------------
At the project level you add Horizon and any desired dashboards to your
``settings.INSTALLED_APPS``::
INSTALLED_APPS = (
'openstack_dashboard',
...
'horizon',
'openstack_dashboard.dashboards.project',
'openstack_dashboard.dashboards.admin',
'openstack_dashboard.dashboards.settings',
...
)
URLs
----
Then you add a single line to your project's ``urls.py``::
url(r'', include(horizon.urls)),
Those urls are automatically constructed based on the registered Horizon apps.
If a different URL structure is desired it can be constructed by hand.
Templates
---------
Pre-built template tags generate navigation. In your ``nav.html``
template you might have the following::
{% load horizon %}
<div class='nav'>
{% horizon_main_nav %}
</div>
And in your ``sidebar.html`` you might have::
{% load horizon %}
<div class='sidebar'>
{% horizon_dashboard_nav %}
</div>
These template tags are aware of the current "active" dashboard and panel
via template context variables and will render accordingly.
Application
===========
Structure
---------
An application would have the following structure (we'll use syspanel as
an example)::
project/
|---__init__.py
|---dashboard.py <-----Registers the app with Horizon and sets dashboard properties
|---overview/
|---images_and_snapshots/
|-- images
|-- __init__.py
|---panel.py <-----Registers the panel in the app and defines panel properties
|-- snapshots/
|-- templates/
|-- tests.py
|-- urls.py
|-- views.py
...
...
Dashboard Classes
-----------------
Inside of ``dashboard.py`` you would have a class definition and the registration
process::
import horizon
....
# ObjectStorePanels is an example for a PanelGroup
# for panel classes in general, see below
class ObjectStorePanels(horizon.PanelGroup):
slug = "object_store"
name = _("Object Store")
panels = ('containers',)
class Project(horizon.Dashboard):
name = _("Project") # Appears in navigation
slug = "project" # Appears in URL
# panels may be strings or refer to classes, such as
# ObjectStorePanels
panels = (BasePanels, NetworkPanels, ObjectStorePanels)
default_panel = 'overview'
supports_tenants = True
...
horizon.register(Project)
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 openstack_dashboard.dashboards.project import dashboard
class Images(horizon.Panel):
name = "Images"
slug = 'images'
permissions = ('openstack.roles.admin', 'my.other.permission',)
# You could also register your panel with another application's dashboard
dashboard.Project.register(Images)
By default a :class:`~horizon.Panel` class looks for a ``urls.py`` file in the
same directory as ``panel.py`` to include in the rollup of url patterns from
panels to dashboards to Horizon, resulting in a wholly extensible, configurable
URL structure.

View File

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

View File

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

View File

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

View File

@ -1,98 +0,0 @@
=============
Horizon Forms
=============
Horizon ships with some very useful base form classes, form fields,
class-based views, and javascript helpers which streamline most of the common
tasks related to form handling.
Form Classes
============
.. automodule:: horizon.forms.base
:members:
Form Fields
===========
.. automodule:: horizon.forms.fields
:members:
Form Views
==========
.. automodule:: horizon.forms.views
:members:
Forms Javascript
================
Switchable Fields
-----------------
By marking fields with the ``"switchable"`` and ``"switched"`` classes along
with defining a few data attributes you can programmatically hide, show,
and rename fields in a form.
The triggers are fields using a ``select`` input widget, marked with the
"switchable" class, and defining a "data-slug" attribute. When they are changed,
any input with the ``"switched"`` class and defining a ``"data-switch-on"``
attribute which matches the ``select`` input's ``"data-slug"`` attribute will be
evaluated for necessary changes. In simpler terms, if the ``"switched"`` target
input's ``"switch-on"`` matches the ``"slug"`` of the ``"switchable"`` trigger
input, it gets switched. Simple, right?
The ``"switched"`` inputs also need to define states. For each state in which
the input should be shown, it should define a data attribute like the
following: ``data-<slug>-<value>="<desired label>"``. When the switch event
happens the value of the ``"switchable"`` field will be compared to the
data attributes and the correct label will be applied to the field. If
a corresponding label for that value is *not* found, the field will
be hidden instead.
A simplified example is as follows::
source = forms.ChoiceField(
label=_('Source'),
choices=[
('cidr', _('CIDR')),
('sg', _('Security Group'))
],
widget=forms.Select(attrs={
'class': 'switchable',
'data-slug': 'source'
})
)
cidr = fields.IPField(
label=_("CIDR"),
required=False,
widget=forms.TextInput(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-cidr': _('CIDR')
})
)
security_group = forms.ChoiceField(
label=_('Security Group'),
required=False,
widget=forms.Select(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-sg': _('Security Group')
})
)
That code would create the ``"switchable"`` control field ``source``, and the
two ``"switched"`` fields ``cidr`` and ``security group`` which are hidden or
shown depending on the value of ``source``.
NOTE: A field can only safely define one slug in its ``"switch-on"`` attribute.
While switching on multiple fields is possible, the behavior is very hard to
predict due to the events being fired from the various switchable fields in
order. You generally end up just having it hidden most of the time by accident,
so it's not recommended. Instead just add a second field to the form and control
the two independently, then merge their results in the form's clean or handle
methods at the end.

View File

@ -1,45 +0,0 @@
======================
The ``horizon`` Module
======================
.. module:: horizon
Horizon ships with a single point of contact for hooking into your project if
you aren't developing your own :class:`~horizon.Dashboard` or
:class:`~horizon.Panel`::
import horizon
From there you can access all the key methods you need.
Horizon
=======
.. attribute:: urls
The auto-generated URLconf for Horizon. Usage::
url(r'', include(horizon.urls)),
.. autofunction:: register
.. autofunction:: unregister
.. autofunction:: get_absolute_url
.. autofunction:: get_user_home
.. autofunction:: get_dashboard
.. autofunction:: get_default_dashboard
.. autofunction:: get_dashboards
Dashboard
=========
.. autoclass:: Dashboard
:members:
Panel
=====
.. autoclass:: Panel
:members:
.. autoclass:: PanelGroup
:members:

View File

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

View File

@ -1,233 +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 more thorough test run you can include the
Selenium tests by using the ``--with-selenium`` flag::
./run_tests.sh --with-selenium
If you run horizon in a minimal installation VM, you will probably need
the following (steps for Fedora 18 minimal installation):
#. Install these packages in the VM:
``yum install xorg-x11-xauth xorg-x11-fonts-Type1.noarch``.
#. Install firefox in the VM:
``yum install firefox``.
#. Connect to the VM by ``ssh -X``
(if you run ``set|grep DISP``, you should see that the DISPLAY is set).
#. Run
``./run_tests.sh --with-selenium``.
Running a subset of tests
-------------------------
Instead of running all tests, you can specify an individual directory, file,
class, or method that contains test code.
To run the tests in the ``horizon/test/tests/tables.py`` file::
./run_tests.sh horizon.test.tests.tables
To run the tests in the `WorkflowsTests` class in
``horizon/test/tests/workflows``::
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests
To run just the `WorkflowsTests.test_workflow_view` test method::
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests.test_workflow_view
Using Dashboard and Panel Templates
===================================
Horizon has a set of convenient management commands for creating new
dashboards and panels based on basic templates.
Dashboards
----------
To create a new dashboard, run the following:
./run_tests.sh -m startdash <dash_name>
This will create a directory with the given dashboard name, a ``dashboard.py``
module with the basic dashboard code filled in, and various other common
"boilerplate" code.
Available options:
* --target: the directory in which the dashboard files should be created.
Default: A new directory within the current directory.
Panels
------
To create a new panel, run the following:
./run_tests -m startpanel <panel_name> --dashboard=<dashboard_path>
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.

View File

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

View File

@ -1,45 +0,0 @@
==========================
Horizon Tabs and TabGroups
==========================
.. module:: horizon.tabs
Horizon includes a set of reusable components for programmatically
building tabbed interfaces with fancy features like dynamic AJAX loading
and nearly effortless templating and styling.
Tab Groups
==========
For any tabbed interface, your fundamental element is the tab group which
contains all your tabs. This class provides a dead-simple API for building
tab groups and encapsulates all the necessary logic behind the scenes.
.. autoclass:: TabGroup
:members:
Tabs
====
The tab itself is the discrete unit for a tab group, representing one
view of data.
.. autoclass:: Tab
:members:
.. autoclass:: TableTab
:members:
TabView
=======
There is also a useful and simple generic class-based view for handling
the display of a :class:`~horizon.tabs.TabGroup` class.
.. autoclass:: TabView
:members:
.. autoclass:: TabbedTableView
:members:

View File

@ -1,25 +0,0 @@
========================
Horizon TestCase Classes
========================
.. module:: horizon.test.helpers
Horizon provides a base test case class which provides several useful
pre-prepared attributes for testing Horizon components.
.. autoclass:: TestCase
:members:
.. module :: openstack_dashboard.test.helpers
The OpenStack Dashboard also provides test case classes for greater
ease-of-use when testing APIs and OpenStack-specific auth scenarios.
.. autoclass:: TestCase
:members:
.. autoclass:: APITestCase
:members:
.. autoclass:: BaseAdminViewTests
:members:

View File

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

View File

@ -1,148 +0,0 @@
======================
Horizon 2012.1 "Essex"
======================
Release Overview
================
During the Essex release cycle, Horizon underwent a significant set of internal
changes to allow extensibility and customization while also adding a significant
number of new features and bringing much greater stability to every interaction
with the underlying components.
Highlights
==========
Extensibility
-------------
Making Horizon extensible for third-party developers was one of the core
goals for the Essex release cycle. Massive strides have been made to allow
for the addition of new "plug-in" components and customization of OpenStack
Dashboard deployments.
To support this 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.

View File

@ -1,159 +0,0 @@
=======================
Horizon 2012.2 "Folsom"
=======================
Release Overview
================
The Folsom release cycle brought several major advances to Horizon's user
experience while also reintroducing Quantum networking as a core piece
of the OpenStack Dashboard.
Highlights
==========
Networking (Quantum)
--------------------
With Quantum being a core project for the Folsom release, we worked closely
with the Quantum team to bring networking support back into Horizon. This
appears in two primary places: the Networks panel in both the Project and
Admin dashboards, and the Network tab in the Launch Instance workflow. Expect
further improvements in these areas as Quantum continues to mature and more
users adopt this model of virtual network management.
User Experience
---------------
Workflows
~~~~~~~~~
By far the biggest UI/UX change in the Folsom release is the introduction of
programmatic workflows. These components allow developers to create concise
interactions that combine discrete tasks spanning multiple services and
resources in a user-friendly way and with minimal boilerplate code. Within
a workflow, related objects can also be dynamically created so users don't lose
their place when they realize the item they wanted isn't currently available.
Look for examples of these workflows in Launch Instance, Associate Floating IP,
and Create/Edit Project.
Resource Browser
~~~~~~~~~~~~~~~~
Another cool new component is an interface designed for "browsing" resources
which are nested under a parent resource. The object store (Swift) is a prime
example of this. Now there is a consistent top-level navigation for containers
on the left-hand pane of the "browser" while the right-hand pane lets you
explore within those containers and sub-folders.
User Experience Improvements
----------------------------
* Timezone support is now enabled. You can select your preferred timezone
in the User Settings panel.
Community
---------
* Third-party developers who wish to build on Horizon can get started much
faster using the new dashboard and panel templates. See the docs on
`creating a dashboard`_ and `creating a panel`_ for more information.
* A `thorough set of documentation`_ for developers on how to go about
internationalizing, localizing and translating OpenStack projects
is now available.
.. _creating a dashboard: http://docs.openstack.org/developer/horizon/topics/tutorial.html#creating-a-dashboard
.. _creating a panel: http://docs.openstack.org/developer/horizon/topics/tutorial.html#creating-a-panel
.. _thorough set of documentation: http://wiki.openstack.org/Translations
Under The Hood
--------------
* The python-swiftclient library and python-cinderclient libraries are now
used under the hood instead of cloudfiles and python-novaclient respectively.
* Internationalization of client-side JavaScript is now possible in addition
to server-side Python code.
* Keystone authentication is now handled by a proper pluggable Django
authentication backend, offering significantly better and more reliable
security for Horizon.
Other Improvements and Fixes
----------------------------
Some of the general areas of improvement include:
* Images can now be added to Glance by providing a URL for Glance to download
the image data from.
* Quotas are now displayed dynamically throughout the Project dashboard.
* API endpoints are now displayed on the OpenStack RC File panel so they
can be organically discovered by an end-user.
* DataTables now support a summation row at the bottom of the table.
* Better cross-browser support (Safari and IE particularly).
* Fewer API calls to OpenStack endpoints (improves performance).
* Better validation of what actions are permitted when.
* Improved error handling and error messages.
Known Issues and Limitations
============================
Floating IPs and Quantum
------------------------
Due to the very late addition of floating IP support in Quantum, Nova's
integration there is lacking, so floating IP-related API calls to Nova will
fail when your OpenStack deployment uses Quantum for networking. This means
that Horizon actions such as "allocate" and "associate" floating IPs will
not work either since they rely on the underlying APIs.
Pagination
----------
A number of the "index" pages don't fully work with API pagination yet,
causing them to only display the first chunk of results returned by the API.
This number is often 1000 (as in the case of novaclient results), but does vary
somewhat.
Deleting large numbers of resources simultaneously
--------------------------------------------------
Using the "select all" checkbox to delete large numbers of resources via the
API can cause network timeouts (depending on configuration). This is
due to the APIs not supporting bulk-deletion natively, and consequently Horizon
has to send requests to delete each resource individually behind the scenes.
Backwards Compatibility
=======================
The Folsom Horizon release should be fully-compatible with both Folsom and
Essex versions of the rest of the OpenStack core projects (Nova, Swift, etc.).
While some features work significantly better with an all-Folsom stack due
to bugfixes, etc. in underlying services, there should not be any limitations
on what will or will not function. (Note: Quantum was not a core OpenStack
project in Essex, and thus this statement does not apply to network management.)
In terms of APIs provided for extending Horizon, there are a handful of
backwards-incompatible changes that were made:
* The ``can_haz`` and ``can_haz_list`` template filters have been renamed
to ``has_permissions`` and ``has_permissions_on_list`` respectively.
* The dashboard-specific ``base.html`` templates (e.g. ``nova/base.html``,
``syspanel/base.html``, etc.) have been removed in favor of a single
``base.html`` template.
* In conjunction with the previous item, the dashboard-specific template blocks
(e.g. ``nova_main``, ``syspanel_main``, etc.) have been removed in favor of
a single ``main`` template block.
Overall, though, great effort has been made to maintain compatibility for
third-party developers who may have built on Horizon so far.

View File

@ -1,274 +0,0 @@
========================
Horizon 2013.1 "Grizzly"
========================
Release Overview
================
The Grizzly release cycle saw sweeping improvements to overall user experience,
huge stability improvements, lots of new networking, instance management and
image management features, a long-needed architectural clarification, and big
increases in community engagement! Read on to get the specifics.
Highlights
==========
New Features
------------
Networking
~~~~~~~~~~
Quantum added a huge number of new features in Grizzly, including L3 support
(routers), load balancers, network topology infographics, better compatibility
with Nova networking APIs (VNIC ordering when launching an instance; security
groups and floating IP integration) and vastly improved informational displays.
Direct Image Upload To Glance
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
It is now possible (though there are numerous deployment/security implications)
to upload an image file directly from a user's hard disk to Glance through
Horizon. For multi-GB images it is still strongly recommended that the upload
be done using the Glance CLI. Further improvements to this feature will come in
future releases.
Flavor Extra Specs Support
~~~~~~~~~~~~~~~~~~~~~~~~~~
In Folsom, Nova added support for "extra specs" on flavors--additional metadata
which custom schedulers could use for appropriately scheduling instances. As of
the Grizzly release, Horizon now supports reading and writing that data on any
flavor.
Migrate Instance
~~~~~~~~~~~~~~~~
Administrators now have the ability to migrate an instance off of its current
host via the Admin dashboard's Instances panel.
User Experience Improvements
----------------------------
"Not Authorized" & Being Logged Out
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A shocking number of the problems first-time deployers of OpenStack have can be
summarized as "I thought I set everything up, then I tried to log into the
dashboard and I was immediately logged back out." The root cause of this was
that in an effort to be as secure as possible any 401 or 403 response from
any service API was being treated the same as if it was an attempt to access
an unauthorized portion of Horizon, and the user was summarily logged out with
little to no information as to why.
In Grizzly we have instead chosen to improve this by treating service API
401 and 403 errors as slightly less severe than unauthorized access attempts
to resitricted areas of Horizon. The reason for this is threefold:
#. For a non-malicious user these errors are almost 100% the result of
misconfiguration and this makes debugging possible.
#. A malicious user can make the exact same "unauthorized" requests via the
CLI as they can via the dashboard; no special privileges are granted.
#. API errors are generated by external systems not under the purview of our
project and while we should attempt to respect and take appropriate action
on those errors, we should not do anything drastic or even potentially
destructive because of them.
Going forward the user will not be logged out, but no information will be
populated on the page and they will be presented with error messages informing
them that they are unauthorized for the data they attempted to access.
Reorganizations
~~~~~~~~~~~~~~~
A couple of long-standing user confusions were fixed in Grizzly.
First off, the API Access panel (containing a user's API endpoints, rc files,
and EC2 credentials) was moved from Settings to the Access & Security section
of the Project dashboard.
Second, the Default Quotas and Services panels (which were both strictly
informational) were combined into tabs in a single System Info panel to make
it clear that these panels are thematically related, and to create a home for
informational-only displays like these.
One-click Floating IP Management
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A common complaint from users was that associating a floating IP to an
instance involved numerous clicks and form selections for something that
the majority of users had no knowledge of and didn't care about. As such, a
one-click "simple" floating IP association option has been created. For
deployments which only have a single floating IP pool, this allows users to
ignore explicit floating IP management and just click a button to associate
or disassociate a floating IP with an instance.
Organized Images
~~~~~~~~~~~~~~~~
The Images table now has a new feature: predefined filters for seeing your own
images, images that have been shared with you, or public images. This makes
finding the image you're looking for a great deal easier and more pleasant.
Security Group Rule Editing Improvements
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The security group rule editing experience has always been inherently very
complicated simply given the number of options and the very technical terms
involved. Moreover, the combined table-plus-form approach the OpenStack
Dashboard had taken only made the UX more frustrating for an already difficult
area.
In Grizzly this has all been reworked to be signficantly simpler, and to
provide as much contextual help and streamlining as possible.
Icons!
~~~~~~
In an effort to make the dashboard more at-a-glance usable, we've added icons
to most of the common action buttons throughout the dashboard.
"More Actions", More Better
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Lots of feedback came in that the "more actions" dropdown menu (for tables with
numerous actions available on each row) was confusing to new users and/or
difficult to click.
We've now improved it so that the button to open the menu is clearly labeled
and the hitbox for clicking it is significantly larger.
Community
---------
Docs, docs, and more docs!
~~~~~~~~~~~~~~~~~~~~~~~~~~
Large amounts of new documentation was added during the Grizzly cycle, most
notably are sections documenting: all of the available settings for Horizon and
the OpenStack Dashboard; security and deployment considerations; and deeper
guides on customizing the OpenStack Dashboard.
IRC Meeting
~~~~~~~~~~~
During the Grizzly cycle we started holding a weekly project meeting on IRC.
This has been extremely beneficial for the growth and progress of the project.
Check out the `OpenStack Meetings wiki page`_ for specifics.
.. _Openstack Meetings wiki page: https://wiki.openstack.org/wiki/Meetings#Horizon_team_meeting
Under The Hood
--------------
Legacy Dashboard Names & Code Separation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Very early in the Grizzly cycle we took the opportunity to do some longstanding
cleanup and refactoring work. The "nova" dashboard was renamed to "project" and
the "syspanel" dashboard was renamed to "admin" to better reflect their
respective purposes.
Moreover, a better separation was created between code related to the core
Horizon framework code (which is not related to OpenStack specifically) and
the OpenStack Dashboard code. At this point *all* code related to OpenStack
lives in the OpenStack Dashboard directory, while the Horizon framework is
completely agnostic and is a reusable Django app.
Object Storage Delimiters and Pseudo-folder Objects
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When Horizon's object storage interface was first added, Swift's documentation
recommended adding 0-byte objects with a special content type to denote
pseudo-folders within a container. They have since decided that this is not the
recommended practice, and that pseudo-folders should only be demarcated by
a delimiting character (usually "/") in the object name.
Horizon has been updated under the hood to use this method, which should bring
it better into line with how most deployments are using their object storage.
Other Improvements and Fixes
----------------------------
* Support for Keystone's PKI tokens.
* Flavor editing was made significantly more stable.
* Security groups can be added to a running instance.
* Volume quotas are handled by the appopriate service depending on whether
or not Cinder is enabled.
* Password confirmation boxes are now validated for matching passwords on
the client side for more immediate feedback.
* Numerous fixes to display more and better information for instances and
volumes in their overview pages.
* Improved unicode support for the Object Storage panels.
* Logout now attempts to delete the token(s) associated with the current
session to avoid replay attacks, etc.
* Various fixes for browser compatibility and rendering.
* Many, many other bugfixes and improvements. Check out Launchpad for the full
list of what went on in Grizzly.
Known Issues and Limitations
============================
Editing a Flavor Which Results In An API Error Will Delete The Flavor
---------------------------------------------------------------------
Due to the way that Nova handles flavor editing/replacement it is necessary
to delete the old flavor before creating the replacement flavor. As such,
if an API error occurs while creating the replacement it is possible to
lose the old flavor without the new one being created.
Creating Rich Network Topologies
--------------------------------
Due to several Quantum features landing very late in the Grizzly cycle, it
is not possible to create particularly complex networking configurations
through the OpenStack Dashboard. These features will continue to grow
throughout future releases.
Loadbalancer Feature
--------------------
The Loadbalancer feature landed in the 11th hour for both Quantum and Horizon
and, though we did our best to test it, may still contain undiscovered bugs. It
is best considered a "beta" or "experimental" feature for the Grizzly release.
Quantum Brocade Plugin Not Compatible
-------------------------------------
The Brocade plugin for Quantum does not support key features of the floating
IP addresses API which are considered central to Horizon's functionality. As
such, it is not compatible with the Grizzly release's Quantum integration.
Deleting large numbers of resources simultaneously
--------------------------------------------------
Using the "select all" checkbox to delete large numbers of resources via the
API can cause network timeouts (depending on configuration). This is
due to the APIs not supporting bulk-deletion natively, and consequently Horizon
has to send requests to delete each resource individually behind the scenes.
Backwards Compatibility
=======================
The Grizzly Horizon release should be fully compatible with both Grizzly and
Folsom versions of the rest of the OpenStack core projects (Nova, Swift, etc.).
While some features work significantly better with an all-Grizzly stack due
to bugfixes, etc. in underlying services, there should not be limitations
on what will or will not function.
Overall, great effort has been made to maintain compatibility for
third-party developers who may have built on Horizon so far.

View File

@ -1,41 +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
It's also possible to :doc:`run a subset of unit tests<ref/run_tests>`.
.. seealso::
:doc:`ref/run_tests`
Full reference for the ``run_tests.sh`` script.
Writing tests
=============
Horizon uses Django's unit test machinery (which extends Python's ``unittest2``
library) as the core of its test suite. As such, all tests for the Python code
should be written as unit tests. No doctests please.
In general new code without unit tests will not be accepted, and every bugfix
*must* include a regression test.
For a much more in-depth discussion of testing, see the :doc:`testing topic
guide </topics/testing>`.

View File

@ -1,137 +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"
}
You can do essentially anything you like in the customization module. For
example, you could change the name of a panel::
from django.utils.translation import ugettext_lazy as _
import horizon
# Rename "User Settings" to "User Options"
settings = horizon.get_dashboard("settings")
user_panel = settings.get_panel("user")
user_panel.name = _("User Options")
Or get the instances panel::
projects_dashboard = horizon.get_dashboard("project")
instances_panel = projects_dashboard.get_panel("instances")
And limit access to users with the Keystone Admin role::
permissions = list(getattr(instances_panel, 'permissions', []))
permissions.append('openstack.roles.admin')
instances_panel.permissions = tuple(permissions)
Or just remove it entirely::
projects_dashboard.unregister(instances_panel.__class__)
.. NOTE::
``my_project.overrides`` needs to be importable by the python process running
Horizon.
If your module is not installed as a system-wide python package,
you can either make it installable (e.g., with a setup.py)
or you can adjust the python path used by your WSGI server to include its location.
Probably the easiest way is to add a ``python-path`` argument to
the ``WSGIDaemonProcess`` line in Apache's Horizon config.
Assuming your ``my_project`` module lives in ``/opt/python/my_project``,
you'd make it look like the following::
WSGIDaemonProcess [... existing options ...] python-path=/opt/python
Button Icons
============
Horizon 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.

View File

@ -1,217 +0,0 @@
=================
Deploying Horizon
=================
This guide aims to cover some common questions, concerns and pitfalls you
may encounter when deploying Horizon in a production environment.
.. seealso:: :doc:`settings`
.. note::
The Service Catalog returned by the Identity Service after a user
has successfully authenticated determines the dashboards and panels
that will be available within the OpenStack Dashboard. If you are not
seeing a particular service you expected (e.g. Object Storage/Swift or
Networking/Neutron) make sure your Service Catalog is configured correctly.
Prior to the Essex release of Horizon these features were controlled by
individual settings in the ``local_settings.py`` file. This code has been
long-since removed and those pre-Essex settings have no impact now.
Configure Your Identity Service Host
====================================
The one thing you *must* do in order to run Horizon is to specify the
host for your OpenStack Identity Service endpoint. To do this, set the value
of the ``OPENSTACK_HOST`` settings in your ``local_settings.py`` file.
Logging
=======
Logging is an important concern for production deployments, and the intricacies
of good logging configuration go far beyond what can be covered here. However
there are a few points worth noting about the logging included with Horizon,
how to customize it, and where other components may take over:
* Horizon's logging uses Django's logging configuration mechanism, which
can be customized in your ``local_settings.py`` file through the
``LOGGING`` dictionary.
* Horizon's default logging example sets the log level to ``"INFO"``, which is
a reasonable choice for production deployments. For development, however,
you may want to change the log level to ``"DEBUG"``.
* Horizon also uses a number of 3rd-party clients which log separately. The
log level for these can still be controlled through Horizon's ``LOGGING``
config, however behaviors may vary beyond Horizon's control.
* For more information regarding configuring logging in Horizon, please
read the `Django logging directive`_ and the `Python logging directive`_
documentation. Horizon is built on Python and Django.
.. _Django logging directive: https://docs.djangoproject.com/en/1.5/topics/logging
.. _Python logging directive: http://docs.python.org/2/library/logging.html
.. warning::
At this time there is `a known bug in python-keystoneclient`_ where it will
log the complete request body of any request sent to Keystone through it
(including logging passwords in plain text) when the log level is set to
``"DEBUG"``. If this behavior is not desired, make sure your log level is
``"INFO"`` or higher.
.. _a known bug in python-keystoneclient: https://bugs.launchpad.net/keystone/+bug/1004114
File Uploads
============
Horizon allows users to upload files via their web browser to other OpenStack
services such as Glance and Swift. Files uploaded through this mechanism are
first stored on the Horizon server before being forwarded on - files are not
uploaded directly or streamed as Horizon receives them. As Horizon itself does
not impose any restrictions on the size of file uploads, production deployments
will want to consider configuring their server hosting the Horizon application
to enforce such a limit to prevent large uploads exhausting system resources
and disrupting services. Deployments using Apache2 can use the
`LimitRequestBody directive`_ to achieve this.
Uploads to the Glance image store service tend to be particularly large - in
the order of hundreds of megabytes to multiple gigabytes. Deployments are able
to disable the ability to upload images through Horizon by setting
``HORIZON_IMAGES_ALLOW_UPLOAD`` to ``False`` in your ``local_settings.py``
file.
.. _LimitRequestBody directive: http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestbody
Session Storage
===============
Horizon uses `Django's sessions framework`_ for handling user session data;
however that's not the end of the story. There are numerous session backends
available, which are controlled through the ``SESSION_ENGINE`` setting in
your ``local_settings.py`` file. What follows is a quick discussion of the
pros and cons of each of the common options as they pertain to deploying
Horizon specifically.
.. _Django's sessions framework: https://docs.djangoproject.com/en/dev/topics/http/sessions/
Local Memory Cache
------------------
Enabled by::
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
CACHES = {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
}
Local memory storage is the quickest and easiest session backend to set up,
as it has no external dependencies whatsoever. However, it has two significant
drawbacks:
* No shared storage across processes or workers.
* No persistence after a process terminates.
The local memory backend is enabled as the default for Horizon solely because
it has no dependencies. It is not recommended for production use, or even for
serious development work. For better options, read on.
Memcached
---------
Enabled by::
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
CACHES = {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache'
'LOCATION': 'my_memcached_host:11211',
}
External caching using an application such as memcached offers persistence
and shared storage, and can be very useful for small-scale deployment and/or
development. However, for distributed and high-availability scenarios
memcached has inherent problems which are beyond the scope of this
documentation.
Memcached is an extremely fast and efficient cache backend for cases where it
fits the deployment need. But it's not appropriate for all scenarios.
Requirements:
* Memcached service running and accessible.
* Python memcached module installed.
Database
--------
Enabled by::
SESSION_ENGINE = 'django.core.cache.backends.db.DatabaseCache'
DATABASES = {
'default': {
# 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
Secure Site Recommendations
---------------------------
When implementing Horizon for public usage, with the website served through
HTTPS, it is recommended that the following settings are applied.
To help protect the session cookies from `cross-site scripting`_, add the
following to ``local_settings.py`` :
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
Note that the CSRF_COOKIE_SECURE option is only available from Django 1.4. It
does no harm to have the setting in earlier versions, but it does not take effect.
You can also disable `browser autocompletion`_ for the authentication form by
changing the ``password_autocomplete`` attribute to ``off`` in ``horizon/conf/default.py``
.. _cross-site scripting: https://www.owasp.org/index.php/HttpOnly
.. _browser autocompletion: https://wiki.mozilla.org/The_autocomplete_attribute_and_web_documents_using_XHTML

View File

@ -1,294 +0,0 @@
==================================
Horizon Settings and Configuration
==================================
Introduction
============
Horizon's settings tend to fall into three categories:
* Horizon configuration options (contained in the ``HORIZON_CONFIG`` dict)
which are not OpenStack-specific and pertain only to the core framework.
* OpenStack-related settings which pertain to other projects/services and
are generally prefixed with ``OPENSTACK_`` in the settings file.
* Django settings (including common plugins like ``django-compressor``) which
can be (and should be) read about in their respective documentation.
What follows is an overview of the Horizon and OpenStack-specific settings
and a few notes on the Django-related settings.
.. note::
Prior to the Essex release of Horizon there were settings which controlled
whether features such as Object Storage/Swift or Networking/Neutron would be
enabled in the OpenStack Dashboard. This code has beenlong-since removed and
those pre-Essex settings have no impact now.
In Essex and later, the Service Catalog returned by the Identity Service
after a user has successfully authenticated determines the dashboards and
panels that will be available within the OpenStack Dashboard. If you are not
seeing a particular service you expected make sure your Service Catalog is
configured correctly.
Horizon Settings
================
The following options are available in order to configure/customize the
behavior of your Horizon installation. All of them are contained in the
``HORIZON_CONFIG`` dictionary.
``dashboards``
--------------
Default: ``None``
A list containing the slugs of any dashboards which should be active in this
Horizon installation. The dashboards listed must be in a Python module which
is included in the ``INSTALLED_APPS`` list and on the Python path.
``default_dashboard``
---------------------
Default: ``None``
The slug of the dashboard which should act as the first-run/fallback dashboard
whenever a user logs in or is otherwise redirected to an ambiguous location.
``user_home``
-------------
Default: ``settings.LOGIN_REDIRECT_URL``
This can be either a literal URL path (such as the default), or Python's
dotted string notation representing a function which will evaluate what URL
a user should be redirected to based on the attributes of that user.
``ajax_queue_limit``
--------------------
Default: ``10``
The maximum number of simultaneous AJAX connections the dashboard may try
to make. This is particularly relevant when monitoring a large number of
instances, volumes, etc. which are all actively trying to update/change state.
``ajax_poll_interval``
----------------------
Default: ``2500``
How frequently resources in transition states should be polled for updates,
expressed in milliseconds.
``help_url``
------------
Default: None
If provided, a "Help" link will be displayed in the site header which links
to the value of this settings (ideally a URL containing help information).
``exceptions``
--------------
Default: ``{'unauthorized': [], 'not_found': [], 'recoverable': []}``
A dictionary containing classes of exceptions which Horizon's centralized
exception handling should be aware of.
``password_validator``
----------------------
Default: {'regex': '.*', 'help_text': _("Password is not accepted")}
A dictionary containing a regular expression which will be used for password
validation and help text which will be displayed if the password does not
pass validation. The help text should describe the password requirements if
there are any.
This setting allows you to set rules for passwords if your organization
requires them.
``password_autocomplete``
-------------------------
Default: ``"on"``
Controls whether browser autocompletion should be enabled on the login form.
Valid values are ``"on"`` and ``"off"``.
``simple_ip_management``
------------------------
Default: ``True``
Enable or disable simplified floating IP address management.
"Simple" floating IP address management means that the user does not ever have
to select the specific IP addresses they wish to use, and the process of
allocating an IP and assigning it to an instance is one-click.
The "advanced" floating IP management allows users to select the floating IP
pool from which the IP should be allocated and to select a specific IP address
when associating one with an instance.
OpenStack Settings
==================
The following settings inform the OpenStack Dashboard of information about the
other OpenStack projects which are part of this cloud and control the behavior
of specific dashboards, panels, API calls, etc.
``OPENSTACK_HOST``
------------------
Default: ``"127.0.0.1"``
The hostname of the Keystone server used for authentication if you only have
one region. This is often the *only* settings that needs to be set for a
basic deployment.
``OPENSTACK_KEYSTONE_URL``
--------------------------
Default: ``"http://%s:5000/v2.0" % OPENSTACK_HOST``
The full URL for the Keystone endpoint used for authentication. Unless you
are using HTTPS, running your Keystone server on a nonstandard port, or using
a nonstandard URL scheme you shouldn't need to touch this setting.
``AVAILABLE_REGIONS``
---------------------
Default: ``None``
A tuple of tuples which define multiple regions. The tuple format is
``('http://{{keystone_host}}:5000/v2.0', '{{region_name}}')``. If any regions
are specified the login form will have a dropdown selector for authenticating
to the appropriate region, and there will be a region switcher dropdown in
the site header when logged in.
If you do not have multiple regions you should use the ``OPENSTACK_HOST`` and
``OPENSTACK_KEYSTONE_URL`` settings above instead.
``OPENSTACK_KEYSTONE_DEFAULT_ROLE``
-----------------------------------
Default: "Member"
The name of the role which will be assigned to a user when added to a project.
This name must correspond to a role name in Keystone.
``OPENSTACK_SSL_NO_VERIFY``
---------------------------
Default: ``False``
Disable SSL certificate checks in the OpenStack clients (useful for self-signed
certificates).
``OPENSTACK_KEYSTONE_BACKEND``
------------------------------
Default: ``{'name': 'native', 'can_edit_user': True, 'can_edit_project': True}``
A dictionary containing settings which can be used to identify the
capabilities of the auth backend for Keystone.
If Keystone has been configured to use LDAP as the auth backend then set
``can_edit_user`` and ``can_edit_project`` to ``False`` and name to ``"ldap"``.
``OPENSTACK_HYPERVISOR_FEATURES``
---------------------------------
Default: ``{'can_set_mount_point': True, 'can_encrypt_volumes': False}``
A dictionary containing settings which can be used to identify the
capabilities of the hypervisor for Nova.
Some hypervisors have the ability to set the mount point for volumes attached
to instances (KVM does not). Setting ``can_set_mount_point`` to ``False`` will
remove the option to set the mount point from the UI.
In the Havana release, there will be a feature for encrypted volumes
which will be controlled by the ``can_encrypt_volumes``. Setting it to ``True``
in the Grizzly release will have no effect.
``OPENSTACK_NEUTRON_NETWORK``
-----------------------------
Default: ``{'enable_lb': False}``
A dictionary of settings which can be used to enable optional services provided
by neutron. Currently only the load balancer service is available.
``OPENSTACK_ENDPOINT_TYPE``
---------------------------
Default: ``"internalURL"``
A string which specifies the endpoint type to use for the endpoints in the
Keystone service catalog. If Horizon is running external to the OpenStack
environment you may wish to use ``"publicURL"`` instead.
``API_RESULT_LIMIT``
--------------------
Default: ``1000``
The maximum number of objects (e.g. Swift objects or Glance images) to display
on a single page before providing a paging element (a "more" link) to paginate
results.
``API_RESULT_PAGE_SIZE``
------------------------
Default: ``20``
Similar to ``API_RESULT_LIMIT``. This setting currently only controls the
Glance image list page size. It will be removed in a future version.
Django Settings (Partial)
=========================
.. warning::
This is not meant to be anywhere near a complete list of settings for
Django. You should always consult the upstream documentation, especially
with regards to deployment considerations and security best-practices.
There are a few key settings you should be aware of for development and the
most basic of deployments. Further recommendations can be found in the
Deploying Horizon section of this documentation.
``DEBUG`` and ``TEMPLATE_DEBUG``
--------------------------------
Default: ``True``
Controls whether unhandled exceptions should generate a generic 500 response
or present the user with a pretty-formatted debug information page.
This setting should **always** be set to ``False`` for production deployments
as the debug page can display sensitive information to users and attackers
alike.
``SECRET_KEY``
--------------
This should absolutely be set to a unique (and secret) value for your
deployment. Unless you are running a load-balancer with multiple Horizon
installations behind it, each Horizon instance should have a unique secret key.
The ``local_settings.py.example`` file includes a quick-and-easy way to
generate a secret key for a single installation.
``SECURE_PROXY_SSL_HEADER``, ``CSRF_COOKIE_SECURE`` and ``SESSION_COOKIE_SECURE``
---------------------------------------------------------------------------------
These three settings should be configured if you are deploying Horizon with
SSL. The values indicated in the default ``local_settings.py.example`` file
are generally safe to use.

View File

@ -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 </ref/tables>`.
Tables
======
The majority of interface in a dashboard-style interface ends up being
tabular displays of the various resources the dashboard interacts with.
The :class:`~horizon.tables.DataTable` class exists so you don't have to
reinvent the wheel each time.
Creating your own tables
------------------------
Creating a table is fairly simple:
#. Create a subclass of :class:`~horizon.tables.DataTable`.
#. Define columns on it using :class:`~horizon.tables.Column`.
#. Create an inner ``Meta`` class to contain the special options for
this table.
#. Define any actions for the table, and add them to
:attr:`~horizon.tables.DataTableOptions.table_actions` or
:attr:`~horizon.tables.DataTableOptions.row_actions`.
Examples of this can be found in any of the ``tables.py`` modules included
in the reference modules under ``horizon.dashboards``.
Connecting a table to a view
----------------------------
Once you've got your table set up the way you like it, the next step is to
wire it up to a view. To make this as easy as possible Horizon provides the
:class:`~horizon.tables.DataTableView` class-based view which can be subclassed
to display your table with just a couple lines of code. At 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.

View File

@ -1,276 +0,0 @@
===================
Testing Topic Guide
===================
Having good tests in place is absolutely critical for ensuring a stable,
maintainable codebase. Hopefully that doesn't need any more explanation.
However, what defines a "good" test is not always obvious, and there are
a lot of common pitfalls that can easily shoot your test suite in the
foot.
If you already know everything about testing but are fed up with trying to
debug why a specific test failed, you can skip the intro and jump
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.helpers.TestCase.assertNoFormErrors`,
:meth:`~horizon.test.helpers.TestCase.assertMessageCount`, and
:meth:`~horizon.test.helpers.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.helpers.TestCase.assertNoFormErrors` immediately
after your ``client.post`` call for tests that handle form views. This will
immediately fail if your form POST failed due to a validation error and
tell you what the error was.
#. Use :meth:`~horizon.test.helpers.TestCase.assertMessageCount` and
:meth:`~horizon.test.helpers.TestCase.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.helpers.TestCase.assertNoFormErrors` and
:meth:`~horizon.test.helpers.TestCase.assertMessageCount` will make it readily
apparent what the problem is in the majority of cases. If not, then use ``pdb``
and start interrupting the code flow to see where things are getting off track.

View File

@ -1,556 +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 </quickstart>` 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 <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 permissions required to
access this dashboard::
class VizDash(horizon.Dashboard):
name = _("Visualizations")
slug = "visualizations"
panels = ('flocking',)
default_panel = 'flocking'
permissions = ('openstack.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'
permissions = ('openstack.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 '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 main %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}
</div>
</div>
{% 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': ('project', 'admin', 'settings',),
}
Please note, the dashboards also must be added to settings.py::
INSTALLED_APPS = (
'openstack_dashboard',
...
'horizon',
'openstack_dashboard.dashboards.project',
'openstack_dashboard.dashboards.admin',
'openstack_dashboard.dashboards.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!

View File

@ -1,57 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
""" The Horizon interface.
Contains the core Horizon classes--:class:`~horizon.Dashboard` and
:class:`horizon.Panel`--the dynamic URLconf for Horizon, and common interface
methods like :func:`~horizon.register` and :func:`~horizon.unregister`.
"""
# Because this module is compiled by setup.py before Django may be installed
# in the environment we try importing Django and issue a warning but move on
# should that fail.
Horizon = None
try:
from horizon.base import Dashboard
from horizon.base import Horizon
from horizon.base import Panel
from horizon.base import PanelGroup
except ImportError:
import warnings
def simple_warn(message, category, filename, lineno, file=None, line=None):
return '%s: %s' % (category.__name__, message)
msg = ("Could not import Horizon dependencies. "
"This is normal during installation.\n")
warnings.formatwarning = simple_warn
warnings.warn(msg, Warning)
if Horizon:
register = Horizon.register
unregister = Horizon.unregister
get_absolute_url = Horizon.get_absolute_url
get_user_home = Horizon.get_user_home
get_dashboard = Horizon.get_dashboard
get_default_dashboard = Horizon.get_default_dashboard
get_dashboards = Horizon.get_dashboards
urls = Horizon._lazy_urls
# silence flake8 about unused imports here:
assert Dashboard
assert Panel
assert PanelGroup

View File

@ -1,803 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
"""
Contains the core classes and functionality that makes Horizon what it is.
This module is considered internal, and should not be relied on directly.
Public APIs are made available through the :mod:`horizon` module and
the classes contained therein.
"""
import collections
import copy
import inspect
import logging
import os
from django.conf import settings
from django.conf.urls.defaults import include
from django.conf.urls.defaults import patterns
from django.conf.urls.defaults import url
from django.core.exceptions import ImproperlyConfigured
from django.core.urlresolvers import reverse
from django.utils.datastructures import SortedDict
from django.utils.functional import SimpleLazyObject
from django.utils.importlib import import_module
from django.utils.module_loading import module_has_submodule
from django.utils.translation import ugettext_lazy as _
from horizon import conf
from horizon.decorators import _current_component
from horizon.decorators import require_auth
from horizon.decorators import require_perms
from horizon import loaders
LOG = logging.getLogger(__name__)
def _decorate_urlconf(urlpatterns, decorator, *args, **kwargs):
for pattern in urlpatterns:
if getattr(pattern, 'callback', None):
pattern._callback = decorator(pattern.callback, *args, **kwargs)
if getattr(pattern, 'url_patterns', []):
_decorate_urlconf(pattern.url_patterns, decorator, *args, **kwargs)
class NotRegistered(Exception):
pass
class HorizonComponent(object):
def __init__(self):
super(HorizonComponent, self).__init__()
if not self.slug:
raise ImproperlyConfigured('Every %s must have a slug.'
% self.__class__)
def __unicode__(self):
name = getattr(self, 'name', u"Unnamed %s" % self.__class__.__name__)
return unicode(name)
def _get_default_urlpatterns(self):
package_string = '.'.join(self.__module__.split('.')[:-1])
if getattr(self, 'urls', None):
try:
mod = import_module('.%s' % self.urls, package_string)
except ImportError:
mod = import_module(self.urls)
urlpatterns = mod.urlpatterns
else:
# Try importing a urls.py from the dashboard package
if module_has_submodule(import_module(package_string), 'urls'):
urls_mod = import_module('.urls', package_string)
urlpatterns = urls_mod.urlpatterns
else:
urlpatterns = patterns('')
return urlpatterns
class Registry(object):
def __init__(self):
self._registry = {}
if not getattr(self, '_registerable_class', None):
raise ImproperlyConfigured('Subclasses of Registry must set a '
'"_registerable_class" property.')
def _register(self, cls):
"""Registers the given class.
If the specified class is already registered then it is ignored.
"""
if not inspect.isclass(cls):
raise ValueError('Only classes may be registered.')
elif not issubclass(cls, self._registerable_class):
raise ValueError('Only %s classes or subclasses may be registered.'
% self._registerable_class.__name__)
if cls not in self._registry:
cls._registered_with = self
self._registry[cls] = cls()
return self._registry[cls]
def _unregister(self, cls):
"""Unregisters the given class.
If the specified class isn't registered, ``NotRegistered`` will
be raised.
"""
if not issubclass(cls, self._registerable_class):
raise ValueError('Only %s classes or subclasses may be '
'unregistered.' % self._registerable_class)
if cls not in self._registry.keys():
raise NotRegistered('%s is not registered' % cls)
del self._registry[cls]
return True
def _registered(self, cls):
if inspect.isclass(cls) and issubclass(cls, self._registerable_class):
found = self._registry.get(cls, None)
if found:
return found
else:
# Allow for fetching by slugs as well.
for registered in self._registry.values():
if registered.slug == cls:
return registered
class_name = self._registerable_class.__name__
if hasattr(self, "_registered_with"):
parent = self._registered_with._registerable_class.__name__
raise NotRegistered('%(type)s with slug "%(slug)s" is not '
'registered with %(parent)s "%(name)s".'
% {"type": class_name,
"slug": cls,
"parent": parent,
"name": self.slug})
else:
slug = getattr(cls, "slug", cls)
raise NotRegistered('%(type)s with slug "%(slug)s" is not '
'registered.' % {"type": class_name,
"slug": slug})
class Panel(HorizonComponent):
""" A base class for defining Horizon dashboard panels.
All Horizon dashboard panels should extend from this class. It provides
the appropriate hooks for automatically constructing URLconfs, and
providing permission-based access control.
.. attribute:: name
The name of the panel. This will be displayed in the
auto-generated navigation and various other places.
Default: ``''``.
.. attribute:: slug
A unique "short name" for the panel. The slug is used as
a component of the URL path for the panel. Default: ``''``.
.. attribute:: permissions
A list of permission names, all of which a user must possess in order
to access any view associated with this panel. This attribute
is combined cumulatively with any permissions required on the
``Dashboard`` class with which it is registered.
.. attribute:: urls
Path to a URLconf of views for this panel using dotted Python
notation. If no value is specified, a file called ``urls.py``
living in the same package as the ``panel.py`` file is used.
Default: ``None``.
.. attribute:: nav
.. method:: nav(context)
The ``nav`` attribute can be either boolean value or a callable
which accepts a ``RequestContext`` object as a single argument
to control whether or not this panel should appear in
automatically-generated navigation. Default: ``True``.
.. attribute:: index_url_name
The ``name`` argument for the URL pattern which corresponds to
the index view for this ``Panel``. This is the view that
:meth:`.Panel.get_absolute_url` will attempt to reverse.
"""
name = ''
slug = ''
urls = None
nav = True
index_url_name = "index"
def __repr__(self):
return "<Panel: %s>" % self.slug
def get_absolute_url(self):
""" Returns the default URL for this panel.
The default URL is defined as the URL pattern with ``name="index"`` in
the URLconf for this panel.
"""
try:
return reverse('horizon:%s:%s:%s' % (self._registered_with.slug,
self.slug,
self.index_url_name))
except Exception as exc:
# Logging here since this will often be called in a template
# where the exception would be hidden.
LOG.info("Error reversing absolute URL for %s: %s" % (self, exc))
raise
@property
def _decorated_urls(self):
urlpatterns = self._get_default_urlpatterns()
# Apply access controls to all views in the patterns
permissions = getattr(self, 'permissions', [])
_decorate_urlconf(urlpatterns, require_perms, permissions)
_decorate_urlconf(urlpatterns, _current_component, panel=self)
# Return the three arguments to django.conf.urls.defaults.include
return urlpatterns, self.slug, self.slug
class PanelGroup(object):
""" A container for a set of :class:`~horizon.Panel` classes.
When iterated, it will yield each of the ``Panel`` instances it
contains.
.. attribute:: slug
A unique string to identify this panel group. Required.
.. attribute:: name
A user-friendly name which will be used as the group heading in
places such as the navigation. Default: ``None``.
.. attribute:: panels
A list of panel module names which should be contained within this
grouping.
"""
def __init__(self, dashboard, slug=None, name=None, panels=None):
self.dashboard = dashboard
self.slug = slug or getattr(self, "slug", "default")
self.name = name or getattr(self, "name", None)
# Our panels must be mutable so it can be extended by others.
self.panels = list(panels or getattr(self, "panels", []))
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.slug)
def __unicode__(self):
return self.name
def __iter__(self):
panel_instances = []
for name in self.panels:
try:
panel_instances.append(self.dashboard.get_panel(name))
except NotRegistered as e:
LOG.debug(e)
return iter(panel_instances)
class Dashboard(Registry, HorizonComponent):
""" A base class for defining Horizon dashboards.
All Horizon dashboards should extend from this base class. It provides the
appropriate hooks for automatic discovery of :class:`~horizon.Panel`
modules, automatically constructing URLconfs, and providing
permission-based access control.
.. attribute:: name
The name of the dashboard. This will be displayed in the
auto-generated navigation and various other places.
Default: ``''``.
.. attribute:: slug
A unique "short name" for the dashboard. The slug is used as
a component of the URL path for the dashboard. Default: ``''``.
.. attribute:: panels
The ``panels`` attribute can be either a flat list containing the name
of each panel **module** which should be loaded as part of this
dashboard, or a list of :class:`~horizon.PanelGroup` classes which
define groups of panels as in the following example::
class SystemPanels(horizon.PanelGroup):
slug = "syspanel"
name = _("System Panel")
panels = ('overview', 'instances', ...)
class Syspanel(horizon.Dashboard):
panels = (SystemPanels,)
Automatically generated navigation will use the order of the
modules in this attribute.
Default: ``[]``.
.. warning::
The values for this attribute should not correspond to the
:attr:`~.Panel.name` attributes of the ``Panel`` classes.
They should be the names of the Python modules in which the
``panel.py`` files live. This is used for the automatic
loading and registration of ``Panel`` classes much like
Django's ``ModelAdmin`` machinery.
Panel modules must be listed in ``panels`` in order to be
discovered by the automatic registration mechanism.
.. attribute:: default_panel
The name of the panel which should be treated as the default
panel for the dashboard, i.e. when you visit the root URL
for this dashboard, that's the panel that is displayed.
Default: ``None``.
.. attribute:: permissions
A list of permission names, all of which a user must possess in order
to access any panel registered with this dashboard. This attribute
is combined cumulatively with any permissions required on individual
:class:`~horizon.Panel` classes.
.. attribute:: urls
Optional path to a URLconf of additional views for this dashboard
which are not connected to specific panels. Default: ``None``.
.. attribute:: nav
Optional boolean to control whether or not this dashboard should
appear in automatically-generated navigation. Default: ``True``.
.. attribute:: supports_tenants
Optional boolean that indicates whether or not this dashboard includes
support for projects/tenants. If set to ``True`` this dashboard's
navigation will include a UI element that allows the user to select
project/tenant. Default: ``False``.
.. attribute:: public
Boolean value to determine whether this dashboard can be viewed
without being logged in. Defaults to ``False``.
"""
_registerable_class = Panel
name = ''
slug = ''
urls = None
panels = []
default_panel = None
nav = True
supports_tenants = False
public = False
def __repr__(self):
return "<Dashboard: %s>" % self.slug
def __init__(self, *args, **kwargs):
super(Dashboard, self).__init__(*args, **kwargs)
self._panel_groups = None
def get_panel(self, panel):
"""
Returns the specified :class:`~horizon.Panel` instance registered
with this dashboard.
"""
return self._registered(panel)
def get_panels(self):
"""
Returns the :class:`~horizon.Panel` instances registered with this
dashboard in order, without any panel groupings.
"""
all_panels = []
panel_groups = self.get_panel_groups()
for panel_group in panel_groups.values():
all_panels.extend(panel_group)
return all_panels
def get_panel_group(self, slug):
return self._panel_groups[slug]
def get_panel_groups(self):
registered = copy.copy(self._registry)
panel_groups = []
# Gather our known panels
if self._panel_groups is not None:
for panel_group in self._panel_groups.values():
for panel in panel_group:
registered.pop(panel.__class__)
panel_groups.append((panel_group.slug, panel_group))
# Deal with leftovers (such as add-on registrations)
if len(registered):
slugs = [panel.slug for panel in registered.values()]
new_group = PanelGroup(self,
slug="other",
name=_("Other"),
panels=slugs)
panel_groups.append((new_group.slug, new_group))
return SortedDict(panel_groups)
def get_absolute_url(self):
""" Returns the default URL for this dashboard.
The default URL is defined as the URL pattern with ``name="index"``
in the URLconf for the :class:`~horizon.Panel` specified by
:attr:`~horizon.Dashboard.default_panel`.
"""
try:
return self._registered(self.default_panel).get_absolute_url()
except:
# Logging here since this will often be called in a template
# where the exception would be hidden.
LOG.exception("Error reversing absolute URL for %s." % self)
raise
@property
def _decorated_urls(self):
urlpatterns = self._get_default_urlpatterns()
default_panel = None
# Add in each panel's views except for the default view.
for panel in self._registry.values():
if panel.slug == self.default_panel:
default_panel = panel
continue
urlpatterns += patterns('',
url(r'^%s/' % panel.slug, include(panel._decorated_urls)))
# Now the default view, which should come last
if not default_panel:
raise NotRegistered('The default panel "%s" is not registered.'
% self.default_panel)
urlpatterns += patterns('',
url(r'', include(default_panel._decorated_urls)))
# Require login if not public.
if not self.public:
_decorate_urlconf(urlpatterns, require_auth)
# Apply access controls to all views in the patterns
permissions = getattr(self, 'permissions', [])
_decorate_urlconf(urlpatterns, require_perms, permissions)
_decorate_urlconf(urlpatterns, _current_component, dashboard=self)
# Return the three arguments to django.conf.urls.defaults.include
return urlpatterns, self.slug, self.slug
def _autodiscover(self):
""" Discovers panels to register from the current dashboard module. """
if getattr(self, "_autodiscover_complete", False):
return
panels_to_discover = []
panel_groups = []
# If we have a flat iterable of panel names, wrap it again so
# we have a consistent structure for the next step.
if all([isinstance(i, basestring) for i in self.panels]):
self.panels = [self.panels]
# Now iterate our panel sets.
for panel_set in self.panels:
# Instantiate PanelGroup classes.
if not isinstance(panel_set, collections.Iterable) and \
issubclass(panel_set, PanelGroup):
panel_group = panel_set(self)
# Check for nested tuples, and convert them to PanelGroups
elif not isinstance(panel_set, PanelGroup):
panel_group = PanelGroup(self, panels=panel_set)
# Put our results into their appropriate places
panels_to_discover.extend(panel_group.panels)
panel_groups.append((panel_group.slug, panel_group))
self._panel_groups = SortedDict(panel_groups)
# Do the actual discovery
package = '.'.join(self.__module__.split('.')[:-1])
mod = import_module(package)
for panel in panels_to_discover:
try:
before_import_registry = copy.copy(self._registry)
import_module('.%s.panel' % panel, package)
except:
self._registry = before_import_registry
if module_has_submodule(mod, panel):
raise
self._autodiscover_complete = True
@classmethod
def register(cls, panel):
""" Registers a :class:`~horizon.Panel` with this dashboard. """
panel_class = Horizon.register_panel(cls, panel)
# Support template loading from panel template directories.
panel_mod = import_module(panel.__module__)
panel_dir = os.path.dirname(panel_mod.__file__)
template_dir = os.path.join(panel_dir, "templates")
if os.path.exists(template_dir):
key = os.path.join(cls.slug, panel.slug)
loaders.panel_template_dirs[key] = template_dir
return panel_class
@classmethod
def unregister(cls, panel):
""" Unregisters a :class:`~horizon.Panel` from this dashboard. """
success = Horizon.unregister_panel(cls, panel)
if success:
# Remove the panel's template directory.
key = os.path.join(cls.slug, panel.slug)
if key in loaders.panel_template_dirs:
del loaders.panel_template_dirs[key]
return success
class Workflow(object):
def __init__(*args, **kwargs):
raise NotImplementedError()
try:
from django.utils.functional import empty
except ImportError:
#Django 1.3 fallback
empty = None
class LazyURLPattern(SimpleLazyObject):
def __iter__(self):
if self._wrapped is empty:
self._setup()
return iter(self._wrapped)
def __reversed__(self):
if self._wrapped is empty:
self._setup()
return reversed(self._wrapped)
def __len__(self):
if self._wrapped is empty:
self._setup()
return len(self._wrapped)
def __getitem__(self, idx):
if self._wrapped is empty:
self._setup()
return self._wrapped[idx]
class Site(Registry, HorizonComponent):
""" The overarching class which encompasses all dashboards and panels. """
# Required for registry
_registerable_class = Dashboard
name = "Horizon"
namespace = 'horizon'
slug = 'horizon'
urls = 'horizon.site_urls'
def __repr__(self):
return u"<Site: %s>" % self.slug
@property
def _conf(self):
return conf.HORIZON_CONFIG
@property
def dashboards(self):
return self._conf['dashboards']
@property
def default_dashboard(self):
return self._conf['default_dashboard']
def register(self, dashboard):
""" Registers a :class:`~horizon.Dashboard` with Horizon."""
return self._register(dashboard)
def unregister(self, dashboard):
""" Unregisters a :class:`~horizon.Dashboard` from Horizon. """
return self._unregister(dashboard)
def registered(self, dashboard):
return self._registered(dashboard)
def register_panel(self, dashboard, panel):
dash_instance = self.registered(dashboard)
return dash_instance._register(panel)
def unregister_panel(self, dashboard, panel):
dash_instance = self.registered(dashboard)
if not dash_instance:
raise NotRegistered("The dashboard %s is not registered."
% dashboard)
return dash_instance._unregister(panel)
def get_dashboard(self, dashboard):
""" Returns the specified :class:`~horizon.Dashboard` instance. """
return self._registered(dashboard)
def get_dashboards(self):
""" Returns an ordered tuple of :class:`~horizon.Dashboard` modules.
Orders dashboards according to the ``"dashboards"`` key in
``HORIZON_CONFIG`` or else returns all registered dashboards
in alphabetical order.
Any remaining :class:`~horizon.Dashboard` classes registered with
Horizon but not listed in ``HORIZON_CONFIG['dashboards']``
will be appended to the end of the list alphabetically.
"""
if self.dashboards:
registered = copy.copy(self._registry)
dashboards = []
for item in self.dashboards:
dashboard = self._registered(item)
dashboards.append(dashboard)
registered.pop(dashboard.__class__)
if len(registered):
extra = registered.values()
extra.sort()
dashboards.extend(extra)
return dashboards
else:
dashboards = self._registry.values()
dashboards.sort()
return dashboards
def get_default_dashboard(self):
""" Returns the default :class:`~horizon.Dashboard` instance.
If ``"default_dashboard"`` is specified in ``HORIZON_CONFIG``
then that dashboard will be returned. If not, the first dashboard
returned by :func:`~horizon.get_dashboards` will be returned.
"""
if self.default_dashboard:
return self._registered(self.default_dashboard)
elif len(self._registry):
return self.get_dashboards()[0]
else:
raise NotRegistered("No dashboard modules have been registered.")
def get_user_home(self, user):
""" Returns the default URL for a particular user.
This method can be used to customize where a user is sent when
they log in, etc. By default it returns the value of
:meth:`get_absolute_url`.
An alternative function can be supplied to customize this behavior
by specifying a either a URL or a function which returns a URL via
the ``"user_home"`` key in ``HORIZON_CONFIG``. Each of these
would be valid::
{"user_home": "/home",} # A URL
{"user_home": "my_module.get_user_home",} # Path to a function
{"user_home": lambda user: "/" + user.name,} # A function
{"user_home": None,} # Will always return the default dashboard
This can be useful if the default dashboard may not be accessible
to all users. When user_home is missing from HORIZON_CONFIG,
it will default to the settings.LOGIN_REDIRECT_URL value.
"""
user_home = self._conf['user_home']
if user_home:
if callable(user_home):
return user_home(user)
elif isinstance(user_home, basestring):
# Assume we've got a URL if there's a slash in it
if user_home.find("/") != -1:
return user_home
else:
mod, func = user_home.rsplit(".", 1)
return getattr(import_module(mod), func)(user)
# If it's not callable and not a string, it's wrong.
raise ValueError('The user_home setting must be either a string '
'or a callable object (e.g. a function).')
else:
return self.get_absolute_url()
def get_absolute_url(self):
""" Returns the default URL for Horizon's URLconf.
The default URL is determined by calling
:meth:`~horizon.Dashboard.get_absolute_url`
on the :class:`~horizon.Dashboard` instance returned by
:meth:`~horizon.get_default_dashboard`.
"""
return self.get_default_dashboard().get_absolute_url()
@property
def _lazy_urls(self):
""" Lazy loading for URL patterns.
This method avoids problems associated with attempting to evaluate
the the URLconf before the settings module has been loaded.
"""
def url_patterns():
return self._urls()[0]
return LazyURLPattern(url_patterns), self.namespace, self.slug
def _urls(self):
""" Constructs the URLconf for Horizon from registered Dashboards. """
urlpatterns = self._get_default_urlpatterns()
self._autodiscover()
# Discover each dashboard's panels.
for dash in self._registry.values():
dash._autodiscover()
# Allow for override modules
if self._conf.get("customization_module", None):
customization_module = self._conf["customization_module"]
bits = customization_module.split('.')
mod_name = bits.pop()
package = '.'.join(bits)
mod = import_module(package)
try:
before_import_registry = copy.copy(self._registry)
import_module('%s.%s' % (package, mod_name))
except:
self._registry = before_import_registry
if module_has_submodule(mod, mod_name):
raise
# Compile the dynamic urlconf.
for dash in self._registry.values():
urlpatterns += patterns('',
url(r'^%s/' % dash.slug, include(dash._decorated_urls)))
# Return the three arguments to django.conf.urls.defaults.include
return urlpatterns, self.namespace, self.slug
def _autodiscover(self):
""" Discovers modules to register from ``settings.INSTALLED_APPS``.
This makes sure that the appropriate modules get imported to register
themselves with Horizon.
"""
if not getattr(self, '_registerable_class', None):
raise ImproperlyConfigured('You must set a '
'"_registerable_class" property '
'in order to use autodiscovery.')
# Discover both dashboards and panels, in that order
for mod_name in ('dashboard', 'panel'):
for app in settings.INSTALLED_APPS:
mod = import_module(app)
try:
before_import_registry = copy.copy(self._registry)
import_module('%s.%s' % (app, mod_name))
except:
self._registry = before_import_registry
if module_has_submodule(mod, mod_name):
raise
class HorizonSite(Site):
"""
A singleton implementation of Site such that all dealings with horizon
get the same instance no matter what. There can be only one.
"""
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Site, cls).__new__(cls, *args, **kwargs)
return cls._instance
# The one true Horizon
Horizon = HorizonSite()

View File

@ -1,21 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
from horizon.browsers.base import ResourceBrowser
from horizon.browsers.views import ResourceBrowserView
assert ResourceBrowser
assert ResourceBrowserView

View File

@ -1,150 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
from django import template
from django.utils.translation import ugettext_lazy as _
from horizon.browsers.breadcrumb import Breadcrumb
from horizon.tables import DataTable
from horizon.utils import html
class ResourceBrowser(html.HTMLElement):
"""A class which defines a browser for displaying data.
.. attribute:: name
A short name or slug for the browser.
.. attribute:: verbose_name
A more verbose name for the browser meant for display purposes.
.. attribute:: navigation_table_class
This table displays data on the left side of the browser.
Set the ``navigation_table_class`` attribute with
the desired :class:`~horizon.tables.DataTable` class.
This table class must set browser_table attribute in Meta to
``"navigation"``.
.. attribute:: content_table_class
This table displays data on the right side of the browser.
Set the ``content_table_class`` attribute with
the desired :class:`~horizon.tables.DataTable` class.
This table class must set browser_table attribute in Meta to
``"content"``.
.. attribute:: navigation_kwarg_name
This attribute represents the key of the navigatable items in the
kwargs property of this browser's view.
Defaults to ``"navigation_kwarg"``.
.. attribute:: content_kwarg_name
This attribute represents the key of the content items in the
kwargs property of this browser's view.
Defaults to ``"content_kwarg"``.
.. attribute:: template
String containing the template which should be used to render
the browser. Defaults to ``"horizon/common/_resource_browser.html"``.
.. attribute:: context_var_name
The name of the context variable which will contain the browser when
it is rendered. Defaults to ``"browser"``.
.. attribute:: has_breadcrumb
Indicates if the content table of the browser would have breadcrumb.
Defaults to false.
.. attribute:: breadcrumb_template
This is a template used to render the breadcrumb.
Defaults to ``"horizon/common/_breadcrumb.html"``.
"""
name = None
verbose_name = None
navigation_table_class = None
content_table_class = None
navigation_kwarg_name = "navigation_kwarg"
content_kwarg_name = "content_kwarg"
navigable_item_name = _("Navigation Item")
template = "horizon/common/_resource_browser.html"
context_var_name = "browser"
has_breadcrumb = False
breadcrumb_template = "horizon/common/_breadcrumb.html"
breadcrumb_url = None
def __init__(self, request, tables_dict=None, attrs=None, **kwargs):
super(ResourceBrowser, self).__init__()
self.name = self.name or self.__class__.__name__
self.verbose_name = self.verbose_name or self.name.title()
self.request = request
self.kwargs = kwargs
self.has_breadcrumb = getattr(self, "has_breadcrumb")
if self.has_breadcrumb:
self.breadcrumb_template = getattr(self, "breadcrumb_template")
self.breadcrumb_url = getattr(self, "breadcrumb_url")
if not self.breadcrumb_url:
raise ValueError("You must specify a breadcrumb_url "
"if the has_breadcrumb is set to True.")
self.attrs.update(attrs or {})
self.check_table_class(self.content_table_class, "content_table_class")
self.check_table_class(self.navigation_table_class,
"navigation_table_class")
if tables_dict:
self.set_tables(tables_dict)
def check_table_class(self, cls, attr_name):
if not cls or not issubclass(cls, DataTable):
raise ValueError("You must specify a DataTable subclass for "
"the %s attribute on %s."
% (attr_name, self.__class__.__name__))
def set_tables(self, tables):
"""
Sets the table instances on the browser from a dictionary mapping table
names to table instances (as constructed by MultiTableView).
"""
self.navigation_table = tables[self.navigation_table_class._meta.name]
self.content_table = tables[self.content_table_class._meta.name]
navigation_item = self.kwargs.get(self.navigation_kwarg_name)
content_path = self.kwargs.get(self.content_kwarg_name)
# Tells the navigation table what is selected.
self.navigation_table.current_item_id = navigation_item
if self.has_breadcrumb:
self.prepare_breadcrumb(tables, navigation_item, content_path)
def prepare_breadcrumb(self, tables, navigation_item, content_path):
if self.has_breadcrumb and navigation_item and content_path:
for table in tables.values():
table.breadcrumb = Breadcrumb(self.request,
self.breadcrumb_template,
navigation_item,
content_path,
self.breadcrumb_url)
def render(self):
browser_template = template.loader.get_template(self.template)
extra_context = {self.context_var_name: self}
context = template.RequestContext(self.request, extra_context)
return browser_template.render(context)

View File

@ -1,48 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
from django import template
from horizon.utils import html
class Breadcrumb(html.HTMLElement):
def __init__(self, request, template, root,
subfolder_path, url, attr=None):
super(Breadcrumb, self).__init__()
self.template = template
self.request = request
self.root = root
self.subfolder_path = subfolder_path
self.url = url
self._subfolders = []
def get_subfolders(self):
if self.subfolder_path and not self._subfolders:
(parent, slash, folder) = self.subfolder_path.strip('/') \
.rpartition('/')
while folder:
path = "%s%s%s/" % (parent, slash, folder)
self._subfolders.insert(0, (folder, path))
(parent, slash, folder) = parent.rpartition('/')
return self._subfolders
def render(self):
""" Renders the table using the template from the table options. """
breadcrumb_template = template.loader.get_template(self.template)
extra_context = {"breadcrumb": self}
context = template.RequestContext(self.request, extra_context)
return breadcrumb_template.render(context)

View File

@ -1,49 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
from django.utils.translation import ugettext_lazy as _
from horizon.tables import MultiTableView
class ResourceBrowserView(MultiTableView):
browser_class = None
def __init__(self, *args, **kwargs):
if not self.browser_class:
raise ValueError("You must specify a ResourceBrowser subclass "
"for the browser_class attribute on %s."
% self.__class__.__name__)
self.table_classes = (self.browser_class.navigation_table_class,
self.browser_class.content_table_class)
self.navigation_selection = False
super(ResourceBrowserView, self).__init__(*args, **kwargs)
def get_browser(self):
if not hasattr(self, "browser"):
self.browser = self.browser_class(self.request, **self.kwargs)
self.browser.set_tables(self.get_tables())
if not self.navigation_selection:
ct = self.browser.content_table
item = self.browser.navigable_item_name.lower()
ct._no_data_message = _("Select a %s to browse.") % item
return self.browser
def get_context_data(self, **kwargs):
context = super(ResourceBrowserView, self).get_context_data(**kwargs)
browser = self.get_browser()
context["%s_browser" % browser.name] = browser
return context

View File

@ -1,35 +0,0 @@
import copy
from django.utils.functional import empty
from django.utils.functional import LazyObject
class LazySettings(LazyObject):
def _setup(self, name=None):
from django.conf import settings
from horizon.conf.default import HORIZON_CONFIG as DEFAULT_CONFIG
HORIZON_CONFIG = copy.copy(DEFAULT_CONFIG)
HORIZON_CONFIG.update(settings.HORIZON_CONFIG)
# Ensure we always have our exception configuration...
for exc_category in ['unauthorized', 'not_found', 'recoverable']:
if exc_category not in HORIZON_CONFIG['exceptions']:
default_exc_config = DEFAULT_CONFIG['exceptions'][exc_category]
HORIZON_CONFIG['exceptions'][exc_category] = default_exc_config
# Ensure our password validator always exists...
if 'regex' not in HORIZON_CONFIG['password_validator']:
default_pw_regex = DEFAULT_CONFIG['password_validator']['regex']
HORIZON_CONFIG['password_validator']['regex'] = default_pw_regex
if 'help_text' not in HORIZON_CONFIG['password_validator']:
default_pw_help = DEFAULT_CONFIG['password_validator']['help_text']
HORIZON_CONFIG['password_validator']['help_text'] = default_pw_help
self._wrapped = HORIZON_CONFIG
def __getitem__(self, name, fallback=None):
if self._wrapped is empty:
self._setup(name)
return self._wrapped.get(name, fallback)
HORIZON_CONFIG = LazySettings()

View File

@ -1,13 +0,0 @@
from django.utils.translation import ugettext_lazy as _
import horizon
class {{ dash_name|title }}(horizon.Dashboard):
name = _("{{ dash_name|title }}")
slug = "{{ dash_name|slugify }}"
panels = () # Add your panels here.
default_panel = '' # Specify the slug of the dashboard's default panel.
horizon.register({{ dash_name|title }})

View File

@ -1,3 +0,0 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -1 +0,0 @@
/* Additional CSS for {{ dash_name }}. */

View File

@ -1 +0,0 @@
/* Additional JavaScript for {{ dash_name }}. */

View File

@ -1,11 +0,0 @@
{% load horizon %}{% jstemplate %}[% extends 'base.html' %]
[% block sidebar %]
[% include 'horizon/common/_sidebar.html' %]
[% endblock %]
[% block main %]
[% include "horizon/_messages.html" %]
[% block {{ dash_name }}_main %][% endblock %]
[% endblock %]
{% endjstemplate %}

View File

@ -1,35 +0,0 @@
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
# Default configuration dictionary. Do not mutate.
HORIZON_CONFIG = {
# Allow for ordering dashboards; list or tuple if provided.
'dashboards': None,
# Name of a default dashboard; defaults to first alphabetically if None
'default_dashboard': None,
# Default redirect url for users' home
'user_home': settings.LOGIN_REDIRECT_URL,
# AJAX settings for JavaScript
'ajax_queue_limit': 10,
'ajax_poll_interval': 2500,
# URL for additional help with this site.
'help_url': None,
# Exception configuration.
'exceptions': {'unauthorized': [],
'not_found': [],
'recoverable': []},
# Password configuration.
'password_validator': {'regex': '.*',
'help_text': _("Password is not accepted")},
'password_autocomplete': 'on',
# Enable or disable simplified floating IP address management.
'simple_ip_management': True
}

View File

@ -1,3 +0,0 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -1,13 +0,0 @@
from django.utils.translation import ugettext_lazy as _
import horizon
from {{ dash_path }} import dashboard
class {{ panel_name|title }}(horizon.Panel):
name = _("{{ panel_name|title }}")
slug = "{{ panel_name|slugify }}"
dashboard.{{ dash_name|title }}.register({{ panel_name|title }})

View File

@ -1,12 +0,0 @@
{% load horizon %}{% jstemplate %}[% extends '{{ dash_name }}/base.html' %]
[% load i18n %]
[% block title %][% trans "{{ panel_name|title }}" %][% endblock %]
[% block page_header %]
[% include "horizon/common/_page_header.html" with title=_("{{ panel_name|title }}") %]
[% endblock page_header %]
[% block {{ dash_name }}_main %]
[% endblock %]
{% endjstemplate %}

View File

@ -1,7 +0,0 @@
from horizon.test import helpers as test
class {{ panel_name|title}}Tests(test.TestCase):
# Unit tests for {{ panel_name }}.
def test_me(self):
self.assertTrue(1 + 1 == 2)

View File

@ -1,8 +0,0 @@
from django.conf.urls.defaults import patterns, url
from .views import IndexView
urlpatterns = patterns('',
url(r'^$', IndexView.as_view(), name='index'),
)

View File

@ -1,10 +0,0 @@
from horizon import views
class IndexView(views.APIView):
# A very simple class-based view...
template_name = '{{ dash_name }}/{{ panel_name }}/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context

View File

@ -1,44 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# 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.
"""
Context processors used by Horizon.
"""
from horizon import conf
def horizon(request):
""" The main Horizon context processor. Required for Horizon to function.
It adds the Horizon config to the context as well as setting the names
``True`` and ``False`` in the context to their boolean equivalents
for convenience.
.. warning::
Don't put API calls in context processors; they will be called once
for each template/template fragment which takes context that is used
to render the complete output.
"""
context = {"HORIZON_CONFIG": conf.HORIZON_CONFIG,
"True": True,
"False": False}
return context

View File

@ -1,94 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 CRS4
#
# 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.
"""
General-purpose decorators for use with Horizon.
"""
import functools
from django.utils.decorators import available_attrs
from django.utils.translation import ugettext_lazy as _
def _current_component(view_func, dashboard=None, panel=None):
""" Sets the currently-active dashboard and/or panel on the request. """
@functools.wraps(view_func, assigned=available_attrs(view_func))
def dec(request, *args, **kwargs):
if dashboard:
request.horizon['dashboard'] = dashboard
if panel:
request.horizon['panel'] = panel
return view_func(request, *args, **kwargs)
return dec
def require_auth(view_func):
""" Performs user authentication check.
Similar to Django's `login_required` decorator, except that this throws
:exc:`~horizon.exceptions.NotAuthenticated` exception if the user is not
signed-in.
"""
from horizon.exceptions import NotAuthenticated
@functools.wraps(view_func, assigned=available_attrs(view_func))
def dec(request, *args, **kwargs):
if request.user.is_authenticated():
return view_func(request, *args, **kwargs)
raise NotAuthenticated(_("Please log in to continue."))
return dec
def require_perms(view_func, required):
""" Enforces permission-based access controls.
:param list required: A tuple of permission names, all of which the request
user must possess in order access the decorated view.
Example usage::
from horizon.decorators import require_perms
@require_perms(['foo.admin', 'foo.member'])
def my_view(request):
...
Raises a :exc:`~horizon.exceptions.NotAuthorized` exception if the
requirements are not met.
"""
from horizon.exceptions import NotAuthorized
# We only need to check each permission once for a view, so we'll use a set
current_perms = getattr(view_func, '_required_perms', set([]))
view_func._required_perms = current_perms | set(required)
@functools.wraps(view_func, assigned=available_attrs(view_func))
def dec(request, *args, **kwargs):
if request.user.is_authenticated():
if request.user.has_perms(view_func._required_perms):
return view_func(request, *args, **kwargs)
raise NotAuthorized(_("You are not authorized to access %s")
% request.path)
# If we don't have any permissions, just return the original view.
if required:
return dec
else:
return view_func

View File

@ -1,321 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
"""
Exceptions raised by the Horizon code and the machinery for handling them.
"""
import logging
import os
import sys
from django.contrib.auth import logout
from django.core.management import color_style
from django.http import HttpRequest
from django.utils.translation import ugettext_lazy as _
from django.views.debug import CLEANSED_SUBSTITUTE
from django.views.debug import SafeExceptionReporterFilter
from horizon.conf import HORIZON_CONFIG
from horizon import messages
LOG = logging.getLogger(__name__)
class HorizonReporterFilter(SafeExceptionReporterFilter):
""" Error report filter that's always active, even in DEBUG mode. """
def is_active(self, request):
return True
# TODO(gabriel): This bugfix is cribbed from Django's code. When 1.4.1
# is available we can remove this code.
def get_traceback_frame_variables(self, request, tb_frame):
"""
Replaces the values of variables marked as sensitive with
stars (*********).
"""
# Loop through the frame's callers to see if the sensitive_variables
# decorator was used.
current_frame = tb_frame.f_back
sensitive_variables = None
while current_frame is not None:
if (current_frame.f_code.co_name == 'sensitive_variables_wrapper'
and 'sensitive_variables_wrapper'
in current_frame.f_locals):
# The sensitive_variables decorator was used, so we take note
# of the sensitive variables' names.
wrapper = current_frame.f_locals['sensitive_variables_wrapper']
sensitive_variables = getattr(wrapper,
'sensitive_variables',
None)
break
current_frame = current_frame.f_back
cleansed = []
if self.is_active(request) and sensitive_variables:
if sensitive_variables == '__ALL__':
# Cleanse all variables
for name, value in tb_frame.f_locals.items():
cleansed.append((name, CLEANSED_SUBSTITUTE))
return cleansed
else:
# Cleanse specified variables
for name, value in tb_frame.f_locals.items():
if name in sensitive_variables:
value = CLEANSED_SUBSTITUTE
elif isinstance(value, HttpRequest):
# Cleanse the request's POST parameters.
value = self.get_request_repr(value)
cleansed.append((name, value))
return cleansed
else:
# Potentially cleanse only the request if it's one of the
# frame variables.
for name, value in tb_frame.f_locals.items():
if isinstance(value, HttpRequest):
# Cleanse the request's POST parameters.
value = self.get_request_repr(value)
cleansed.append((name, value))
return cleansed
class HorizonException(Exception):
""" Base exception class for distinguishing our own exception classes. """
pass
class Http302(HorizonException):
"""
Error class which can be raised from within a handler to cause an
early bailout and redirect at the middleware level.
"""
status_code = 302
def __init__(self, location, message=None):
self.location = location
self.message = message
class NotAuthorized(HorizonException):
"""
Raised whenever a user attempts to access a resource which they do not
have permission-based access to (such as when failing the
:func:`~horizon.decorators.require_perms` decorator).
The included :class:`~horizon.middleware.HorizonMiddleware` catches
``NotAuthorized`` and handles it gracefully by displaying an error
message and redirecting the user to a login page.
"""
status_code = 401
class NotAuthenticated(HorizonException):
"""
Raised when a user is trying to make requests and they are not logged in.
The included :class:`~horizon.middleware.HorizonMiddleware` catches
``NotAuthenticated`` and handles it gracefully by displaying an error
message and redirecting the user to a login page.
"""
status_code = 403
class NotFound(HorizonException):
""" Generic error to replace all "Not Found"-type API errors. """
status_code = 404
class RecoverableError(HorizonException):
""" Generic error to replace any "Recoverable"-type API errors. """
status_code = 100 # HTTP status code "Continue"
class ServiceCatalogException(HorizonException):
"""
Raised when a requested service is not available in the ``ServiceCatalog``
returned by Keystone.
"""
def __init__(self, service_name):
message = 'Invalid service catalog service: %s' % service_name
super(ServiceCatalogException, self).__init__(message)
class AlreadyExists(HorizonException):
"""
Exception to be raised when trying to create an API resource which
already exists.
"""
def __init__(self, name, resource_type):
self.attrs = {"name": name, "resource": resource_type}
self.msg = 'A %(resource)s with the name "%(name)s" already exists.'
def __repr__(self):
return self.msg % self.attrs
def __str__(self):
return self.msg % self.attrs
def __unicode__(self):
return _(self.msg) % self.attrs
class WorkflowError(HorizonException):
""" Exception to be raised when something goes wrong in a workflow. """
pass
class WorkflowValidationError(HorizonException):
"""
Exception raised during workflow validation if required data is missing,
or existing data is not valid.
"""
pass
class HandledException(HorizonException):
"""
Used internally to track exceptions that have gone through
:func:`horizon.exceptions.handle` more than once.
"""
def __init__(self, wrapped):
self.wrapped = wrapped
UNAUTHORIZED = tuple(HORIZON_CONFIG['exceptions']['unauthorized'])
NOT_FOUND = tuple(HORIZON_CONFIG['exceptions']['not_found'])
RECOVERABLE = (AlreadyExists,)
RECOVERABLE += tuple(HORIZON_CONFIG['exceptions']['recoverable'])
def error_color(msg):
return color_style().ERROR_OUTPUT(msg)
def check_message(keywords, message):
"""
Checks an exception for given keywords and raises a new ``ActionError``
with the desired message if the keywords are found. This allows selective
control over API error messages.
"""
exc_type, exc_value, exc_traceback = sys.exc_info()
if set(str(exc_value).split(" ")).issuperset(set(keywords)):
exc_value._safe_message = message
raise
def handle(request, message=None, redirect=None, ignore=False,
escalate=False, log_level=None, force_log=None):
""" Centralized error handling for Horizon.
Because Horizon consumes so many different APIs with completely
different ``Exception`` types, it's necessary to have a centralized
place for handling exceptions which may be raised.
Exceptions are roughly divided into 3 types:
#. ``UNAUTHORIZED``: Errors resulting from authentication or authorization
problems. These result in being logged out and sent to the login screen.
#. ``NOT_FOUND``: Errors resulting from objects which could not be
located via the API. These generally result in a user-facing error
message, but are otherwise returned to the normal code flow. Optionally
a redirect value may be passed to the error handler so users are
returned to a different view than the one requested in addition to the
error message.
#. RECOVERABLE: Generic API errors which generate a user-facing message
but drop directly back to the regular code flow.
All other exceptions bubble the stack as normal unless the ``ignore``
argument is passed in as ``True``, in which case only unrecognized
errors are bubbled.
If the exception is not re-raised, an appropriate wrapper exception
class indicating the type of exception that was encountered will be
returned.
"""
exc_type, exc_value, exc_traceback = sys.exc_info()
log_method = getattr(LOG, log_level or "exception")
force_log = force_log or os.environ.get("HORIZON_TEST_RUN", False)
force_silence = getattr(exc_value, "silence_logging", False)
# Because the same exception may travel through this method more than
# once (if it's re-raised) we may want to treat it differently
# the second time (e.g. no user messages/logging).
handled = issubclass(exc_type, HandledException)
wrap = False
# Restore our original exception information, but re-wrap it at the end
if handled:
exc_type, exc_value, exc_traceback = exc_value.wrapped
wrap = True
# We trust messages from our own exceptions
if issubclass(exc_type, HorizonException):
message = exc_value
# Check for an override message
elif getattr(exc_value, "_safe_message", None):
message = exc_value._safe_message
# If the message has a placeholder for the exception, fill it in
elif message and "%(exc)s" in message:
message = message % {"exc": exc_value}
if issubclass(exc_type, UNAUTHORIZED):
if ignore:
return NotAuthorized
if not force_silence and not handled:
log_method(error_color("Unauthorized: %s" % exc_value))
if not handled:
if message:
message = _("Unauthorized: %s") % message
# We get some pretty useless error messages back from
# some clients, so let's define our own fallback.
fallback = _("Unauthorized. Please try logging in again.")
messages.error(request, message or fallback)
# Escalation means logging the user out and raising NotAuthorized
# so the middleware will redirect them appropriately.
if escalate:
logout(request)
raise NotAuthorized
# Otherwise continue and present our "unauthorized" error message.
return NotAuthorized
if issubclass(exc_type, NOT_FOUND):
wrap = True
if not force_silence and not handled and (not ignore or force_log):
log_method(error_color("Not Found: %s" % exc_value))
if not ignore and not handled:
messages.error(request, message or exc_value)
if redirect:
raise Http302(redirect)
if not escalate:
return NotFound # return to normal code flow
if issubclass(exc_type, RECOVERABLE):
wrap = True
if not force_silence and not handled and (not ignore or force_log):
# Default recoverable error to WARN log level
log_method = getattr(LOG, log_level or "warning")
log_method(error_color("Recoverable error: %s" % exc_value))
if not ignore and not handled:
messages.error(request, message or exc_value)
if redirect:
raise Http302(redirect)
if not escalate:
return RecoverableError # return to normal code flow
# If we've gotten here, time to wrap and/or raise our exception.
if wrap:
raise HandledException([exc_type, exc_value, exc_traceback])
raise exc_type, exc_value, exc_traceback

View File

@ -1,40 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
# FIXME(gabriel): Legacy imports for API compatibility.
from django.forms import * # noqa
from django.forms import widgets
# Convenience imports for public API components.
from horizon.forms.base import DateForm
from horizon.forms.base import SelfHandlingForm
from horizon.forms.base import SelfHandlingMixin
from horizon.forms.fields import DynamicChoiceField
from horizon.forms.fields import DynamicTypedChoiceField
# FIXME: TableStep hack adding NumberInput
from horizon.forms.fields import NumberInput
from horizon.forms.views import ModalFormMixin
from horizon.forms.views import ModalFormView
assert widgets
assert SelfHandlingMixin
assert SelfHandlingForm
assert DateForm
assert ModalFormView
assert ModalFormMixin
assert DynamicTypedChoiceField
assert DynamicChoiceField
assert NumberInput

View File

@ -1,57 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2012 Nebula, Inc.
#
# 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.
from django import forms
from django.forms.forms import NON_FIELD_ERRORS
class SelfHandlingMixin(object):
def __init__(self, request, *args, **kwargs):
self.request = request
if not hasattr(self, "handle"):
raise NotImplementedError("%s does not define a handle method."
% self.__class__.__name__)
super(SelfHandlingMixin, self).__init__(*args, **kwargs)
class SelfHandlingForm(SelfHandlingMixin, forms.Form):
"""
A base :class:`Form <django:django.forms.Form>` class which includes
processing logic in its subclasses.
"""
def api_error(self, message):
"""
Adds an error to the form's error dictionary after validation
based on problems reported via the API. This is useful when you
wish for API errors to appear as errors on the form rather than
using the messages framework.
"""
self._errors[NON_FIELD_ERRORS] = self.error_class([message])
class DateForm(forms.Form):
""" A simple form for selecting a range of time. """
start = forms.DateField(input_formats=("%Y-%m-%d",))
end = forms.DateField(input_formats=("%Y-%m-%d",))
def __init__(self, *args, **kwargs):
super(DateForm, self).__init__(*args, **kwargs)
self.fields['start'].widget.attrs['data-date-format'] = "yyyy-mm-dd"
self.fields['end'].widget.attrs['data-date-format'] = "yyyy-mm-dd"

View File

@ -1,77 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
from django.core import urlresolvers
from django.forms import fields
from django.forms import widgets
class DynamicSelectWidget(widgets.Select):
"""
A subclass of the ``Select`` widget which renders extra attributes for use
in callbacks to handle dynamic changes to the available choices.
"""
_data_add_url_attr = "data-add-item-url"
def render(self, *args, **kwargs):
add_item_url = self.get_add_item_url()
if add_item_url is not None:
self.attrs.update({self._data_add_url_attr: add_item_url})
return super(DynamicSelectWidget, self).render(*args, **kwargs)
def get_add_item_url(self):
if callable(self.add_item_link):
return self.add_item_link()
try:
if self.add_item_link_args:
return urlresolvers.reverse(self.add_item_link,
args=[self.add_item_link_args])
else:
return urlresolvers.reverse(self.add_item_link)
except urlresolvers.NoReverseMatch:
return self.add_item_link
class DynamicChoiceField(fields.ChoiceField):
"""
A subclass of ``ChoiceField`` with additional properties that make
dynamically updating its elements easier.
Notably, the field declaration takes an extra argument, ``add_item_link``
which may be a string or callable defining the URL that should be used
for the "add" link associated with the field.
"""
widget = DynamicSelectWidget
def __init__(self,
add_item_link=None,
add_item_link_args=None,
*args,
**kwargs):
super(DynamicChoiceField, self).__init__(*args, **kwargs)
self.widget.add_item_link = add_item_link
self.widget.add_item_link_args = add_item_link_args
class DynamicTypedChoiceField(DynamicChoiceField, fields.TypedChoiceField):
""" Simple mix of ``DynamicChoiceField`` and ``TypedChoiceField``. """
pass
# FIXME: TableStep
# Should be in django 1.5.1 forms.widgets
class NumberInput(widgets.TextInput):
input_type = 'number'

View File

@ -1,117 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Nebula, Inc.
#
# 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.
import json
import os
from django import http
from django.views import generic
from horizon import exceptions
ADD_TO_FIELD_HEADER = "HTTP_X_HORIZON_ADD_TO_FIELD"
class ModalFormMixin(object):
def get_template_names(self):
if self.request.is_ajax():
if not hasattr(self, "ajax_template_name"):
# Transform standard template name to ajax name (leading "_")
bits = list(os.path.split(self.template_name))
bits[1] = "".join(("_", bits[1]))
self.ajax_template_name = os.path.join(*bits)
template = self.ajax_template_name
else:
template = self.template_name
return template
def get_context_data(self, **kwargs):
context = super(ModalFormMixin, self).get_context_data(**kwargs)
if self.request.is_ajax():
context['hide'] = True
if ADD_TO_FIELD_HEADER in self.request.META:
context['add_to_field'] = self.request.META[ADD_TO_FIELD_HEADER]
return context
class ModalFormView(ModalFormMixin, generic.FormView):
"""
The main view class from which all views which handle forms in Horizon
should inherit. It takes care of all details with processing
:class:`~horizon.forms.base.SelfHandlingForm` classes, and modal concerns
when the associated template inherits from
`horizon/common/_modal_form.html`.
Subclasses must define a ``form_class`` and ``template_name`` attribute
at minimum.
See Django's documentation on the `FormView <https://docs.djangoproject.com
/en/dev/ref/class-based-views/generic-editing/#formview>`_ class for
more details.
"""
def get_object_id(self, obj):
"""
For dynamic insertion of resources created in modals, this method
returns the id of the created object. Defaults to returning the ``id``
attribute.
"""
return obj.id
def get_object_display(self, obj):
"""
For dynamic insertion of resources created in modals, this method
returns the display name of the created object. Defaults to returning
the ``name`` attribute.
"""
return obj.name
def get_form(self, form_class):
"""
Returns an instance of the form to be used in this view.
"""
return form_class(self.request, **self.get_form_kwargs())
def form_valid(self, form):
try:
handled = form.handle(self.request, form.cleaned_data)
except:
handled = None
exceptions.handle(self.request)
if handled:
if ADD_TO_FIELD_HEADER in self.request.META:
field_id = self.request.META[ADD_TO_FIELD_HEADER]
data = [self.get_object_id(handled),
self.get_object_display(handled)]
response = http.HttpResponse(json.dumps(data))
response["X-Horizon-Add-To-Field"] = field_id
elif isinstance(handled, http.HttpResponse):
return handled
else:
success_url = self.get_success_url()
response = http.HttpResponseRedirect(success_url)
# TODO(gabriel): This is not a long-term solution to how
# AJAX should be handled, but it's an expedient solution
# until the blueprint for AJAX handling is architected
# and implemented.
response['X-Horizon-Location'] = success_url
return response
else:
# If handled didn't return, we can assume something went
# wrong, and we should send back the form as-is.
return self.form_invalid(form)

View File

@ -1,48 +0,0 @@
"""
Wrapper for loading templates from "templates" directories in panel modules.
"""
import os
from django.conf import settings
from django.template.base import TemplateDoesNotExist
from django.template.loader import BaseLoader
from django.utils._os import safe_join
# Set up a cache of the panel directories to search.
panel_template_dirs = {}
class TemplateLoader(BaseLoader):
is_usable = True
def get_template_sources(self, template_name):
bits = template_name.split(os.path.sep, 2)
if len(bits) == 3:
dash_name, panel_name, remainder = bits
key = os.path.join(dash_name, panel_name)
if key in panel_template_dirs:
template_dir = panel_template_dirs[key]
try:
yield safe_join(template_dir, panel_name, remainder)
except UnicodeDecodeError:
# The template dir name wasn't valid UTF-8.
raise
except ValueError:
# The joined path was located outside of template_dir.
pass
def load_template_source(self, template_name, template_dirs=None):
for path in self.get_template_sources(template_name):
try:
file = open(path)
try:
return (file.read().decode(settings.FILE_CHARSET), path)
finally:
file.close()
except IOError:
pass
raise TemplateDoesNotExist(template_name)
_loader = TemplateLoader()

View File

@ -1,514 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Dimitar Dimitrov <dimitrov@linuxmail.org>, 2012
# Yasen Pramatarov <yasen@lindeas.com>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: bg_BG\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Друго"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Влезте, за да продължите."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "Нямате права да достъпвате %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Нямате права. Опитайте да влезете отново."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "Паролата не е одобрена."
#: tables/actions.py:349
msgid "Filter"
msgstr "Филтър"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "Нямате права да %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Изтриване"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Изтрито"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr ""
#: tables/base.py:748
msgid "No items to display."
msgstr ""
#: tables/base.py:852
msgid "Actions"
msgstr "Действия"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr ""
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Изберете ред, преди да предприемете това действие."
#: templates/_header.html:3
msgid "Logged in as"
msgstr ""
#: templates/_header.html:5
msgid "Help"
msgstr "Помощ"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "Изход"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "Вход"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "Вход"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr ""
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "Записване"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Информация: "
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Внимание: "
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Успех: "
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Грешка: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Обобщение"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr ""
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "Обобщение"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "Всички инстанции"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "Налично"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "Налично"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Текущ проект"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Изберете месец, за да намерите потреблението"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Изпращане"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Активни инстанции"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr ""
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr ""
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr ""
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Отказ"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "Без ограничение"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Налично"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "Паролата трябва да е между 8 и 18 символа."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Котки"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Котенца"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Тигри"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Кучета"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Кученца"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "Моето табло"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "Моят панел"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Админ панел"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "Грамадни мравки нападат Сан Франциско!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr ""
#: test/tests/tables.py:108
msgid "Batched"
msgstr ""
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr ""
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr ""
#: test/tests/tables.py:118
msgid "Down"
msgstr ""
#: test/tests/tables.py:118
msgid "Up"
msgstr ""
#: test/tests/tables.py:119
msgid "Downed"
msgstr ""
#: test/tests/tables.py:119
msgid "Upped"
msgstr ""
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr ""
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr ""
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr ""
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr ""
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr ""
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr ""
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Проект"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Потребител"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Пръбно действие първо"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Инстанция"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Пробно действие второ"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Пробно действие трето"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Админ"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Администраторско действие"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Неправилен формат на IP-адрес"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Неправилна версия на IP-адрес"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Неправилна маска на подмрежата"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Обработване..."
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr ""
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr ""
#: workflows/base.py:569
msgid "Save"
msgstr "Запазване"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s приключи успешно."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s не приключи."

View File

@ -1,73 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -1,513 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Sergi Almacellas <pokoli@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ca\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Altre"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Identifique-se per continuar."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "No esteu autoritzat per accedir a %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Sense autorització. Torneu a intentar-ho."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "La contrasenya no ha estat acceptada"
#: tables/actions.py:349
msgid "Filter"
msgstr "Filtre"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "No teniu per misos per a %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "No es pot %(action)s: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Eliminar"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Eliminat"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "L'attribut %(attr)s no existeix a %(obj)s"
#: tables/base.py:748
msgid "No items to display."
msgstr "No hi ha itemas per mostrar"
#: tables/base.py:852
msgid "Actions"
msgstr "Accions"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "No hi ha resultats per l'identificador \"%s\"."
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Heu de seleccionar una fila abans de dur a terme aquesta acció."
#: templates/_header.html:3
msgid "Logged in as"
msgstr "Identificat com"
#: templates/_header.html:5
msgid "Help"
msgstr "Ajuda"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "Sortir"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "Usuari"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "Identificació"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr ""
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "Registrar-se"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Informació:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Advertència:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Èxit:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Error: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Resum"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Mostrant %(counter)s element"
msgstr[1] "Mostrar %(counter)s elements"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr ""
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "Resum"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "Usat"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr "de"
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "Totes les instàncies"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "Disponible"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "Disponible"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Mostrant %(nav_items)s objjecte"
msgstr[1] "Mostrant %(nav_items)s objjectes"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Mostrant %(content_items)s objjecte"
msgstr[1] "Mostrant %(content_items)s objjectes"
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Projecte actual"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Seleccioneu un mes per obtenir les seves estadístiques d'us"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Transmet"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Activar instàncies"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "Memòria activa"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "Hores de VCPU d'aquest més"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "GB-Hores d'aquest més"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Cancela"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "Sense limit"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Disponible"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d byte"
msgstr[1] "%(size)d bytes"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
msgstr[1] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "La contrasenya ha de tenir entre 8 i 18 caràcters."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr ""
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr ""
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr ""
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr ""
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr ""
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "El meu tauler de control"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "El meu tauler"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Tauler d'administració"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr ""
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr "Lot"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "En lots"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "Item"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "Items"
#: test/tests/tables.py:118
msgid "Down"
msgstr "Davall"
#: test/tests/tables.py:118
msgid "Up"
msgstr "Amunt"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "Baixat"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "Pujat"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "Primera pestanya"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "Pestanya retardada"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "Pestanya deshabilitada"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "Pestanya enraderida"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "Pestanya amb la meua taula"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "Pestanya d'error recuperables"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Projecte"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Usuari"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Acció de prova 1"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Instància"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Acció de prova 2"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Acció de prova 3"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Adminstració"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Acció de l'administració"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Format incorrecte per l'adreça IP"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Versió incorrecta de l'adreça IP"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Màscara de xarxa invàlida"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Processant..."
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr ""
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr ""
#: workflows/base.py:569
msgid "Save"
msgstr "Desa"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s completat correctament."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s no s'ha completat"

View File

@ -1,520 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# _AdamCz_ <a.skotnicky@tcpisek.cz>, 2013
# pavlija7 <pavlk.jakub@gmail.com>, 2013
# Jaroslav Henner <jaroslav.henner@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: cs\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
#: base.py:424
msgid "Other"
msgstr "Ostatní"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "K pokračování je nutno se přihlásit."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "Nejste autorizován k přístupu %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Nejste autorizován. Prosím pokuste se přihlásit znovu."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr "Navigační položka"
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr "Vyberte %s pro prohlížení"
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "Heslo nebylo akceptováno"
#: tables/actions.py:349
msgid "Filter"
msgstr "Filtr"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:561
msgid "N/A"
msgstr "nedostupné"
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "Nemáte oprávnění pro %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Není možné provést %(action)s na %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Vymazat"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Smazáno"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "Atribut %(attr)s neexistuje na %(obj)s."
#: tables/base.py:748
msgid "No items to display."
msgstr "Žádné položky k zobrazení."
#: tables/base.py:852
msgid "Actions"
msgstr "Akce"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Nic s id \"%s\" nebylo nalezeno."
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Pro provedení akce prosím vyberte řádky."
#: templates/_header.html:3
msgid "Logged in as"
msgstr "Přihlášen jako"
#: templates/_header.html:5
msgid "Help"
msgstr "Nápověda"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "Odhlásit"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "Přihlášení"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "Přihlásit"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr "Nemáte povolení pro přístup k:"
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr "Přihlašte se jako jiný uživatel nebo se vraťte k"
#: templates/auth/_login.html:17
msgid "home page"
msgstr "domovská stránka"
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "Registrovat"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Info:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Varování:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Úspěch:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Chyba:"
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Souhrn"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Zobrazuji %(counter)s položku"
msgstr[1] "Zobrazuji %(counter)s položek"
msgstr[2] "Zobrazuji %(counter)s položky"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr "Více"
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "Souhrn kvóty"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "Použito"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr "z"
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "Dostupné instance"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "Dostupné vCPU"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "Dostupná RAM"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr "Dostupné volumes"
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr "Dostupný diskový prostor"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Zobrazuji %(nav_items)s položku"
msgstr[1] "Zobrazuji %(nav_items)s položky"
msgstr[2] "Zobrazuji %(nav_items)s položky"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Zobrazuji %(content_items)s položku"
msgstr[1] "Zobrazuji %(content_items)s položky"
msgstr[2] "Zobrazuji %(content_items)s položky"
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Současný projekt"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Vyberte měsíc pro zobrazení jeho využití."
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Potvrdit"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Aktivní instance"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "Využívaná RAM"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "VCPU-hodin za tento měsíc"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "GB-hodiny za tento měsíc"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Zrušit"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "Bez limitu"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Dostupné"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d byte"
msgstr[1] "%(size)d bytes"
msgstr[2] "%(size)d bytes"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
msgstr[1] "%(size)d"
msgstr[2] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "Heslo musí mít od 8 do 18 znaků."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "Roztomilé kočky"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Divoké kočky"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Kočky"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Koťata"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tygři"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Psi"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Štěňata"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "Můj Dashboard"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "Můj panel"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Admin panel"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "Obří mravenci utočí na San Francisco!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr "Už jsme v bezpečí před maravenci! Běž <a>sem</a>!"
#: test/tests/tables.py:107
msgid "Batch"
msgstr "Dávka"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "Dávkově"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "Položka"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "Položek"
#: test/tests/tables.py:118
msgid "Down"
msgstr "Dolů"
#: test/tests/tables.py:118
msgid "Up"
msgstr "Nahoru"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "Sestřelený"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "Nahozený"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr "Žádná Action Table"
#: test/tests/tables.py:684
msgid "Single Table"
msgstr "Jedna tabulka"
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "Záložka jedna"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "Delayed Tab"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "Disabled Tab"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "Disallowed Tab"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "Záložka s My Table"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "Recoverable Error Tab"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Projekt"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Uživatel"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Testovat Action One"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Instance"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Testovat Action Two"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Testovat Action Three"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Admin"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Admin akce"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Nekorektní formát IP adresy"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Špatná verze IP adresy"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Špatná subnet maska"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Zpracovávám..."
#: workflows/base.py:467
msgid "All available"
msgstr "Vše dostupné"
#: workflows/base.py:468
msgid "Members"
msgstr "Členové"
#: workflows/base.py:469
msgid "None available."
msgstr "Nedostupné"
#: workflows/base.py:470
msgid "No members."
msgstr "Žádní členové."
#: workflows/base.py:569
msgid "Save"
msgstr "Uložit"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s úspěšně dokončeno"
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s nedokončeno"

View File

@ -1,514 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Gabriel Hurley <gabriel@strikeawe.com>, 2012
# johnpostlethwait <john.postlethwait@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Other"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Please log in to continue."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "You are not authorized to access %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Unauthorized. Please try logging in again."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "Password is not accepted"
#: tables/actions.py:349
msgid "Filter"
msgstr "Filter"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "You do not have permission to %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Unable to %(action)s: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Delete"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Deleted"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "The attribute %(attr)s doesn't exist on %(obj)s."
#: tables/base.py:748
msgid "No items to display."
msgstr "No items to display."
#: tables/base.py:852
msgid "Actions"
msgstr "Actions"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "No match returned for the id \"%s\"."
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Please select a row before taking that action."
#: templates/_header.html:3
msgid "Logged in as"
msgstr ""
#: templates/_header.html:5
msgid "Help"
msgstr ""
#: templates/_header.html:7
msgid "Sign Out"
msgstr ""
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr ""
#: templates/auth/_login.html:4
msgid "Log In"
msgstr ""
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr ""
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr ""
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Info: "
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Warning: "
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Success: "
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Error: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Summary"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Displaying %(counter)s item"
msgstr[1] "Displaying %(counter)s items"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr ""
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr ""
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr ""
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr ""
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Current Project"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Select a month to query its usage"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Submit"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Active Instances"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr ""
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "This Month's VCPU-Hours"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "This Month's GB-Hours"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Cancel"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "No Limit"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Available"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d byte"
msgstr[1] "%(size)d bytes"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
msgstr[1] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "Password must be between 8 and 18 characters."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "Cute Cats"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Fierce Cats"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Cats"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Kittens"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tigers"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Dogs"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Puppies"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "My Dashboard"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "My Panel"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Admin Panel"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "Giant ants are attacking San Francisco!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr "Batch"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "Batched"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "Item"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "Items"
#: test/tests/tables.py:118
msgid "Down"
msgstr "Down"
#: test/tests/tables.py:118
msgid "Up"
msgstr "Up"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "Downed"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "Upped"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "Tab One"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "Delayed Tab"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "Disabled Tab"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "Disallowed Tab"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "Tab With My Table"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "Recoverable Error Tab"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Project"
#: test/tests/workflows.py:44
msgid "User"
msgstr "User"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Test Action One"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Instance"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Test Action Two"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Test Action Three"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Admin"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Admin Action"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Incorrect format for IP address"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Invalid version for IP address"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Invalid subnet mask"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Processing..."
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr ""
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr ""
#: workflows/base.py:569
msgid "Save"
msgstr "Save"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s completed successfully."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s did not complete."

View File

@ -1,73 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -1,513 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Andi Chandler <andi@gowling.com>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-30 22:33+0000\n"
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_GB\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Other"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Please log in to continue."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "You are not authorised to access %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Unauthorised. Please try logging in again."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr "Navigation Item"
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr "Select a %s to browse."
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "Password is not accepted"
#: tables/actions.py:349
msgid "Filter"
msgstr "Filter"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:561
msgid "N/A"
msgstr "N/A"
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "You do not have permission to %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Unable to %(action)s: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Delete"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Deleted"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "The attribute %(attr)s does not exist on %(obj)s."
#: tables/base.py:748
msgid "No items to display."
msgstr "No items to display."
#: tables/base.py:852
msgid "Actions"
msgstr "Actions"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "No match returned for the id \"%s\"."
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Please select a row before taking that action."
#: templates/_header.html:3
msgid "Logged in as"
msgstr "Logged in as"
#: templates/_header.html:5
msgid "Help"
msgstr "Help"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "Sign Out"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "Login"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "Log In"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr "You do not have permissions to access:"
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr "Login as different user or go back to"
#: templates/auth/_login.html:17
msgid "home page"
msgstr "home page"
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "Sign In"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Info: "
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Warning: "
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Success: "
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Error: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Summary"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr "More"
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "Quota Summary"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "Used"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr "of"
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "Available Instances"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "Available vCPUs"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "Available RAM"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr "Available volumes"
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr "Available volume storage"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Current Project"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Select a month to query its usage"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Submit"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Active Instances"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "Active RAM"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "This Month's VCPU-Hours"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "This Month's GB-Hours"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Cancel"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "No Limit"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Available"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "Password must be between 8 and 18 characters."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "Cute Cats"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Fierce Cats"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Cats"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Kittens"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tigers"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Dogs"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Puppies"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "My Dashboard"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "My Panel"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Admin Panel"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "Giant ants are attacking San Francisco!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr "We are now safe from ants! Go <a>here</a>!"
#: test/tests/tables.py:107
msgid "Batch"
msgstr "Batch"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "Batched"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "Item"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "Items"
#: test/tests/tables.py:118
msgid "Down"
msgstr "Down"
#: test/tests/tables.py:118
msgid "Up"
msgstr "Up"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "Downed"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "Upped"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr "No Actions Table"
#: test/tests/tables.py:684
msgid "Single Table"
msgstr "Single Table"
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "Tab One"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "Delayed Tab"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "Disabled Tab"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "Disallowed Tab"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "Tab With My Table"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "Recoverable Error Tab"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Project"
#: test/tests/workflows.py:44
msgid "User"
msgstr "User"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Test Action One"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Instance"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Test Action Two"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Test Action Three"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Admin"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Admin Action"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Incorrect format for IP address"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Invalid version for IP address"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Invalid subnet mask"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Processing..."
#: workflows/base.py:467
msgid "All available"
msgstr "All available"
#: workflows/base.py:468
msgid "Members"
msgstr "Members"
#: workflows/base.py:469
msgid "None available."
msgstr "None available."
#: workflows/base.py:470
msgid "No members."
msgstr "No members."
#: workflows/base.py:569
msgid "Save"
msgstr "Save"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s completed successfully."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s did not complete."

View File

@ -1,518 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Alberto Molina Coballes <alb.molina@gmail.com>, 2012-2013
# Alberto Molina Coballes <alb.molina@gmail.com>, 2012
# emujicad <emujicad@gmail.com>, 2013
# Gabriel Hurley <gabriel@strikeawe.com>, 2012
# zeus <jonathan.abdiel@gmail.com>, 2012
# Pedro Navarro Pérez <pednape@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: es\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Otro"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Por favor inicie sesión para continuar."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "No está autorizado para acceder a %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "No autorizado. Por favor ingrese de nuevo."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr "Ítem de Navegación"
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr "Seleccionar una %s para navegar."
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "La contraseña no se ha aceptado"
#: tables/actions.py:349
msgid "Filter"
msgstr "Filtrar"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:561
msgid "N/A"
msgstr "N/A"
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "No tiene permiso para %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "No ha sido posible %(action)s: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Borrar"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Borrado"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "El atributo %(attr)s no existe en %(obj)s."
#: tables/base.py:748
msgid "No items to display."
msgstr "No hay ítems que mostrar"
#: tables/base.py:852
msgid "Actions"
msgstr "Acciones"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Ninguna coincidencia para el id \"%s\"."
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Por favor, seleccione una fila antes de realizar la acción."
#: templates/_header.html:3
msgid "Logged in as"
msgstr "Identificado como"
#: templates/_header.html:5
msgid "Help"
msgstr "Ayuda"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "Salir"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "Ingresar"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "Ingresar"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr "No tiene permisos para acceder:"
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr "Ingresar como otro usuario o volver"
#: templates/auth/_login.html:17
msgid "home page"
msgstr "Inicio"
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "Darse de alta"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Info:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Aviso:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Correcto:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Error: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Resumen"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Mostrando %(counter)s item"
msgstr[1] "Mostrando %(counter)s items"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr "Más"
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "Resumen de cuotas"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "Usado"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr "de"
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "Instancias disponibles"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "vCPUs disponibles"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "RAM Disponible"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr "Volúmenes disponibles"
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr "Almacenamiento de volúmenes disponible"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Mostrando %(nav_items)s item"
msgstr[1] "Mostrando %(nav_items)s ítems"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Mostrando %(content_items)s item"
msgstr[1] "Mostrando %(content_items)s ítems"
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Proyecto Actual"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Seleccione un mes para el que solicitar uso"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Enviar"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Instancias Activas"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "RAM Activa"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "Horas VCPU de este mes"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "Horas GB de este mes"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Cancelar"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "Sin límite"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Disponible"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d byte"
msgstr[1] "%(size)d bytes"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
msgstr[1] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "La contraseña debe tener entre 8 y 18 caracteres."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "Gatos bonitos"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Gatos feroces"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Gatos"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Gatitos"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tigres"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Perros"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Perritos"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "Mi Dashboard"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "Mi Panel"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Panel de Administración"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "¡Hormigas gigantes están atacando Sevilla!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr "¡Estamos a salvo de las hormigas! ¡Vaya <a>aquí</a>!"
#: test/tests/tables.py:107
msgid "Batch"
msgstr "Lote"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "Por lotes"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "Ítem"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "Ítems"
#: test/tests/tables.py:118
msgid "Down"
msgstr "Abajo"
#: test/tests/tables.py:118
msgid "Up"
msgstr "Arriba"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "Bajados"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "Subidos"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr "No hay tabla de acciones"
#: test/tests/tables.py:684
msgid "Single Table"
msgstr "Tabla única"
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "Pestaña Uno"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "Pestaña Retrasada"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "Pestaña Deshabilitada"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "Pestaña no Permitida"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "Pestaña con mi tabla"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "Pestaña de errores recuperables"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Proyecto"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Usuario"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Acción de prueba Uno"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Instancia"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Acción de prueba Dos"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Acción de prueba Tres"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Admin"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Acción de Administrador"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Formato incorrecto de dirección IP"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Versión no válida de dirección IP"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Máscara de red no válida"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Procesando..."
#: workflows/base.py:467
msgid "All available"
msgstr "Todas disponibles"
#: workflows/base.py:468
msgid "Members"
msgstr "Miembros"
#: workflows/base.py:469
msgid "None available."
msgstr "Ninguna disponible."
#: workflows/base.py:470
msgid "No members."
msgstr "Sin miembros."
#: workflows/base.py:569
msgid "Save"
msgstr "Guardar"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s completado correctamente."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s no completado."

View File

@ -1,74 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -1,513 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# copard <ari.karhunen@pard.co>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-05-02 12:39+0000\n"
"Last-Translator: copard <ari.karhunen@pard.co>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fi_FI\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Toinen"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Kirjaudu jatkaaksesi"
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "Et ole oikeutettu päästäksesi %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Ei oikeutettu: Yritä kirjautumista uudelleen."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr "Navigaatio yksikkö"
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr "valitse %s selataksesi."
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "Salasanaa ei hyväksytty"
#: tables/actions.py:349
msgid "Filter"
msgstr "Filtteri"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:561
msgid "N/A"
msgstr "N/A"
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "Sinulla ei ole oikeuksia %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Ei voida %(action)s: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Poista"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Poistettu"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "Arvoa %(attr)s ei löydy %(obj)s. "
#: tables/base.py:748
msgid "No items to display."
msgstr "Ei näytettävää."
#: tables/base.py:852
msgid "Actions"
msgstr "Toiminnot"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "ID \"%s\" ei palauttanut osumia."
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Valitse rivi ennen toiminnon tekemistä."
#: templates/_header.html:3
msgid "Logged in as"
msgstr "Kirjauduttu käyttäjänä"
#: templates/_header.html:5
msgid "Help"
msgstr "Apua"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "Kirjaudu Ulos"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "Kirjaudu"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "Kirjaudu"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr "Teillä ei ole oikeuksia päästäksenne:"
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr "Kirjaudu toisena käyttäjänä palataksesi"
#: templates/auth/_login.html:17
msgid "home page"
msgstr "kotisivu"
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "Kirjaudu"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Info:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Varoitus:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Onnistui:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Virhe:"
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "yhteenveto"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Näytetään kohde %(counter)s "
msgstr[1] "Näytetään %(counter)s kohdetta"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr "Lisää"
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "Kiintiö yleiskatsaus"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "käytetty"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr "kohteesta"
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "Saatavilla olevat Instanssit"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "Saatavilla olevat vCPU:t"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "Saatavilla oleva RAM"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr "Saatavilla olevat verkkolevyt"
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr "Saatavilla oleva verkkolevykapasiteetti"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Näytetään %(nav_items)s kohde"
msgstr[1] "Näytetään %(nav_items)s kohdetta"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Näytetään %(content_items)s kohde"
msgstr[1] "Näytetään %(content_items)s kohdetta"
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Nykyinen projekti"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Valitse kuukausi katsoaksesi käyttöä"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Lähetä"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Aktivoi instanssit"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "Käytetty RAM"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "Tämän kuukauden VCPU-tunnit"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "Tämän kuukauden GB-tunnit"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Keskeytä"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "Ei rajaa"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Saatavilla"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d tavu"
msgstr[1] "%(size)d tavua"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
msgstr[1] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "Salasanan täytyy olla 8 ja 18 merkin väliltä."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "meow meow kissat."
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Hurjia kisuja"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Kisuja"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Pikku kisuja"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tiikereitä roar."
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Koiria"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Pentuja. aww.."
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "Minun hallintapaneeli"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "Minun Paneeli"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Admin paneeli"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "YAY. Let the giant ants attack San FernandSisco."
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr "Olemme turvassa muurahaisilta, MENE <a>tänne</a>|"
#: test/tests/tables.py:107
msgid "Batch"
msgstr "Sarja"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "Sarjoitettu"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "kohta"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "kohdat"
#: test/tests/tables.py:118
msgid "Down"
msgstr "Alhaalla"
#: test/tests/tables.py:118
msgid "Up"
msgstr "Ylhäällä"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "Pudotettu"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "Nostettu"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr "Ei tehtävätauluja"
#: test/tests/tables.py:684
msgid "Single Table"
msgstr "Yksittäinen taulu"
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "Ensimmäinen Tab"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "Viivästetty Tab"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "Poistettu Tab"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "Estettu Tab"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "Tab minun taulussa"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "Palautettava Virhe Tab"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Projekti"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Käyttäjä"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "ensimmäinen Testitoimi"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Insanssi"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "toinen testitoimi"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "kolmas testitoimi"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Admin"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Admin toimi"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Väärä formaatti IP-osoitteelle."
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Väärä versio IP-osoitteesta"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Väärä aliverkonpeite."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Käsitellään..."
#: workflows/base.py:467
msgid "All available"
msgstr "Kaikki saatavilla"
#: workflows/base.py:468
msgid "Members"
msgstr "Käyttäjät"
#: workflows/base.py:469
msgid "None available."
msgstr "Ei mitään saatavilla."
#: workflows/base.py:470
msgid "No members."
msgstr "Ei käyttäjiä"
#: workflows/base.py:569
msgid "Save"
msgstr "Tallenna"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s tehtiin onnistuneesti."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s ei valmistunut."

View File

@ -1,510 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2012-05-08 20:22+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: base.py:424
msgid "Other"
msgstr ""
#: decorators.py:55
msgid "Please log in to continue."
msgstr ""
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr ""
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr ""
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr ""
#: tables/actions.py:349
msgid "Filter"
msgstr ""
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:611
msgid "Delete"
msgstr ""
#: tables/actions.py:612
msgid "Deleted"
msgstr ""
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr ""
#: tables/base.py:748
msgid "No items to display."
msgstr ""
#: tables/base.py:852
msgid "Actions"
msgstr ""
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr ""
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr ""
#: templates/_header.html:3
msgid "Logged in as"
msgstr ""
#: templates/_header.html:5
msgid "Help"
msgstr ""
#: templates/_header.html:7
msgid "Sign Out"
msgstr ""
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr ""
#: templates/auth/_login.html:4
msgid "Log In"
msgstr ""
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr ""
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr ""
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr ""
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr ""
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr ""
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr ""
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr ""
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr ""
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr ""
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr ""
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr ""
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr ""
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr ""
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr ""
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr ""
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr ""
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr ""
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr ""
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr ""
#: templatetags/branding.py:35
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr ""
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr ""
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr ""
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr ""
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr ""
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr ""
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr ""
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr ""
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr ""
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr ""
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr ""
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr ""
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr ""
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr ""
#: test/tests/base.py:45
msgid "My Panel"
msgstr ""
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr ""
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr ""
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr ""
#: test/tests/tables.py:108
msgid "Batched"
msgstr ""
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr ""
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr ""
#: test/tests/tables.py:118
msgid "Down"
msgstr ""
#: test/tests/tables.py:118
msgid "Up"
msgstr ""
#: test/tests/tables.py:119
msgid "Downed"
msgstr ""
#: test/tests/tables.py:119
msgid "Upped"
msgstr ""
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr ""
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr ""
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr ""
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr ""
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr ""
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr ""
#: test/tests/workflows.py:43
msgid "Project"
msgstr ""
#: test/tests/workflows.py:44
msgid "User"
msgstr ""
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr ""
#: test/tests/workflows.py:61
msgid "Instance"
msgstr ""
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr ""
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr ""
#: test/tests/workflows.py:77
msgid "Admin"
msgstr ""
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr ""
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr ""
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr ""
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr ""
#: workflows/base.py:71
msgid "Processing..."
msgstr ""
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr ""
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr ""
#: workflows/base.py:569
msgid "Save"
msgstr ""
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr ""
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr ""

View File

@ -1,74 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -1,515 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012
# kelemeng <kelemeng@gnome.hu>, 2013
# marton.kiss <marton.kiss@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: hu\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Egyéb"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Lépjen be a folytatáshoz."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "Nincsen engedélyezve a hozzáférés ehhez: %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Jogosulatlan hozzáférés. Próbáljon meg belépni újra."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr "Navigációs elem"
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr "A böngészéshez válasszon egy %s elemet"
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "A jelszó nincs elfogadva"
#: tables/actions.py:349
msgid "Filter"
msgstr "Szűrő"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:561
msgid "N/A"
msgstr "---"
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "Nincsen hozzáférése ehhez: %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Nem hajtható végre: %(action)s: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "Törlés"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Törölve"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "A(z) %(attr)s attribútum nem létezik ezen: %(obj)s."
#: tables/base.py:748
msgid "No items to display."
msgstr "Nincs megjeleníthető elem."
#: tables/base.py:852
msgid "Actions"
msgstr "Műveletek"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Nincs találat a(z) „%s” azonosítóhoz."
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Válasszon ki egy sort a művelet végrehajtása előtt."
#: templates/_header.html:3
msgid "Logged in as"
msgstr "Belépve mint"
#: templates/_header.html:5
msgid "Help"
msgstr "Súgó"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "Kijelentkezés"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "Bejelentkezés"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "Bejelentkezés"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr "Nincs jogosultsága elérni:"
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr "Jelentkezzen be másik felhasználóként, vagy lépjen vissza a"
#: templates/auth/_login.html:17
msgid "home page"
msgstr "kezdőlapra"
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "Bejelentkezés"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Információ: "
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Figyelmeztetés: "
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Siker: "
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Hiba: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Összegzés"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "%(counter)s tétel megjelenítése"
msgstr[1] "%(counter)s tétel megjelenítése"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr "Több"
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "Kvóta összefoglaló"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "Használt"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr "/"
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "Elérhető példányok"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "Elérhető vCPU-k"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "Szabad memória"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr "Elérhető kötetek"
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr "Elérhető tárhely"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "%(nav_items)s tétel megjelenítése"
msgstr[1] "%(nav_items)s tétel megjelenítése"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "%(content_items)s tétel megjelenítése"
msgstr[1] "%(content_items)s tétel megjelenítése"
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Jelenlegi projekt"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "Válasszon egy hónapot a felhasználási adatok lekéréséhez"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "Beküldés"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Aktív példányok"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "Aktív memória"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "Havi vCPU-óra felhasználás"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "Havi GB-óra felhasználás"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Mégse"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "Nincs korlát"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Elérhető"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d bájt"
msgstr[1] "%(size)d bájt"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
msgstr[1] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "A jelszónak 8 és 18 karakter közöttinek kell lennie."
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "Cuki macskák"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Morcos macskák"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Macskák"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Kismacskák"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tigrisek"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Kutyák"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Kutyakölykök"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "Vezérlőpult"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "Panel"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Adminisztrációs panel"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "Óriás hangyák támadják San Francisco-t!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr "Biztonságban vagyunk a hangyáktól! Lépjen <a>ide</a>!"
#: test/tests/tables.py:107
msgid "Batch"
msgstr "Kötegelt"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "Kötegelt"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "Tétel"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "Tételek"
#: test/tests/tables.py:118
msgid "Down"
msgstr "Le"
#: test/tests/tables.py:118
msgid "Up"
msgstr "Fel"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "Csökkentve"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "Növelve"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr "Nincs műveletek táblázat"
#: test/tests/tables.py:684
msgid "Single Table"
msgstr "Egy tábla"
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "Első fül"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "Késleltetett fül"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "Letiltott fül"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "Nem engedélyezett fül"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "Saját táblázatom füle"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "Helyreállítható hiba fül"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Projekt"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Felhasználó"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Első teszt művelet"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Példány"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Második teszt művelet"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Harmadik teszt művelet"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Adminisztrátor"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Adminisztrátori művelet"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Helytelen IP-cím formátum"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Érvénytelen IP-cím változat"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Érvénytelen alhálózati maszk"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Feldolgozás…"
#: workflows/base.py:467
msgid "All available"
msgstr "Összes elérhető"
#: workflows/base.py:468
msgid "Members"
msgstr "Tagok"
#: workflows/base.py:469
msgid "None available."
msgstr "Nincs elérhető."
#: workflows/base.py:470
msgid "No members."
msgstr "Nincsenek tagok."
#: workflows/base.py:569
msgid "Save"
msgstr "Mentés"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s sikeresen befejeződött."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s nem fejeződött be."

View File

@ -1,515 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Loris Strozzini <lstrozzini@gmail.com>, 2012
# Salvatore Orlando <sorlando@nicira.com>, 2012
# Stefano Maffulli <smaffulli@gmail.com>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: it\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Altro"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Accedi per continuare"
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "Accesso non autorizzato a %s"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Non autorizzato. Ritentare il login."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "La password non è stata accettata."
#: tables/actions.py:349
msgid "Filter"
msgstr "Filtro"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "Non si dispone dei permessi per %(action)s: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:611
msgid "Delete"
msgstr "Elimina"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Eliminato"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "L'attributo %(attr)s non esiste in %(obj)s"
#: tables/base.py:748
msgid "No items to display."
msgstr "Nessun elemento da visualizzare"
#: tables/base.py:852
msgid "Actions"
msgstr "Azioni"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Nessuna corrispondenza restituita per l'identificativo \"%s\""
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "Per cortesia, selezionare una riga prima di eseguire tale azione."
#: templates/_header.html:3
msgid "Logged in as"
msgstr ""
#: templates/_header.html:5
msgid "Help"
msgstr ""
#: templates/_header.html:7
msgid "Sign Out"
msgstr ""
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr ""
#: templates/auth/_login.html:4
msgid "Log In"
msgstr ""
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr ""
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr ""
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr ""
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Attenzione:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Successo:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Errore:"
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "Riepilogo"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr ""
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr ""
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr ""
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr ""
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "Progetto corrente"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr ""
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr ""
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "Istanze attive"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr ""
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr ""
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr ""
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Annulla"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr ""
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr ""
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr ""
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr ""
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr ""
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr ""
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr ""
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr ""
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "Gattini carini"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Gattacci feroci"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Gatti"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Micini"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tigri"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Cani"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Cucciolini"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr ""
#: test/tests/base.py:45
msgid "My Panel"
msgstr ""
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr ""
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "Scarrafoni giganti stanno attaccando Napoli!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr ""
#: test/tests/tables.py:108
msgid "Batched"
msgstr ""
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr ""
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr ""
#: test/tests/tables.py:118
msgid "Down"
msgstr ""
#: test/tests/tables.py:118
msgid "Up"
msgstr ""
#: test/tests/tables.py:119
msgid "Downed"
msgstr ""
#: test/tests/tables.py:119
msgid "Upped"
msgstr ""
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr ""
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr ""
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr ""
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr ""
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr ""
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr ""
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Progetto"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Utente"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr ""
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Istanza"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr ""
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr ""
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Amministratore"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr ""
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Formato incorretto per l'indirizzo IP"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Versione dell'indirizzo IP non valida"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Maschera sottorete non valida."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Attendere prego..."
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr ""
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr ""
#: workflows/base.py:569
msgid "Save"
msgstr "Salva"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s completato correttamente."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s non completato."

View File

@ -1,74 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -1,511 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Akihiro MOTOKI <amotoki@gmail.com>, 2013
# bi.yamagata <bi.yamagata@gmail.com>, 2012
# bi.yamagata <bi.yamagata@gmail.com>, 2012
# Tomoyuki KATO <tomo@dream.daynight.jp>, 2012-2013
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-05-07 10:24+0000\n"
"Last-Translator: Akihiro MOTOKI <amotoki@gmail.com>\n"
"Language-Team: Japanese (http://www.transifex.com/projects/p/openstack/language/ja/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ja\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: base.py:424
msgid "Other"
msgstr "その他"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "続行するには、ログインしてください。"
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "%s へのアクセスが許可されていません。"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "認証されていません。もう一度ログインしてください。"
#: browsers/base.py:90
msgid "Navigation Item"
msgstr "ナビゲーション項目"
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr "表示する %s を選択してください。"
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "パスワードを受け付けられません"
#: tables/actions.py:349
msgid "Filter"
msgstr "フィルター"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr "%(data_type)sの%(action)s"
#: tables/actions.py:561
msgid "N/A"
msgstr "N/A"
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "%(action)s を実行する権限がありません: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "%(action)s を実行できません: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "削除"
#: tables/actions.py:612
msgid "Deleted"
msgstr "削除しました"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "%(obj)s に 属性 %(attr)s が存在しません。"
#: tables/base.py:748
msgid "No items to display."
msgstr "表示する項目がありません。"
#: tables/base.py:852
msgid "Actions"
msgstr "アクション"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "ID \"%s\" に一致するものがありません。"
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr "このアクションを実行する前に、対象を選択してください。"
#: templates/_header.html:3
msgid "Logged in as"
msgstr "次の役割でログイン中"
#: templates/_header.html:5
msgid "Help"
msgstr "ヘルプ"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "ログアウト"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "ログイン"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr "ログイン"
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr "アクセスする権限がありません: "
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr "他のユーザーとしてログインしてください。または、戻ってください。"
#: templates/auth/_login.html:17
msgid "home page"
msgstr "ホームページ"
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr "ログイン"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "情報: "
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "警告: "
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "成功: "
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "エラー: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "概要"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "%(counter)s 項目の表示中"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr "さらに"
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "クォータ概要"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "使用済み"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr "/"
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr "利用可能なインスタンス"
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr "利用可能な仮想 CPU"
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "利用可能なメモリー"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr "利用可能なボリューム"
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr "利用可能なボリュームストレージ"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "%(nav_items)s 項目の表示中"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "%(content_items)s 項目の表示中"
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr "現在のプロジェクト"
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr "使用量を問い合わせる月の選択"
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "送信"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "稼働中のインスタンス"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "使用中のメモリー"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "今月の仮想 CPU 時間"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "今月の GB 時間"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "取り消し"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "制限なし"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "利用可能"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d バイト"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr "パスワードは 8 文字から 18 文字である必要があります。"
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "かわいいネコ"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "獰猛なネコ"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "ネコ"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "子ネコ"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "トラ"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "イヌ"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "子イヌ"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "マイダッシュボード"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "マイパネル"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "管理パネル"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr "巨大な蟻がサンフランシスコを攻撃しています!"
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr "私たちは蟻から守られています ! <a>こちらへ</a> !"
#: test/tests/tables.py:107
msgid "Batch"
msgstr "バッチ"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "バッチを実行しました"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "項目"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "項目"
#: test/tests/tables.py:118
msgid "Down"
msgstr "下へ"
#: test/tests/tables.py:118
msgid "Up"
msgstr "上へ"
#: test/tests/tables.py:119
msgid "Downed"
msgstr "下げました"
#: test/tests/tables.py:119
msgid "Upped"
msgstr "上げました"
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr "アクション表がありません"
#: test/tests/tables.py:684
msgid "Single Table"
msgstr "単一テーブル"
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr "タブ 1"
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr "遅延されたタブ"
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr "無効化されたタブ"
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr "禁止されたタブ"
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr "テーブルのあるタブ"
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr "修復可能なエラーのタブ"
#: test/tests/workflows.py:43
msgid "Project"
msgstr "プロジェクト"
#: test/tests/workflows.py:44
msgid "User"
msgstr "ユーザー"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "テストアクション 1"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "インスタンス"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "テストアクション 2"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "テストアクション 3"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "管理"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "管理アクション"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "不正な形式の IP アドレス"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "無効な IP アドレスのバージョン"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "無効なサブネットマスク"
#: workflows/base.py:71
msgid "Processing..."
msgstr "処理中..."
#: workflows/base.py:467
msgid "All available"
msgstr "利用可能な全項目"
#: workflows/base.py:468
msgid "Members"
msgstr "メンバー"
#: workflows/base.py:469
msgid "None available."
msgstr "利用可能な項目がありません。"
#: workflows/base.py:470
msgid "No members."
msgstr "メンバーがいません。"
#: workflows/base.py:569
msgid "Save"
msgstr "保存"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s が正常に完了しました。"
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s が完了しませんでした。"

View File

@ -1,73 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:09+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -1,508 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Nika Chkhikvishvili <frrrredo@gmail.com>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ka_GE\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: base.py:424
msgid "Other"
msgstr "სხვა"
#: decorators.py:55
msgid "Please log in to continue."
msgstr ""
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr ""
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr ""
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr ""
#: tables/actions.py:349
msgid "Filter"
msgstr "ფილტრი"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:611
msgid "Delete"
msgstr "წაშლა"
#: tables/actions.py:612
msgid "Deleted"
msgstr "წაშლილი"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr ""
#: tables/base.py:748
msgid "No items to display."
msgstr ""
#: tables/base.py:852
msgid "Actions"
msgstr "მოქმედებები"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr ""
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr ""
#: templates/_header.html:3
msgid "Logged in as"
msgstr ""
#: templates/_header.html:5
msgid "Help"
msgstr "დახმარება"
#: templates/_header.html:7
msgid "Sign Out"
msgstr "გამისვლა"
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr "შესვლა"
#: templates/auth/_login.html:4
msgid "Log In"
msgstr ""
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr "მთავარი გვერდი"
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr ""
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr ""
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "გაფრთხილება:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "წარმატება:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr ""
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr "ჯამი"
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] ""
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr "მეტი"
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr "კვოტის ჯამი"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr "გამოყენებული"
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr ""
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr ""
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr "ხელმისაწვდომი RAM"
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr ""
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr ""
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr ""
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr ""
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr "აქტიური RAM"
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr ""
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr ""
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr ""
#: templatetags/branding.py:35
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr ""
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr ""
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] ""
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] ""
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr ""
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr ""
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr ""
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr ""
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr ""
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr ""
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr ""
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr ""
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr ""
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr ""
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr ""
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr ""
#: test/tests/base.py:45
msgid "My Panel"
msgstr "ჩემი პანელი"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "ადმინ პანელი"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr ""
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr ""
#: test/tests/tables.py:108
msgid "Batched"
msgstr ""
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr ""
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr ""
#: test/tests/tables.py:118
msgid "Down"
msgstr "ქვევით"
#: test/tests/tables.py:118
msgid "Up"
msgstr "ზევით"
#: test/tests/tables.py:119
msgid "Downed"
msgstr ""
#: test/tests/tables.py:119
msgid "Upped"
msgstr ""
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr ""
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr ""
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr ""
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr ""
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr ""
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr ""
#: test/tests/workflows.py:43
msgid "Project"
msgstr "პროექტი"
#: test/tests/workflows.py:44
msgid "User"
msgstr "მომხმარებელი"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr ""
#: test/tests/workflows.py:61
msgid "Instance"
msgstr ""
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr ""
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr ""
#: test/tests/workflows.py:77
msgid "Admin"
msgstr ""
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr ""
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr ""
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr ""
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr ""
#: workflows/base.py:71
msgid "Processing..."
msgstr ""
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr "წევრები"
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr "არ არაის წევრები."
#: workflows/base.py:569
msgid "Save"
msgstr "შენახვა"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr ""
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr ""

View File

@ -1,509 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# bluejay.kr <bluejay.ahn@gmail.com>, 2012
# Sungjin Gang <potopro@gmail.com>, 2013
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-05-03 12:23+0000\n"
"Last-Translator: Sungjin Gang <potopro@gmail.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ko_KR\n"
"Plural-Forms: nplurals=1; plural=0;\n"
#: base.py:424
msgid "Other"
msgstr "다른"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "로그인을 해주세요."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "%s에 접근 권한이 없습니다. "
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "인증되지 않았습니다. 다시 로깅해주시기 바랍니다. "
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr ""
#: tables/actions.py:349
msgid "Filter"
msgstr "필터"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr "%(action)s에 관한 권한이 없습니다.: %(objs)s"
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "%(action)s를 사용할 수 없습니다.: %(objs)s"
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:611
msgid "Delete"
msgstr "삭제"
#: tables/actions.py:612
msgid "Deleted"
msgstr "삭제했음"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr ""
#: tables/base.py:748
msgid "No items to display."
msgstr "표시할 항목이 없습니다."
#: tables/base.py:852
msgid "Actions"
msgstr "작동"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "일치하는 id \"%s\"를 반환"
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr ""
#: templates/_header.html:3
msgid "Logged in as"
msgstr ""
#: templates/_header.html:5
msgid "Help"
msgstr ""
#: templates/_header.html:7
msgid "Sign Out"
msgstr ""
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr ""
#: templates/auth/_login.html:4
msgid "Log In"
msgstr ""
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr ""
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr ""
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "정보:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "주의:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "완료:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "에러:"
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr ""
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "%(counter)s 항목 표시"
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr ""
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr ""
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr ""
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr ""
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr ""
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr ""
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr "제출"
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr "작동중인 인스턴스"
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr ""
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr "한달간 VCPU-시간"
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr "한달간 GB-시간"
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "취소"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "유한한"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "사용가능한"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d bytes"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr ""
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr ""
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr ""
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr ""
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr ""
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr ""
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr ""
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "내 대시보드"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "내 판넬"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "관리자 판넬"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr ""
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr "일괄 처리"
#: test/tests/tables.py:108
msgid "Batched"
msgstr "일괄 처리된"
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "항목"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "항목들"
#: test/tests/tables.py:118
msgid "Down"
msgstr ""
#: test/tests/tables.py:118
msgid "Up"
msgstr ""
#: test/tests/tables.py:119
msgid "Downed"
msgstr ""
#: test/tests/tables.py:119
msgid "Upped"
msgstr ""
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr ""
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr ""
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr ""
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr ""
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr ""
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr ""
#: test/tests/workflows.py:43
msgid "Project"
msgstr "프로젝트"
#: test/tests/workflows.py:44
msgid "User"
msgstr "사용자"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr ""
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "인스턴스"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr ""
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr ""
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "관리자"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "관리자 액션"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr ""
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr ""
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr ""
#: workflows/base.py:71
msgid "Processing..."
msgstr ""
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr ""
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr ""
#: workflows/base.py:569
msgid "Save"
msgstr "저장"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr ""
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr ""

View File

@ -1,73 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -1,514 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Translators:
# Translators:
# Erik-Martijn Kasimier <erik.kasimier@nouveaumedia.nl>, 2012
# Erik-Martijn Kasimier <erik.kasimier@nouveaumedia.nl>, 2012
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: https://launchpad.net/horizon\n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: 2013-04-29 08:33+0000\n"
"Last-Translator: Gabriel Hurley <gabriel@strikeawe.com>\n"
"Language-Team: English (http://www.transifex.com/projects/p/openstack/language/en/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: nl_NL\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:424
msgid "Other"
msgstr "Andere"
#: decorators.py:55
msgid "Please log in to continue."
msgstr "Log in om door te gaan."
#: decorators.py:87
#, python-format
msgid "You are not authorized to access %s"
msgstr "Je bent niet geauthoriseerd om %s te bekijken"
#: exceptions.py:283
msgid "Unauthorized. Please try logging in again."
msgstr "Je bent niet geautoriseerd. Probeer opnieuw in te loggen."
#: browsers/base.py:90
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:42
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:29
msgid "Password is not accepted"
msgstr "Wachtwoord is niet geaccepteerd"
#: tables/actions.py:349
msgid "Filter"
msgstr "Filter"
#: tables/actions.py:527
#, python-format
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:561
msgid "N/A"
msgstr ""
#: tables/actions.py:589
#, python-format
msgid "You do not have permission to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:595
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:601
#, python-format
msgid "%(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:611
msgid "Delete"
msgstr "Verwijder"
#: tables/actions.py:612
msgid "Deleted"
msgstr "Verwijderd"
#: tables/base.py:275
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr ""
#: tables/base.py:748
msgid "No items to display."
msgstr ""
#: tables/base.py:852
msgid "Actions"
msgstr "Acties"
#: tables/base.py:1035
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr ""
#: tables/base.py:1165
msgid "Please select a row before taking that action."
msgstr ""
#: templates/_header.html:3
msgid "Logged in as"
msgstr ""
#: templates/_header.html:5
msgid "Help"
msgstr ""
#: templates/_header.html:7
msgid "Sign Out"
msgstr ""
#: templates/splash.html:7 templates/auth/login.html:4
msgid "Login"
msgstr ""
#: templates/auth/_login.html:4
msgid "Log In"
msgstr ""
#: templates/auth/_login.html:14
msgid "You don't have permissions to access:"
msgstr ""
#: templates/auth/_login.html:16
msgid "Login as different user or go back to"
msgstr ""
#: templates/auth/_login.html:17
msgid "home page"
msgstr ""
#: templates/auth/_login.html:27
msgid "Sign In"
msgstr ""
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Informatie: "
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Waarschuwing: "
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Succesvol: "
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Error: "
#: templates/horizon/common/_data_table.html:54
msgid "Summary"
msgstr ""
#: templates/horizon/common/_data_table.html:63
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_data_table_row_actions.html:10
msgid "More"
msgstr ""
#: templates/horizon/common/_quota_summary.html:4
msgid "Quota Summary"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "Used"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
#: templates/horizon/common/_quota_summary.html:8
#: templates/horizon/common/_quota_summary.html:11
#: templates/horizon/common/_quota_summary.html:15
#: templates/horizon/common/_quota_summary.html:18
msgid "of"
msgstr ""
#: templates/horizon/common/_quota_summary.html:5
msgid "Available Instances"
msgstr ""
#: templates/horizon/common/_quota_summary.html:8
msgid "Available vCPUs"
msgstr ""
#: templates/horizon/common/_quota_summary.html:11
msgid "Available RAM"
msgstr ""
#: templates/horizon/common/_quota_summary.html:15
msgid "Available volumes"
msgstr ""
#: templates/horizon/common/_quota_summary.html:18
msgid "Available volume storage"
msgstr ""
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_sidebar.html:14
msgid "Current Project"
msgstr ""
#: templates/horizon/common/_usage_summary.html:5
msgid "Select a month to query its usage"
msgstr ""
#: templates/horizon/common/_usage_summary.html:9
msgid "Submit"
msgstr ""
#: templates/horizon/common/_usage_summary.html:14
msgid "Active Instances"
msgstr ""
#: templates/horizon/common/_usage_summary.html:15
msgid "Active RAM"
msgstr ""
#: templates/horizon/common/_usage_summary.html:16
msgid "This Month's VCPU-Hours"
msgstr ""
#: templates/horizon/common/_usage_summary.html:17
msgid "This Month's GB-Hours"
msgstr ""
#: templates/horizon/common/_workflow.html:33
msgid "Cancel"
msgstr "Annuleren"
#: templatetags/branding.py:35
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:109
msgid "No Limit"
msgstr "Geen limiet"
#: templatetags/horizon.py:111 templatetags/horizon.py:113
msgid "Available"
msgstr "Beschikbaar"
#: templatetags/sizeformat.py:45
#, python-format
msgid "%(size)d byte"
msgid_plural "%(size)d bytes"
msgstr[0] "%(size)d byte"
msgstr[1] "%(size)d bytes"
#: templatetags/sizeformat.py:49
#, python-format
msgid "%(size)d"
msgid_plural "%(size)d"
msgstr[0] "%(size)d"
msgstr[1] "%(size)d"
#: templatetags/sizeformat.py:52
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:55
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:58
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: test/settings.py:114
msgid "Password must be between 8 and 18 characters."
msgstr ""
#: test/test_dashboards/cats/dashboard.py:8
msgid "Cute Cats"
msgstr "Schattige katten"
#: test/test_dashboards/cats/dashboard.py:14
msgid "Fierce Cats"
msgstr "Felle katten"
#: test/test_dashboards/cats/dashboard.py:19
msgid "Cats"
msgstr "Katten"
#: test/test_dashboards/cats/kittens/panel.py:9
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:3
#: test/test_dashboards/cats/kittens/templates/kittens/index.html:6
msgid "Kittens"
msgstr "Kittens"
#: test/test_dashboards/cats/tigers/panel.py:9
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:3
#: test/test_dashboards/cats/tigers/templates/tigers/index.html:6
msgid "Tigers"
msgstr "Tijgers"
#: test/test_dashboards/dogs/dashboard.py:7
msgid "Dogs"
msgstr "Honden"
#: test/test_dashboards/dogs/puppies/panel.py:9
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:3
#: test/test_dashboards/dogs/puppies/templates/puppies/index.html:6
msgid "Puppies"
msgstr "Puppy's"
#: test/tests/base.py:39
msgid "My Dashboard"
msgstr "Mijn dashboard"
#: test/tests/base.py:45
msgid "My Panel"
msgstr "Alle instances"
#: test/tests/base.py:51
msgid "Admin Panel"
msgstr "Beheerderspaneel"
#: test/tests/messages.py:32
msgid "Giant ants are attacking San Francisco!"
msgstr ""
#: test/tests/messages.py:46
msgid "We are now safe from ants! Go <a>here</a>!"
msgstr ""
#: test/tests/tables.py:107
msgid "Batch"
msgstr ""
#: test/tests/tables.py:108
msgid "Batched"
msgstr ""
#: test/tests/tables.py:109 test/tests/tables.py:120
msgid "Item"
msgstr "Item"
#: test/tests/tables.py:110 test/tests/tables.py:121
msgid "Items"
msgstr "Items"
#: test/tests/tables.py:118
msgid "Down"
msgstr ""
#: test/tests/tables.py:118
msgid "Up"
msgstr ""
#: test/tests/tables.py:119
msgid "Downed"
msgstr ""
#: test/tests/tables.py:119
msgid "Upped"
msgstr ""
#: test/tests/tables.py:187
msgid "No Actions Table"
msgstr ""
#: test/tests/tables.py:684
msgid "Single Table"
msgstr ""
#: test/tests/tabs.py:36
msgid "Tab One"
msgstr ""
#: test/tests/tabs.py:42
msgid "Delayed Tab"
msgstr ""
#: test/tests/tabs.py:49
msgid "Disabled Tab"
msgstr ""
#: test/tests/tabs.py:58
msgid "Disallowed Tab"
msgstr ""
#: test/tests/tabs.py:76
msgid "Tab With My Table"
msgstr ""
#: test/tests/tabs.py:85
msgid "Recoverable Error Tab"
msgstr ""
#: test/tests/workflows.py:43
msgid "Project"
msgstr "Project"
#: test/tests/workflows.py:44
msgid "User"
msgstr "Gebruiker"
#: test/tests/workflows.py:47
msgid "Test Action One"
msgstr "Testactie één"
#: test/tests/workflows.py:61
msgid "Instance"
msgstr "Instance"
#: test/tests/workflows.py:64
msgid "Test Action Two"
msgstr "Testactie twee"
#: test/tests/workflows.py:72
msgid "Test Action Three"
msgstr "Testactie drie"
#: test/tests/workflows.py:77
msgid "Admin"
msgstr "Beheerder"
#: test/tests/workflows.py:80
msgid "Admin Action"
msgstr "Beheerdersactie"
#: utils/fields.py:46
msgid "Incorrect format for IP address"
msgstr "Onjuist formaat IP adres"
#: utils/fields.py:47
msgid "Invalid version for IP address"
msgstr "Invalide versie IP adres"
#: utils/fields.py:48
msgid "Invalid subnet mask"
msgstr "Invalide subnet mask"
#: workflows/base.py:71
msgid "Processing..."
msgstr "Verwerken..."
#: workflows/base.py:467
msgid "All available"
msgstr ""
#: workflows/base.py:468
msgid "Members"
msgstr ""
#: workflows/base.py:469
msgid "None available."
msgstr ""
#: workflows/base.py:470
msgid "No members."
msgstr ""
#: workflows/base.py:569
msgid "Save"
msgstr "Opslaan"
#: workflows/base.py:570
#, python-format
msgid "%s completed successfully."
msgstr "%s succesvol afgerond."
#: workflows/base.py:571
#, python-format
msgid "%s did not complete."
msgstr "%s was niet voltooid."

View File

@ -1,73 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-03-12 04:08+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: static/horizon/js/horizon.forms.js:47
msgid "Additional information here..."
msgstr ""
#: static/horizon/js/horizon.forms.js:53
msgid "Filter"
msgstr ""
#: static/horizon/js/horizon.instances.js:28
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:125
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.modals.js:159 static/horizon/js/horizon.tabs.js:9
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.modals.js:178
msgid "An error occurred. Please try again."
msgstr ""
#: static/horizon/js/horizon.tables.js:47
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:145
msgid "You have selected "
msgstr ""
#: static/horizon/js/horizon.tables.js:158
msgid "Confirm "
msgstr ""
#: static/horizon/js/horizon.tables.js:159
msgid "Please confirm your selection. This action cannot be undone."
msgstr ""
#: static/horizon/js/horizon.tables.js:173
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.tables.js:216
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

Some files were not shown because too many files have changed in this diff Show More