[WIP] Horizon dashboard for tap-as-a-service

This is an implementation of a dashboard discussed on ML[1][2].
- Network topology view is based on Kilo[3].
- Tap service panel is added to Horizon dashboard.
- Command options provided on CLI are supported.

[1] http://lists.openstack.org/pipermail/openstack-dev/2016-March/088735.html
[2] http://lists.openstack.org/pipermail/openstack-dev/2016-March/089330.html
[3] https://www.openstack.org/software/kilo/

Co-Authored-By: Soichi Shigeta <shigeta.soichi@jp.fujitsu.com>
Change-Id: Iceec7f873584978533907420d50988f286bf6d7a
This commit is contained in:
Kazuhiro Suzuki 2016-03-22 19:48:05 +09:00
parent 1d58d20ede
commit e75d42025a
1769 changed files with 403161 additions and 0 deletions

31
taas_dashboard/.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
*.mo
*.pyc
*.sw?
*.sqlite3
*.lock
.environment_version
.selenium_log
.coverage*
.noseids
.DS_STORE
coverage.xml
nosetests.xml
pep8.txt
pylint.txt
reports
horizon.egg-info
openstack_dashboard/local/local_settings.py
openstack_dashboard/local/local_settings.diff
openstack_dashboard/local/.secret_key_store
openstack_dashboard/test/.secret_key_store
openstack_dashboard/wsgi/horizon.wsgi
doc/build/
doc/source/sourcecode
/static/
.venv
.tox
build
dist
AUTHORS
ChangeLog
tags

View File

@ -0,0 +1,5 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/horizon.git
defaultbranch=stable/kilo

36
taas_dashboard/.jshintrc Normal file
View File

@ -0,0 +1,36 @@
{
"browser": true,
"trailing": true,
"evil": true,
"globals": {
"horizon": false,
"jQuery": false,
"$": false,
"angular": false,
"module": false,
"inject": false,
"describe": false,
"beforeEach": false,
"afterEach": false,
"beforeAll": false,
"afterAll": false,
"it": false,
"expect": false,
"spyOn": false,
"jasmine": false,
"d3": false,
"pluralidx": false,
"gettext": false,
"ngettext": false,
"gettext_noop": false,
"pgettext": false,
"npgettext": false,
"interpolate": false,
"get_format": false
}
}

13
taas_dashboard/.mailmap Normal file
View File

@ -0,0 +1,13 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>
<ghe@debian.org> <ghe.rivero@stackops.com>
<jake@ansolabs.com> <admin@jakedahn.com>
<launchpad@markgius.com> <mgius7096@gmail.com>
<yorik.sar@gmail.com> <yorik@ytaraday>
<jeblair@hp.com> <james.blair@rackspace.com>
<ke.wu@ibeca.me> <ke.wu@nebula.com>
Zhongyue Luo <zhongyue.nah@intel.com> <lzyeval@gmail.com>
Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
Kun Huang <gareth@unitedstack.com> <academicgareth@gmail.com>
Zhenguo Niu <zhenguo@unitedstack.com> <Niu.ZGlinux@gmail.com>

42
taas_dashboard/.pylintrc Normal file
View File

@ -0,0 +1,42 @@
# The format of this file isn't really documented; just use --generate-rcfile
[MASTER]
# Add <file or directory> to the black list. It should be a base name, not a
# path. You may set this option multiple times.
ignore=test
[Messages Control]
# NOTE(justinsb): We might want to have a 2nd strict pylintrc in future
# C0111: Don't require docstrings on every method
# W0511: TODOs in code comments are fine.
# W0142: *args and **kwargs are fine.
# W0622: Redefining id is fine.
disable=C0111,W0511,W0142,W0622
[Basic]
# Variable names can be 1 to 31 characters long, with lowercase and underscores
variable-rgx=[a-z_][a-z0-9_]{0,30}$
# Argument names can be 2 to 31 characters long, with lowercase and underscores
argument-rgx=[a-z_][a-z0-9_]{1,30}$
# Method names should be at least 3 characters long
# and be lowecased with underscores
method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$
# Module names matching keystone-* are ok (files in bin/)
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(keystone-[a-z0-9_-]+))$
# Don't require docstrings on tests.
no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$
[Design]
max-public-methods=100
min-public-methods=0
max-args=6
[Variables]
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
# _ is used by our localization
additional-builtins=_

21
taas_dashboard/.tx/config Normal file
View File

@ -0,0 +1,21 @@
[main]
host = https://www.transifex.com
[horizon.horizon-translations-kilo]
file_filter = horizon/locale/<lang>/LC_MESSAGES/django.po
source_file = horizon/locale/en/LC_MESSAGES/django.po
source_lang = en
type = PO
[horizon.openstack-dashboard-translations-kilo]
file_filter = openstack_dashboard/locale/<lang>/LC_MESSAGES/django.po
source_file = openstack_dashboard/locale/en/LC_MESSAGES/django.po
source_lang = en
type = PO
[horizon.horizon-js-translations-kilo]
file_filter = horizon/locale/<lang>/LC_MESSAGES/djangojs.po
source_file = horizon/locale/en/LC_MESSAGES/djangojs.po
source_lang = en
type = PO

View File

@ -0,0 +1,18 @@
If you would like to contribute to the development of OpenStack,
you must follow the steps documented at:
http://docs.openstack.org/developer/horizon/contributing.html
or http://docs.openstack.org/infra/manual/developers.html#development-workflow
Once those steps have been completed, changes to OpenStack
should be submitted for review via the Gerrit tool, following
the workflow documented at:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/horizon

View File

@ -0,0 +1,14 @@
Horizon Style Commandments
==========================
- Step 1: Read the OpenStack Style Commandments
http://docs.openstack.org/developer/hacking/
- Step 2: Read [hacking] section in tox.ini to find the list of names which
can be imported directly without triggering the "H302: import only modules"
flake8 warning
- Step 3: Read on
Horizon Specific Commandments
-----------------------------
- Read the Horizon contributing documentation at http://docs.openstack.org/developer/horizon/contributing.html

176
taas_dashboard/LICENSE Normal file
View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -0,0 +1,19 @@
recursive-include doc *.py *.rst *.css *.js *.html *.conf *.jpg *.gif *.png *.css_t
recursive-include horizon *.html *.css *.js *.csv *.template *.tmpl *.mo *.po
recursive-include openstack_dashboard *.html *.js *.less *.mo *.po *.example *.eot *.svg *.ttf *.woff *.png *.ico *.wsgi *.gif *.csv *.template
recursive-include tools *.py *.sh
include AUTHORS
include ChangeLog
include LICENSE
include Makefile
include manage.py
include README.rst
include run_tests.sh
include tox.ini
include doc/Makefile
include doc/source/_templates/.placeholder
include requirements.txt
include test-requirements.txt
exclude openstack_dashboard/local/local_settings.py

24
taas_dashboard/Makefile Normal file
View File

@ -0,0 +1,24 @@
PYTHON=`which python`
DESTDIR=/
PROJECT=horizon
all:
@echo "make test - Run tests"
@echo "make source - Create source package"
@echo "make install - Install on local system"
@echo "make buildrpm - Generate a rpm package"
@echo "make clean - Get rid of scratch and byte files"
source:
$(PYTHON) setup.py sdist $(COMPILE)
install:
$(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE)
buildrpm:
$(PYTHON) setup.py bdist_rpm --post-install=rpm/postinstall --pre-uninstall=rpm/preuninstall
clean:
$(PYTHON) setup.py clean
rm -rf build/ MANIFEST
find . -name '*.pyc' -delete

51
taas_dashboard/README.rst Normal file
View File

@ -0,0 +1,51 @@
=============================
Horizon (OpenStack Dashboard)
=============================
Horizon is a Django-based project aimed at providing a complete OpenStack
Dashboard along with an extensible framework for building new dashboards
from reusable components. The ``openstack_dashboard`` module is a reference
implementation of a Django site that uses the ``horizon`` app to provide
web-based interactions with the various OpenStack projects.
* Release management: https://launchpad.net/horizon
* Blueprints and feature specifications: https://blueprints.launchpad.net/horizon
* Issue tracking: https://bugs.launchpad.net/horizon
Using Horizon
=============
See ``doc/source/topics/install.rst`` about how to install Horizon
in your OpenStack setup. It describes the example steps and
has pointers for more detailed settings and configurations.
It is also available at http://docs.openstack.org/developer/horizon/topics/install.html.
Getting Started for Developers
==============================
``doc/source/quickstart.rst`` or
http://docs.openstack.org/developer/horizon/quickstart.html
describes how to setup Horizon development environment and start development.
Building Contributor Documentation
==================================
This documentation is written by contributors, for contributors.
The source is maintained in the ``doc/source`` directory using
`reStructuredText`_ and built by `Sphinx`_
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _Sphinx: http://sphinx-doc.org/
* Building Automatically::
$ ./run_tests.sh --docs
* Building Manually::
$ tools/with_venv.sh sphinx-build doc/source doc/build/html
Results are in the ``doc/build/html`` directory

153
taas_dashboard/doc/Makefile Normal file
View File

@ -0,0 +1,153 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = build
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
help:
@echo "Please use \`make <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

@ -0,0 +1,440 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# Horizon documentation build configuration file, created by
# sphinx-quickstart on Thu Oct 27 11:38:59 2011.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
from __future__ import print_function
import os
import sys
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
sys.path.insert(0, ROOT)
# This is required for ReadTheDocs.org, but isn't a bad idea anyway.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openstack_dashboard.settings')
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 == 'tests.py':
continue
if filename.endswith(".py"):
# remove the pieces of the root
elements = root.split(os.path.sep)
# replace the leading "." with the module name
elements[0] = module_name
# and get the base module name
base, extension = os.path.splitext(filename)
if not (base == "__init__"):
elements.append(base)
result = ".".join(elements)
# print result
modlist.append(result)
return modlist
RSTDIR = os.path.abspath(os.path.join(BASE_DIR, "sourcecode"))
SRCS = [('horizon', ROOT),
('openstack_dashboard', ROOT)]
EXCLUDED_MODULES = ('horizon.test',
'openstack_dashboard.enabled',
'openstack_dashboard.test',
'openstack_dashboard.openstack.common',
)
CURRENT_SOURCES = {}
if not(os.path.exists(RSTDIR)):
os.mkdir(RSTDIR)
CURRENT_SOURCES[RSTDIR] = ['autoindex.rst']
INDEXOUT = open(os.path.join(RSTDIR, "autoindex.rst"), "w")
INDEXOUT.write("""
=================
Source Code Index
=================
.. contents::
:depth: 1
:local:
""")
for modulename, path in SRCS:
sys.stdout.write("Generating source documentation for %s\n" %
modulename)
INDEXOUT.write("\n%s\n" % modulename.capitalize())
INDEXOUT.write("%s\n" % ("=" * len(modulename),))
INDEXOUT.write(".. toctree::\n")
INDEXOUT.write(" :maxdepth: 1\n")
INDEXOUT.write("\n")
MOD_DIR = os.path.join(RSTDIR, modulename)
CURRENT_SOURCES[MOD_DIR] = []
if not(os.path.exists(MOD_DIR)):
os.mkdir(MOD_DIR)
for module in find_autodoc_modules(modulename, path):
if any([module.startswith(exclude) for exclude
in EXCLUDED_MODULES]):
print("Excluded module %s." % module)
continue
mod_path = os.path.join(path, *module.split("."))
generated_file = os.path.join(MOD_DIR, "%s.rst" % module)
INDEXOUT.write(" %s/%s\n" % (modulename, module))
# Find the __init__.py module if this is a directory
if os.path.isdir(mod_path):
source_file = ".".join((os.path.join(mod_path, "__init__"),
"py",))
else:
source_file = ".".join((os.path.join(mod_path), "py"))
CURRENT_SOURCES[MOD_DIR].append("%s.rst" % module)
# Only generate a new file if the source has changed or we don't
# have a doc file to begin with.
if not os.access(generated_file, os.F_OK) or (
os.stat(generated_file).st_mtime <
os.stat(source_file).st_mtime):
print("Module %s updated, generating new documentation."
% module)
FILEOUT = open(generated_file, "w")
header = "The :mod:`%s` Module" % module
FILEOUT.write("%s\n" % ("=" * len(header),))
FILEOUT.write("%s\n" % header)
FILEOUT.write("%s\n" % ("=" * len(header),))
FILEOUT.write(".. automodule:: %s\n" % module)
FILEOUT.write(" :members:\n")
FILEOUT.write(" :undoc-members:\n")
FILEOUT.write(" :show-inheritance:\n")
FILEOUT.write(" :noindex:\n")
FILEOUT.close()
INDEXOUT.close()
# Delete auto-generated .rst files for sources which no longer exist
for directory, subdirs, files in list(os.walk(RSTDIR)):
for old_file in files:
if old_file not in CURRENT_SOURCES.get(directory, []):
print("Removing outdated file for %s" % old_file)
os.remove(os.path.join(directory, old_file))
write_autodoc_index()
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings.
# They can be extensions coming with Sphinx (named 'sphinx.ext.*')
# or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.pngmath',
'sphinx.ext.viewcode',
'oslosphinx',
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.rst'
# The encoding of source files.
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'Horizon'
copyright = u'2012, OpenStack Foundation'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = horizon.version.version_info.version_string()
# The full version, including alpha/beta/rc tags.
release = horizon.version.version_info.release_string()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
# today = ''
# Else, today_fmt is used as the format for a strftime call.
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['**/#*', '**~', '**/#*#']
# The reST default role (used for this markup: `text`)
# to use for all documents.
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
primary_domain = 'py'
nitpicky = False
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# html_theme_path = ['.']
# html_theme = '_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
html_theme_options = {
"nosidebar": "false"
}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
# html_last_updated_fmt = '%b %d, %Y'
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
html_last_updated_fmt = os.popen(git_cmd).read()
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
# html_additional_pages = {}
# If false, no module index is generated.
# html_domain_indices = True
# If false, no index is generated.
# html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
# If true, links to the reST sources are added to the pages.
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Horizondoc'
# -- Options for LaTeX output -------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
# 'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index', 'Horizon.tex', u'Horizon Documentation',
u'OpenStack Foundation', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
# latex_use_parts = False
# If true, show page references after internal links.
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
# latex_appendices = []
# If false, no module index is generated.
# latex_domain_indices = True
# -- Options for manual page output -------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'horizon', u'Horizon Documentation',
[u'OpenStack'], 1)
]
# If true, show URL addresses after external links.
# man_show_urls = False
# -- Options for Texinfo output -----------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Horizon', u'Horizon Documentation', u'OpenStack',
'Horizon', 'One line description of project.', 'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
# texinfo_appendices = []
# If false, no module index is generated.
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
# texinfo_show_urls = 'footnote'
# -- Options for Epub output --------------------------------------------------
# Bibliographic Dublin Core info.
epub_title = u'Horizon'
epub_author = u'OpenStack'
epub_publisher = u'OpenStack'
epub_copyright = u'2012, OpenStack'
# The language of the text. It defaults to the language option
# or en if the language is not set.
# epub_language = ''
# The scheme of the identifier. Typical schemes are ISBN or URL.
# epub_scheme = ''
# The unique identifier of the text. This can be an ISBN number
# or the project homepage.
# epub_identifier = ''
# A unique identification for the text.
# epub_uid = ''
# A tuple containing the cover image and cover page html template filenames.
# epub_cover = ()
# HTML files that should be inserted before the pages created by sphinx.
# The format is a list of tuples containing the path and title.
# epub_pre_files = []
# HTML files shat should be inserted after the pages created by sphinx.
# The format is a list of tuples containing the path and title.
# epub_post_files = []
# A list of files that should not be packed into the epub file.
# epub_exclude_files = []
# The depth of the table of contents in toc.ncx.
# epub_tocdepth = 3
# Allow duplicate toc entries.
# epub_tocdup = True

View File

@ -0,0 +1,559 @@
==================
Contributing Guide
==================
First and foremost, thank you for wanting to contribute! It's the only way
open source works!
Before you dive into writing patches, here are some of the basics:
* Project page: http://launchpad.net/horizon
* Bug tracker: https://bugs.launchpad.net/horizon
* Source code: https://github.com/openstack/horizon
* Code review: https://review.openstack.org/#q,status:open+project:openstack/horizon,n,z
* Continuous integration:
* Jenkins: https://jenkins.openstack.org
* Zuul: http://status.openstack.org/zuul
* IRC Channel: #openstack-horizon on Freenode.
Making Contributions
====================
Getting Started
---------------
We'll start by assuming you've got a working checkout of the repository (if
not then please see the :doc:`quickstart`).
Second, you'll need to take care of a couple administrative tasks:
#. Create an account on Launchpad.
#. Sign the `OpenStack Contributor License Agreement`_ and follow the associated
instructions to verify your signature.
#. Join the `Horizon Developers`_ team on Launchpad.
#. Follow the `instructions for setting up git-review`_ in your
development environment.
Whew! Got all that? Okay! You're good to go.
Ways To Contribute
------------------
The easiest way to get started with Horizon's code is to pick a bug on
Launchpad that interests you, and start working on that. Alternatively, if
there's an OpenStack API feature you would like to see implemented in Horizon
feel free to try building it.
If those are too big, there are lots of great ways to get involved without
plunging in head-first:
* Report bugs, triage new tickets, and review old tickets on
the `bug tracker`_.
* Propose ideas for improvements via `Launchpad Blueprints`_, via the
mailing list on the project page, or on IRC.
* Write documentation!
* Write unit tests for untested code!
* Help improve the `User Experience Design`_ or contribute to the `Persona Working Group`_.
.. _`bug tracker`: https://bugs.launchpad.net/horizon
.. _`Launchpad Blueprints`: https://blueprints.launchpad.net/horizon
.. _`User Experience Design`: https://wiki.openstack.org/wiki/UX#Getting_Started
.. _`Persona Working Group`: https://wiki.openstack.org/wiki/Personas
Choosing Issues To Work On
--------------------------
In general, if you want to write code, there are three cases for issues
you might want to work on:
#. Confirmed bugs
#. Approved blueprints (features)
#. New bugs you've discovered
If you have an idea for a new feature that isn't in a blueprint yet, it's
a good idea to write the blueprint first so you don't end up writing a bunch
of code that may not go in the direction the community wants.
For bugs, open the bug first, but if you can reproduce the bug reliably and
identify its cause then it's usually safe to start working on it. However,
getting independent confirmation (and verifying that it's not a duplicate)
is always a good idea if you can be patient.
After You Write Your Patch
--------------------------
Once you've made your changes, there are a few things to do:
* Make sure the unit tests pass: ``./run_tests.sh``
* Make sure PEP8 is clean: ``./run_tests.sh --pep8``
* Make sure your code is ready for translation: ``./run_tests.sh --pseudo de`` See the Translatability section below for details.
* Make sure your code is up-to-date with the latest master: ``git pull --rebase``
* Finally, run ``git review`` to upload your changes to Gerrit for review.
The Horizon core developers will be notified of the new review and will examine
it in a timely fashion, either offering feedback or approving it to be merged.
If the review is approved, it is sent to Jenkins to verify the unit tests pass
and it can be merged cleanly. Once Jenkins approves it, the change will be
merged to the master repository and it's time to celebrate!
.. _`OpenStack Contributor License Agreement`: http://wiki.openstack.org/CLA
.. _`OpenStack Contributors`: https://launchpad.net/~openstack-cla
.. _`Horizon Developers`: https://launchpad.net/~horizon
.. _`instructions for setting up git-review`: http://docs.openstack.org/infra/manual/developers.html#development-workflow
Etiquette
=========
The community's guidelines for etiquette are fairly simple:
* Treat everyone respectfully and professionally.
* If a bug is "in progress" in the bug tracker, don't start working on it
without contacting the author. Try on IRC, or via the launchpad email
contact link. If you don't get a response after a reasonable time, then go
ahead. Checking first avoids duplicate work and makes sure nobody's toes
get stepped on.
* If a blueprint is assigned, even if it hasn't been started, be sure you
contact the assignee before taking it on. These larger issues often have a
history of discussion or specific implementation details that the assignee
may be aware of that you are not.
* Please don't re-open tickets closed by a core developer. If you disagree with
the decision on the ticket, the appropriate solution is to take it up on
IRC or the mailing list.
* Give credit where credit is due; if someone helps you substantially with
a piece of code, it's polite (though not required) to thank them in your
commit message.
Translatability
===============
Horizon gets translated into multiple languages. The pseudo translation tool
can be used to verify that code is ready to be translated. The pseudo tool
replaces a language's translation with a complete, fake translation. Then
you can verify that your code properly displays fake translations to validate
that your code is ready for translation.
Running the pseudo translation tool
-----------------------------------
#. Make sure your English po file is up to date: ``./run_tests.sh --makemessages``
#. Run the pseudo tool to create pseudo translations. For example, to replace the German translation with a pseudo translation: ``./run_tests.sh --pseudo de``
#. Compile the catalog: ``./run_tests.sh --compilemessages``
#. Run your development server.
#. Log in and change to the language you pseudo translated.
It should look weird. More specifically, the translatable segments are going
to start and end with a bracket and they are going to have some added
characters. For example, "Log In" will become "[~Log In~您好яшçあ]"
This is useful because you can inspect for the following, and consider if your
code is working like it should:
* If you see a string in English it's not translatable. Should it be?
* If you see brackets next to each other that might be concatenation. Concatenation
can make quality translations difficult or impossible. See
https://wiki.openstack.org/wiki/I18n/TranslatableStrings#Use_string_formating_variables.2C_never_perform_string_concatenation
for additional information.
* If there is unexpected wrapping/truncation there might not be enough
space for translations.
* If you see a string in the proper translated language, it comes from an
external source. (That's not bad, just sometimes useful to know)
* If you get new crashes, there is probably a bug.
Don't forget to cleanup any pseudo translated po files. Those don't get merged!
Code Style
==========
As a project, Horizon adheres to code quality standards.
Python
------
We follow PEP8_ for all our Python code, and use ``pep8.py`` (available
via the shortcut ``./run_tests.sh --pep8``) to validate that our code
meets proper Python style guidelines.
.. _PEP8: http://www.python.org/dev/peps/pep-0008/
Django
------
Additionally, we follow `Django's style guide`_ for templates, views, and
other miscellany.
.. _Django's style guide: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
JavaScript
----------
The following standards are divided into required and recommended sections.
Our main goal in establishing these best practices is to have code that is
reliable, readable, and maintainable.
Required
~~~~~~~~
**Reliable**
* The code has to work on the stable and latest versions of Firefox, Chrome,
Safari, and Opera web browsers, and on Microsoft Internet Explorer 9 and
later.
* If you turned compression off during development via ``COMPRESS_ENABLED =
False`` in local_settings.py, re-enable compression and test your code
before submitting.
* Use ``===`` as opposed to ``==`` for equality checks. The ``==`` will do a
type cast before comparing, which can lead to unwanted results.
.. Note ::
If typecasting is desired, explicit casting is preferred to keep the
meaning of your code clear.
* Keep document reflows to a minimum. DOM manipulation is expensive, and can
become a performance issue. If you are accessing the DOM, make sure that you
are doing it in the most optimized way. One example is to build up a document
fragment and then append the fragment to the DOM in one pass instead of doing
multiple smaller DOM updates.
* Use “strict”, enclosing each JavaScript file inside a self-executing
function. The self-executing function keeps the strict scoped to the file,
so its variables and methods are not exposed to other JavaScript files in
the product.
.. Note ::
Using strict will throw exceptions for common coding errors, like
accessing global vars, that normally are not flagged.
Example:
.. code ::
(function(){
'use strict';
// code...
})();
* Use ``forEach`` | ``each`` when looping whenever possible. AngularJS, and
jQuery both provide for each loops that provide both iteration and scope.
AngularJS:
.. code ::
angular.forEach(objectToIterateOver, function(value, key) {
// loop logic
});
jQuery:
.. code ::
$.each(objectToIterateOver, function( key, value ) {
// loop logic
});
* Do not put variables or functions in the global namespace. There are several
reasons why globals are bad, one being that all JavaScript included in an
application runs in the same scope. The issue with that is if another script
has the same method or variable names they overwrite each other.
* Always put ``var`` in front of your variables. Not putting ``var`` in front
of a variable puts that variable into the global space, see above.
* Do not use ``eval( )``. The eval (expression) evaluates the expression
passed to it. This can open up your code to security vulnerabilities and
other issues.
* Do not use '``with`` object {code}'. The ``with`` statement is used to access
properties of an object. The issue with ``with`` is that its execution is not
consistent, so by reading the statement in the code it is not always clear
how it is being used.
**Readable & Maintainable**
* Give meaningful names to methods and variables.
* Avoid excessive nesting.
* Avoid HTML and CSS in JS code. HTML and CSS belong in templates and
stylesheets respectively. For example:
* In our HTML files, we should focus on layout.
1. Reduce the small/random ``<script>`` and ``<style>`` elements in HTML.
2. Avoid in-lining styles into element in HTML. Use attributes and
classes instead.
* In our JS files, we should focus on logic rather than attempting to
manipulate/style elements.
1. Avoid statements such as ``element.css({property1,property2...})`` they
belong in a CSS class.
2. Avoid statements such as ``$("<div><span>abc</span></div>")`` they
belong in a HTML template file. Use ``show`` | ``hide`` | ``clone``
elements if dynamic content is required.
3. Avoid using classes for detection purposes only, instead, defer to
attributes. For example to find a div:
.. code ::
<div class="something"></div>
$(".something").html("Don't find me this way!");
Is better found like:
.. code ::
<div data-something></div>
$("div[data-something]").html("You found me correctly!");
* Avoid commented-out code.
* Avoid dead code.
**Performance**
* Avoid creating instances of the same object repeatedly within the same scope.
Instead, assign the object to a variable and re-use the existing object. For
example:
.. code ::
$(document).on('click', function() { /* do something. */ });
$(document).on('mouseover', function() { /* do something. */ });
A better approach:
.. code ::
var $document = $(document);
$document.on('click', function() { /* do something. */ });
$document.on('mouseover', function() { /* do something. */ });
In the first approach a jQuery object for ``document`` is created each time.
The second approach creates only one jQuery object and reuses it. Each object
needs to be created, uses memory, and needs to be garbage collected.
Recommended
~~~~~~~~~~~
**Readable & Maintainable**
* Put a comment at the top of every file explaining what the purpose of this
file is when the naming is not obvious. This guideline also applies to
methods and variables.
* Source-code formatting (or “beautification”) is recommended but should be
used with caution. Keep in mind that if you reformat an entire file that was
not previously formatted the same way, it will mess up the diff during the
code review. It is best to use a formatter when you are working on a new file
by yourself, or with others who are using the same formatter. You can also
choose to format a selected portion of a file only. Instructions for setting
up JSHint for Eclipse, Sublime Text, Notepad++ and WebStorm/PyCharm are
provided_.
* Use 2 spaces for code indentation.
* Use ``{ }`` for ``if``, ``for``, ``while`` statements, and don't combine them
on one line.
.. code ::
// Do this //Not this // Not this
if(x) { if(x) if(x) y =x;
y=x; y=x;
}
* Use JSHint in your development environment.
AngularJS
---------
The following standards are divided into required and recommended sections.
Required
~~~~~~~~
* Organization: Define your Angular app under the root Angular folder (such
as ``horizon/static/horizon/js/angular/hz.table.js``). If your application is
small enough you can choose to lump your Controllers, Directives, Filters,
etc.. all in the one file. But if you find your file is growing too large and
readability is becoming an issue, consider moving functionality into their
own files under sub folders as described in the Recommended section.
* Separate presentation and business logic. Controllers are for business logic,
and directives for presentation.
* Controllers and Services should not contain DOM references. Directives
should.
* Services are singletons and contain logic independent of view.
* Scope is not the model (model is your JavaScript Objects). The scope
references the model.
* Read-only in templates.
* Write-only in controllers.
* Since Django already uses ``{{ }}``, use ``{$ $}`` or ``{% verbatim %}``
instead.
* For localization in JavaScript files use either ``gettext`` or ``ngettext``.
Only those two methods are recognized by our tools and will be included in
the .po file after running ``./run_tests --makemessages``.
.. code ::
// recognized
gettext("translatable text");
ngettext("translatable text");
// not recognized
var _ = gettext;
_('translatable text');
$window.gettext('translatable text');
* For localization of AngularJS templates in Horizon, there are a couple of
ways to do it.
* Using ``gettext`` or ``ngettext`` function that is passed from server to
client. If you're only translating a few things, this methodology is ok
to use.
* Use an Angular directive that will fetch a django template instead of a
static HTML file. The advantage here is that you can now use
``{% trans %}`` and anything else Django has to offer. You can also cache
the page according to the locale if you know that the content is static.
Recommended
~~~~~~~~~~~
* Use these directories: filters, directives, controllers, and templates.
.. Note ::
When you use the directory name, the file name does not have to include
words like "directive" or "filter".
* Put "Ctrl" on the end of a controller file name.
* Don't use variables like "app" that are at the highest level in the file,
when Angular gives an alternative. For example use function chaining:
.. code ::
angular.module('my_module')
.controller('my_controller', ['$scope', function($scope) {
// controller code
}]).service('my_service', ['$scope', function($scope) {
// service code
}]);
JSHint
------
JSHint is a great tool to be used during your code editing to improve
JavaScript quality by checking your code against a configurable list of checks.
Therefore, JavaScript developers should configure their editors to use JSHint
to warn them of any such errors so they can be addressed. Since JSHint has a
ton of configuration options to choose from, links are provided below to the
options Horizon wants enforced along with the instructions for setting up
JSHint for Eclipse, Sublime Text, Notepad++ and WebStorm/PyCharm.
JSHint configuration file: `.jshintrc`_
Instructions for setting up JSHint: `JSHint setup instructions`_
.. Note ::
JSHint is part of the automated unit tests performed by Jenkins. The
automated test use the default configurations, which are less strict than
the configurations we recommended to run in your local development
environment.
.. _.jshintrc: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig/Settings#.jshintrc
.. _JSHint setup instructions: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig
.. _provided: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig
CSS
---
Style guidelines for CSS are currently quite minimal. Do your best to make the
code readable and well-organized. Two spaces are preferred for indentation
so as to match both the JavaScript and HTML files.
JavaScript and CSS libraries
----------------------------
We do not bundle the third-party code within Horizon's source tree anymore, any
code that is still there is just left over and will be cleaned up and packaged
properly eventually. What we do instead, is packaging the required files as
XStatic Python packages and adding them as dependencies to Horizon. In
particular, when you need to add a new third-party JavaScript or CSS library to
Horizon, follow those steps:
1. Check if the library is already packaged as Xstatic on PyPi, by searching
for the library name. If it already is, go to step 5. If it is, but not in
the right version, contact the original packager.
2. Package the library as an Xstatic package by following the instructions in
Xstatic documentation_.
3. `Create a new repository on StackForge`_. Use "xstatic-core" and
"xstatic-ptl" groups for the ACLs. Make sure to include the
``publish-to-pypi`` job.
4. `Setup PyPi`_ to allow OpenStack to publish your package.
5. `Tag your release`_. That will cause it to be automatically packaged and
released to PyPi.
6. Add the package to global-requirements_. Make sure to mention the license.
7. Add the package to Horizon's ``requirements.txt`` file, to its
``settings.py``, and to the ``_scripts.html`` or ``_stylesheets.html``
templates. Make sure to keep the order alphabetic.
.. _documentation: http://xstatic.rtfd.org/en/latest/packaging.html
.. _`Create a new repository on StackForge`: http://docs.openstack.org/infra/manual/creators.html
.. _global-requirements: https://github.com/openstack/requirements/blob/master/global-requirements.txt
.. _`Tag your release`: http://docs.openstack.org/infra/manual/drivers.html#tagging-a-release
.. _`Setup PyPi`: http://docs.openstack.org/infra/manual/creators.html#give-openstack-permission-to-publish-releases
.. warning::
Note that once a package is released, you can not "unrealease" it. You
should never attempt to modify, delete or rename a released package without
a lot of careful planning and feedback from all projects that use it.
For the purpose of fixing packaging mistakes, XStatic has the build number
mechanism. Simply fix the error, increment the build number and release the
newer package.
HTML
----
Again, readability is paramount; however be conscientious of how the browser
will handle whitespace when rendering the output. Two spaces is the preferred
indentation style to match all front-end code.
Documentation
-------------
Horizon's documentation is written in reStructuredText and uses Sphinx for
additional parsing and functionality, and should follow
standard practices for writing reST. This includes:
* Flow paragraphs such that lines wrap at 80 characters or less.
* Use proper grammar, spelling, capitalization and punctuation at all times.
* Make use of Sphinx's autodoc feature to document modules, classes
and functions. This keeps the docs close to the source.
* Where possible, use Sphinx's cross-reference syntax (e.g.
``:class:`~horizon.foo.Bar```) when referring to other Horizon components.
The better-linked our docs are, the easier they are to use.
Be sure to generate the documentation before submitting a patch for review.
Unexpected warnings often appear when building the documentation, and slight
reST syntax errors frequently cause links or cross-references not to work
correctly.
Conventions
-----------
Simply by convention, we have a few rules about naming:
* The term "project" is used in place of Keystone's "tenant" terminology
in all user-facing text. The term "tenant" is still used in API code to
make things more obvious for developers.
* The term "dashboard" refers to a top-level dashboard class, and "panel" to
the sub-items within a dashboard. Referring to a panel as a dashboard is
both confusing and incorrect.

View File

@ -0,0 +1,45 @@
==========================
Frequently Asked Questions
==========================
What is the relationship between ``Dashboards``, ``Panels``, and navigation?
The navigational structure is strongly encouraged to flow from
``Dashboard`` objects as top-level navigation items to ``Panel`` objects as
sub-navigation items as in the current implementation. Template tags
are provided to automatically generate this structure.
That said, you are not required to use the provided tools and can write
templates and URLconfs by hand to create any desired structure.
Does a panel have to be an app in ``INSTALLED_APPS``?
A panel can live in any Python module. It can be a standalone which ties
into an existing dashboard, or it can be contained alongside others within
a larger dashboard "app". There is no strict enforcement here. Python
is "a language for consenting adults." A module containing a Panel does
not need to be added to ``INSTALLED_APPS``, but this is a common and
convenient way to load a standalone panel.
Could I hook an external service into a panel using, for example, an iFrame?
Panels are just entry-points to hook views into the larger dashboard
navigational structure and enforce common attributes like RBAC. The
view and corresponding templates can contain anything you would like,
including iFrames.
What does this mean for visual design?
The ability to add an arbitrary number of top-level navigational items
(``Dashboard`` objects) poses a new design challenge. Horizon's lead
designer has taken on the challenge of providing a reference design
for Horizon which supports this possibility.
What browsers are supported?
Horizon is primarily tested and supported on the latest version of Firefox,
the latest version of Chrome, and IE9+. Issues related to Safari and Opera
will also be considered. The list of supported browsers and versions is
informally documented on the `Browser Support wiki page
<https://wiki.openstack.org/wiki/Horizon/BrowserSupport>`_.

View File

@ -0,0 +1,24 @@
========
Glossary
========
Horizon
The OpenStack dashboard project. Also the name of the top-level
Python object which handles registration for the app.
Dashboard
A Python class representing a top-level navigation item (e.g. "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

@ -0,0 +1,132 @@
..
Copyright 2012 OpenStack Foundation
All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
License for the specific language governing permissions and limitations
under the License.
========================================
Horizon: The OpenStack Dashboard Project
========================================
Introduction
============
Horizon is the canonical implementation of `OpenStack's Dashboard
<https://github.com/openstack/horizon>`_, which provides a web based user
interface to OpenStack services including Nova, Swift, Keystone, etc.
For a more in-depth look at Horizon and its architecture, see the
:doc:`Introduction to Horizon <intro>`.
To learn what you need to know to get going, see the :doc:`quickstart`.
Using Horizon
=============
How to use Horizon in your own projects.
.. toctree::
:maxdepth: 1
topics/install
topics/deployment
topics/settings
topics/customizing
Developer Docs
==============
For those wishing to develop Horizon itself, or go in-depth with building
your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes,
the following documentation is provided.
General information
-------------------
Brief guides to areas of interest and importance when developing Horizon.
.. toctree::
:maxdepth: 1
intro
quickstart
topics/tutorial
contributing
testing
Topic Guides
------------
Information on how to work with specific areas of Horizon can be found in
the following topic guides.
.. toctree::
:maxdepth: 1
topics/workflows
topics/tables
topics/policy
topics/testing
topics/table_actions
API Reference
-------------
In-depth documentation for Horizon and its APIs.
.. toctree::
:maxdepth: 1
ref/run_tests
ref/horizon
ref/workflows
ref/tables
ref/tabs
ref/forms
ref/middleware
ref/context_processors
ref/decorators
ref/exceptions
ref/test
ref/local_conf
Source Code Reference
---------------------
Auto-generated reference for the complete source code.
.. toctree::
:maxdepth: 1
sourcecode/autoindex
Release Notes
=============
.. toctree::
:glob:
:maxdepth: 1
releases/*
Information
===========
.. toctree::
:maxdepth: 1
faq
glossary
* :ref:`genindex`
* :ref:`modindex`

View File

@ -0,0 +1,121 @@
==============
Horizon Basics
==============
Values
======
"Think simple" as my old master used to say - meaning reduce
the whole of its parts into the simplest terms, getting back
to first principles.
-- Frank Lloyd Wright
Horizon holds several key values at the core of its design and architecture:
* Core Support: Out-of-the-box support for all core OpenStack projects.
* Extensible: Anyone can add a new component as a "first-class citizen".
* Manageable: The core codebase should be simple and easy-to-navigate.
* Consistent: Visual and interaction paradigms are maintained throughout.
* Stable: A reliable API with an emphasis on backwards-compatibility.
* Usable: Providing an *awesome* interface that people *want* to use.
The only way to attain and uphold those ideals is to make it *easy* for
developers to implement those values.
History
=======
Horizon started life as a single app to manage OpenStack's compute project.
As such, all it needed was a set of views, templates, and API calls.
From there it grew to support multiple OpenStack projects and APIs gradually,
arranged rigidly into "dash" and "syspanel" groupings.
During the "Diablo" release cycle an initial plugin system was added using
signals to hook in additional URL patterns and add links into the "dash"
and "syspanel" navigation.
This incremental growth served the goal of "Core Support" phenomenally, but
left "Extensible" and "Manageable" behind. And while the other key values took
shape of their own accord, it was time to re-architect for an extensible,
modular future.
The Current Architecture & How It Meets Our Values
==================================================
At its core, **Horizon should be a registration pattern for
applications to hook into**. Here's what that means and how it is
implemented in terms of our values:
Core Support
------------
Horizon ships with three central dashboards, a "User Dashboard", a
"System Dashboard", and a "Settings" dashboard. Between these three they
cover the core OpenStack applications and deliver on Core Support.
The Horizon application also ships with a set of API abstractions
for the core OpenStack projects in order to provide a consistent, stable set
of reusable methods for developers. Using these abstractions, developers
working on Horizon don't need to be intimately familiar with the APIs of
each OpenStack project.
Extensible
----------
A Horizon dashboard application is based around the :class:`~horizon.Dashboard`
class that provides a consistent API and set of capabilities for both
core OpenStack dashboard apps shipped with Horizon and equally for third-party
apps. The :class:`~horizon.Dashboard` class is treated as a top-level
navigation item.
Should a developer wish to provide functionality within an existing dashboard
(e.g. adding a monitoring panel to the user dashboard) the simple registration
pattern makes it possible to write an app which hooks into other dashboards
just as easily as creating a new dashboard. All you have to do is import the
dashboard you wish to modify.
Manageable
----------
Within the application, there is a simple method for registering a
:class:`~horizon.Panel` (sub-navigation items). Each panel contains the
necessary logic (views, forms, tests, etc.) for that interface. This granular
breakdown prevents files (such as ``api.py``) from becoming thousands of
lines long and makes code easy to find by correlating it directly to the
navigation.
Consistent
----------
By providing the necessary core classes to build from, as well as a
solid set of reusable templates and additional tools (base form classes,
base widget classes, template tags, and perhaps even class-based views)
we can maintain consistency across applications.
Stable
------
By architecting around these core classes and reusable components we
create an implicit contract that changes to these components will be
made in the most backwards-compatible ways whenever possible.
Usable
------
Ultimately that's up to each and every developer that touches the code,
but if we get all the other goals out of the way then we are free to focus
on the best possible experience.
.. seealso::
:doc:`Quickstart <quickstart>`
A short guide to getting started with using Horizon.
:doc:`Frequently Asked Questions <faq>`
Common questions and answers.
:doc:`Glossary <glossary>`
Common terms and their definitions.

View File

@ -0,0 +1,314 @@
==========
Quickstart
==========
.. Note ::
This section has been tested for Horizon on Ubuntu (12.04-64) and Fedora-based (RHEL 6.4) distributions. Feel free to add notes and any changes according to your experiences or operating system.
Linux Systems
=============
Install the prerequisite packages.
On Ubuntu::
> sudo apt-get install git python-dev python-virtualenv libssl-dev libffi-dev
On Fedora-based distributions (e.g., Fedora/RHEL/CentOS/Scientific Linux)::
> sudo yum install gcc git-core python-devel python-virtualenv openssl-devel libffi-devel which
Setup
=====
To setup a Horizon development environment simply clone the Horizon git
repository from http://github.com/openstack/horizon and execute the
``run_tests.sh`` script from the root folder (see :doc:`ref/run_tests`)::
> git clone https://github.com/openstack/horizon.git
> cd horizon
> ./run_tests.sh
.. note::
Running ``run_tests.sh`` will build a virtualenv, ``.venv``, where all the
python dependencies for Horizon are installed and referenced. After the
dependencies are installed, the unit test suites in the Horizon repo will be
executed. There should be no errors from the tests.
Next you will need to setup your Django application config by copying ``openstack_dashboard/local/local_settings.py.example`` to ``openstack_dashboard/local/local_settings.py``. To do this quickly you can use the following command::
> cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
.. note::
To add new settings or customize existing settings, modify the ``local_settings.py`` file.
Horizon assumes a single end-point for OpenStack services which defaults to
the local host (127.0.0.1), as is the default in DevStack. If this is not the
case change the ``OPENSTACK_HOST`` setting in the
``openstack_dashboard/local/local_settings.py`` file, to the actual IP address
of the OpenStack end-point Horizon should use.
You can save changes you made to
``openstack_dashboard/local/local_settings.py`` with the following command::
> python manage.py migrate_settings --gendiff
.. note::
This creates a ``local_settings.diff`` file which is a diff between
``local_settings.py`` and ``local_settings.py.example``
If you upgrade Horizon, you might need to update your
``openstack_dashboard/local/local_settings.py`` file with new parameters from
``openstack_dashboard/local/local_settings.py.example`` to do so, first update
Horizon::
> git remote update && git pull --ff-only origin master
Then update your ``openstack_dashboard/local/local_settings.py`` file::
> mv openstack_dashboard/local/local_settings.py openstack_dashboard/local/local_settings.py.old
> python manage.py migrate_settings
.. note::
This applies ``openstack_dashboard/local/local_settings.diff`` on
``openstack_dashboard/local/local_settings.py.example`` to regenerate an
``openstack_dashboard/local/local_settings.py`` file.
The migration can sometimes have difficulties to migrate some settings, if
this happens you will be warned with a conflict message pointing to an
``openstack_dashboard/local/local_settings.py_Some_DateTime.rej`` file.
In this file, you will see the lines which could not be automatically
changed and you will have to redo only these few changes manually instead
of modifying the full
``openstack_dashboard/local/local_settings.py.example`` file.
When all settings have been migrated, it is safe to regenerate a clean diff in
order to prevent Conflicts for future migrations::
> mv openstack_dashboard/local/local_settings.diff openstack_dashboard/local/local_settings.diff.old
> python manage.py migrate_settings --gendiff
To start the Horizon development server use ``run_tests.sh``::
> ./run_tests.sh --runserver localhost:9000
.. note::
The default port for runserver is 8000 which is already consumed by
heat-api-cfn in DevStack. If not running in DevStack
`./run_tests.sh --runserver` will start the test server at
`http://localhost:8000`.
.. note::
The ``run_tests.sh`` script provides wrappers around ``manage.py``.
For more information on manage.py which is a django, see
`https://docs.djangoproject.com/en/dev/ref/django-admin/`
Once the Horizon server is running, point a web browser to http://localhost:9000
or to the IP and port the server is listening for.
.. note::
The ``DevStack`` project (http://devstack.org/) can be used to install
an OpenStack development environment from scratch. For a local.conf that
enables most services that Horizon supports managing see
:doc:`local.conf <ref/local_conf>`
.. note::
The minimum required set of OpenStack services running includes the
following:
* Nova (compute, api, scheduler, and network)
* Glance
* Keystone
* Neutron (unless nova-network is used)
Horizon provides optional support for other services.
See :ref:`system-requirements-label` for the supported services.
If Keystone endpoint for a service is configured, Horizon detects it
and enables its support automatically.
Editing Horizon's Source
========================
Although DevStack installs and configures an instance of Horizon when running
stack.sh, the preferred development setup follows the instructions above on the
server/VM running DevStack. The are several advantages to maintaining a
separate copy of the Horizon repo, rather than editing the devstack installed
copy.
* Source code changes aren't as easily lost when running unstack.sh/stack.sh
* The development server picks up source code changes (other than JavaScript
and CSS due to compression and compilation) while still running.
* Log messages and print statements go directly to the console.
* Debugging with pdb becomes much simpler to interact with.
.. Note::
JavaScript and CSS changes require a development server restart. Also,
forcing a refresh of the page (e.g. using Shift-F5) in the browser is
required to pull down non-cached versions of the CSS and JavaScript. The
default setting in Horizon is to do compilation and compression of these
files at server startup. If you have configured your local copy to do
offline compression, more steps are required.
Horizon's Structure
===================
This project is a bit different from other OpenStack projects in that it has
two very distinct components underneath it: ``horizon``, and
``openstack_dashboard``.
The ``horizon`` directory holds the generic libraries and components that can
be used in any Django project.
The ``openstack_dashboard`` directory contains a reference Django project that
uses ``horizon``.
For development, both pieces share an environment which (by default) is
built with the ``tools/install_venv.py`` script. That script creates a
virtualenv and installs all the necessary packages.
If dependencies are added to either ``horizon`` or ``openstack_dashboard``,
they should be added to ``requirements.txt``.
.. important::
If you do anything which changes the environment (adding new dependencies
or renaming directories are both great examples) be sure to increment the
``environment_version`` counter in :doc:`run_tests.sh <ref/run_tests>`.
Project
=======
Dashboard configuration
-----------------------
To add a new dashboard to your project, you need to add a configuration file to
``openstack_dashboard/local/enabled`` directory. For more information on this,
see :ref:`pluggable-settings-label`.
There is also an alternative way to add a new dashboard, by adding it to
Django's ``INSTALLED_APPS`` setting. For more information about this, see
:ref:`dashboards`. However, please note that the recommended way is to take
advantage of the pluggable settings feature.
URLs
----
Then you add a single line to your project's ``urls.py``::
url(r'', include(horizon.urls)),
Those urls are automatically constructed based on the registered Horizon apps.
If a different URL structure is desired it can be constructed by hand.
Templates
---------
Pre-built template tags generate navigation. In your ``nav.html``
template you might have the following::
{% load horizon %}
<div class='nav'>
{% horizon_main_nav %}
</div>
And in your ``sidebar.html`` you might have::
{% load horizon %}
<div class='sidebar'>
{% horizon_dashboard_nav %}
</div>
These template tags are aware of the current "active" dashboard and panel
via template context variables and will render accordingly.
Application
===========
Structure
---------
An application would have the following structure (we'll use project as
an example)::
project/
|---__init__.py
|---dashboard.py <-----Registers the app with Horizon and sets dashboard properties
|---overview/
|---images/
|-- images
|-- __init__.py
|---panel.py <-----Registers the panel in the app and defines panel properties
|-- snapshots/
|-- templates/
|-- tests.py
|-- urls.py
|-- views.py
...
...
Dashboard Classes
-----------------
Inside of ``dashboard.py`` you would have a class definition and the registration
process::
import horizon
....
# ObjectStorePanels is an example for a PanelGroup
# for panel classes in general, see below
class ObjectStorePanels(horizon.PanelGroup):
slug = "object_store"
name = _("Object Store")
panels = ('containers',)
class Project(horizon.Dashboard):
name = _("Project") # Appears in navigation
slug = "project" # Appears in URL
# panels may be strings or refer to classes, such as
# ObjectStorePanels
panels = (BasePanels, NetworkPanels, ObjectStorePanels)
default_panel = 'overview'
...
horizon.register(Project)
Panel Classes
-------------
To connect a :class:`~horizon.Panel` with a :class:`~horizon.Dashboard` class
you register it in a ``panel.py`` file like so::
import horizon
from openstack_dashboard.dashboards.project import dashboard
class Images(horizon.Panel):
name = "Images"
slug = 'images'
permissions = ('openstack.roles.admin', 'my.other.permission',)
# You could also register your panel with another application's dashboard
dashboard.Project.register(Images)
By default a :class:`~horizon.Panel` class looks for a ``urls.py`` file in the
same directory as ``panel.py`` to include in the rollup of url patterns from
panels to dashboards to Horizon, resulting in a wholly extensible, configurable
URL structure.

View File

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

View File

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

View File

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

View File

@ -0,0 +1,98 @@
=============
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

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

View File

@ -0,0 +1,70 @@
==========
local.conf
==========
Configuring DevStack for Horizon
================================
Place the following content into `devstack/local.conf` to start the services
that Horizon supports in DevStack when `stack.sh` is run.
::
[[local|localrc]]
ADMIN_PASSWORD=pass
MYSQL_PASSWORD=pass
RABBIT_PASSWORD=pass
SERVICE_PASSWORD=pass
SERVICE_TOKEN=a682f596-76f3-11e3-b3b2-e716f9080d50
# Recloning will insure that your stack is up to date. The downside
# is overhead on restarts and potentially losing a stable environment.
# If set to yes, will reclone all repos every time stack.sh is run.
# The default is no.
#RECLONE=yes
# Note: there are several network setting changes that may be
# required to get networking properly configured in your environment.
# This file is just using the defaults set up by devstack.
# For a more detailed treatment of devstack network configuration
# options, please see: http://devstack.org/guides/single-machine.html
# Enable Swift (object-store) Service without replication
enable_service s-proxy s-object s-container s-account
SWIFT_HASH=66a3d6b56c1f479c8b4e70ab5c2000f5
SWIFT_REPLICAS=1
SWIFT_DATA_DIR=$DEST/data/swift
# enabling Neutron (network) Service
# to use nova net rather than neutron, comment out the following group
disable_service n-net
enable_service q-svc
enable_service q-agt
enable_service q-dhcp
enable_service q-l3
enable_service q-meta
enable_service q-metering
enable_service neutron
enable_service q-lbaas
enable_service q-fwaas
enable_service q-vpn
# end group
# enable Sahara (data-processing) Service
enable_service sahara
# enable Trove (database) Service
enable_service trove tr-api tr-tmgr tr-cond
# enable Ceilometer (metering) Service
enable_service ceilometer-acompute ceilometer-acentral ceilometer-anotification ceilometer-collector ceilometer-api
# Set ``OFFLINE`` to ``True`` to configure ``stack.sh`` to run cleanly without
# Internet access. ``stack.sh`` must have been previously run with Internet
# access to install prerequisites and fetch repositories.
# OFFLINE=True
[[post-config|$GLANCE_API_CONF]]
[DEFAULT]
default_store=file

View File

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

View File

@ -0,0 +1,263 @@
===========================
The ``run_tests.sh`` Script
===========================
.. contents:: Contents:
:local:
Horizon ships with a script called ``run_tests.sh`` at the root of the
repository. This script provides many crucial functions for the project,
and also makes several otherwise complex tasks trivial for you as a
developer.
First Run
=========
If you start with a clean copy of the Horizon repository, the first thing
you should do is to run ``./run_tests.sh`` from the root of the repository.
This will do two things for you:
#. Set up a virtual environment for both the ``horizon`` module and
the ``openstack_dashboard`` project using ``./tools/install_venv.py``.
#. Run the tests for both ``horizon`` and ``openstack_dashboard`` using
their respective environments and verify that everything is working.
Setting up the environment the first time can take several minutes, but only
needs to be done once. If dependencies are added in the future, updating the
environments will be necessary but not as time consuming.
I just want to run the tests!
=============================
Running the full set of unit tests quickly and easily is the main goal of this
script. All you need to do is::
./run_tests.sh
Yep, that's it. However, for a more thorough test run you can include the
Selenium tests by using the ``--with-selenium`` flag::
./run_tests.sh --with-selenium
If you run horizon in a minimal installation VM, you will probably need
the following (steps for Fedora 18 minimal installation):
#. Install these packages in the VM:
``yum install xorg-x11-xauth xorg-x11-fonts-Type1.noarch``.
#. Install firefox in the VM:
``yum install firefox``.
#. Connect to the VM by ``ssh -X``
(if you run ``set|grep DISP``, you should see that the DISPLAY is set).
#. Run
``./run_tests.sh --with-selenium``.
Running a subset of tests
-------------------------
Instead of running all tests, you can specify an individual directory, file,
class, or method that contains test code.
To run the tests in the ``horizon/test/tests/tables.py`` file::
./run_tests.sh horizon.test.tests.tables
To run the tests in the `WorkflowsTests` class in
``horizon/test/tests/workflows``::
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests
To run just the `WorkflowsTests.test_workflow_view` test method::
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests.test_workflow_view
Running the integration tests
-----------------------------
The Horizon integration tests treat Horizon as a black box, and similar
to Tempest must be run against an existing OpenStack system. These
tests are not run by default.
#. Update the configuration file
`openstack_dashboard/test/integration_tests/horizon.conf` as
required (the format is similar to the Tempest configuration file).
#. Run the tests with the following command: ::
$ ./run_tests.sh --integration
Like for the unit tests, you can choose to only run a subset. ::
$ ./run_tests.sh --integration openstack_dashboard.test.integration_tests.tests.test_login
Using Dashboard and Panel Templates
===================================
Horizon has a set of convenient management commands for creating new
dashboards and panels based on basic templates.
Dashboards
----------
To create a new dashboard, run the following::
./run_tests.sh -m startdash <dash_name>
This will create a directory with the given dashboard name, a ``dashboard.py``
module with the basic dashboard code filled in, and various other common
"boilerplate" code.
Available options:
* ``--target``: the directory in which the dashboard files should be created.
Default: A new directory within the current directory.
Panels
------
To create a new panel, run the following::
./run_tests -m startpanel <panel_name>
This will create a directory with the given panel name, and ``panel.py``
module with the basic panel code filled in, and various other common
"boilerplate" code.
Available options:
* ``-d``, ``--dashboard``: The dotted python path to your dashboard app (the
module which containers the ``dashboard.py`` file.). If not specified, the
target dashboard should be specified in a pluggable settings file for the
panel.
* ``--target``: the directory in which the panel files should be created.
If the value is ``auto`` the panel will be created as a new directory inside
the dashboard module's directory structure. Default: A new directory within
the current directory.
Give me metrics!
================
You can generate various reports and metrics using command line arguments
to ``run_tests.sh``.
Coverage
--------
To run coverage reports::
./run_tests.sh --coverage
The reports are saved to ``./reports/`` and ``./coverage.xml``.
PEP8
----
You can check for PEP8 violations as well::
./run_tests.sh --pep8
The results are saved to ``./pep8.txt``.
PyLint
------
For more detailed code analysis you can run::
./run_tests.sh --pylint
The output will be saved in ``./pylint.txt``.
JsHint
------
For code analysis of JavaScript files::
./run_tests.sh --jshint
You need to have jshint installed before running the command.
Tab Characters
--------------
For those who dislike having a mix of tab characters and spaces for indentation
there's a command to check for that in Python, CSS, JavaScript and HTML files::
./run_tests.sh --tabs
This will output a total "tab count" and a list of the offending files.
Running the development server
==============================
As an added bonus, you can run Django's development server directly from
the root of the repository with ``run_tests.sh`` like so::
./run_tests.sh --runserver
This is effectively just an alias for::
./tools/with_venv.sh ./manage.py runserver
Generating the documentation
============================
You can build Horizon's documentation automatically by running::
./run_tests.sh --docs
The output is stored in ``./doc/build/html/``.
Updating the translation files
==============================
You can update all of the translation files for both the ``horizon`` app and
``openstack_dashboard`` project with a single command::
./run_tests.sh --makemessages
or, more compactly::
./run_tests.sh --m
Starting clean
==============
If you ever want to start clean with a new environment for Horizon, you can
run::
./run_tests.sh --force
That will blow away the existing environments and create new ones for you.
Non-interactive Mode
====================
There is an optional flag which will run the script in a non-interactive
(and eventually less verbose) mode::
./run_tests.sh --quiet
This will automatically take the default action for actions which would
normally prompt for user input such as installing/updating the environment.
Environment Backups
===================
To speed up the process of doing clean checkouts, running continuous
integration tests, etc. there are options for backing up the current
environment and restoring from a backup::
./run_tests.sh --restore-environment
./run_tests.sh --backup-environment
The environment backup is stored in ``/tmp/.horizon_environment/``.
Environment Versioning
======================
Horizon keeps track of changes to the environment by comparing the
current requirements files (``requirements.txt`` and
``test-requirements.txt``) and the files last time the virtual
environment was created or updated. If there is any difference,
the virtual environment will be update automatically when you run
``run_tests.sh``.

View File

@ -0,0 +1,104 @@
==================
Horizon DataTables
==================
.. module:: horizon.tables
Horizon includes a componentized API for programmatically creating tables
in the UI. Why would you want this? It means that every table renders
correctly and consistently, table- and row-level actions all have a consistent
API and appearance, and generally you don't have to reinvent the wheel or
copy-and-paste every time you need a new table!
.. seealso::
For usage information, tips & tricks and more examples check out the :doc:`DataTables Topic
Guide </topics/tables>`.
DataTable
=========
The core class which defines the high-level structure of the table being
represented. Example::
class MyTable(DataTable):
name = Column('name')
email = Column('email')
class Meta:
name = "my_table"
table_actions = (MyAction, MyOtherAction)
row_actions - (MyAction)
A full reference is included below:
.. autoclass:: DataTable
:members:
DataTable Options
=================
The following options can be defined in a ``Meta`` class inside a
:class:`.DataTable` class. Example::
class MyTable(DataTable):
class Meta:
name = "my_table"
verbose_name = "My Table"
.. autoclass:: horizon.tables.base.DataTableOptions
:members:
FormsetDataTable
================
You can integrate the :class:`.DataTable` with a Django Formset using one of following classes:
.. autoclass:: horizon.tables.formset.FormsetDataTableMixin
:members:
.. autoclass:: horizon.tables.formset.FormsetDataTable
:members:
Table Components
================
.. autoclass:: Column
:members:
.. autoclass:: Row
:members:
Actions
=======
.. autoclass:: Action
:members:
.. autoclass:: LinkAction
:members:
.. autoclass:: FilterAction
:members:
.. autoclass:: FixedFilterAction
:members:
.. autoclass:: BatchAction
:members:
.. autoclass:: DeleteAction
:members:
.. autoclass:: UpdateAction
:members:
Class-Based Views
=================
Several class-based views are provided to make working with DataTables
easier in your UI.
.. autoclass:: DataTableView
.. autoclass:: MultiTableView

View File

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

View File

@ -0,0 +1,25 @@
========================
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

@ -0,0 +1,38 @@
=================
Horizon Workflows
=================
.. module:: horizon.workflows
One of the most challenging aspects of building a compelling user experience
is crafting complex multi-part workflows. Horizon's ``workflows`` module
aims to bring that capability within everyday reach.
.. seealso::
For usage information, tips & tricks and more examples check out the
:doc:`Workflows Topic Guide </topics/workflows>`.
Workflows
=========
.. autoclass:: Workflow
:members:
Steps
=====
.. autoclass:: Step
:members:
Actions
=======
.. autoclass:: Action
:members:
WorkflowView
============
.. autoclass:: WorkflowView
:members:

View File

@ -0,0 +1,148 @@
======================
Horizon 2012.1 "Essex"
======================
Release Overview
================
During the Essex release cycle, Horizon underwent a significant set of internal
changes to allow extensibility and customization while also adding a significant
number of new features and bringing much greater stability to every interaction
with the underlying components.
Highlights
==========
Extensibility
-------------
Making Horizon extensible for third-party developers was one of the core
goals for the Essex release cycle. Massive strides have been made to allow
for the addition of new "plug-in" components and customization of OpenStack
Dashboard deployments.
To support this extensibility, all the components used to build on Horizon's
interface are now modular and reusable. Horizon's own dashboards use these
components, and they have all been built with third-party developers in mind.
Some of the main components are listed below.
Dashboards and Panels
~~~~~~~~~~~~~~~~~~~~~
Horizon's structure has been divided into logical groupings called dashboards
and panels. Horizon's classes representing these concepts handle all the
structural concerns associated with building a complete user interface
(navigation, access control, url structure, etc.).
Data Tables
~~~~~~~~~~~
One of the most common activities in a dashboard user interface is simply
displaying a list of resources or data and allowing the user to take actions on
that data. To this end, Horizon abstracted the commonalities of this task into a
reusable set of classes which allow developers to programmatically create
displays and interactions for their data with minimal effort and zero
boilerplate.
Tabs and TabGroups
~~~~~~~~~~~~~~~~~~
Another extremely common user-interface element is the use of "tabs" to break
down discrete groups of data into manageable chunks. Since these tabs often
encompass vastly different data, may have completely different access
restrictions, and may sometimes be better-off being loaded dynamically rather
than with the initial page load, Horizon includes tab and tab group classes for
constructing these interfaces elegantly and with no knowledge of the HTML, CSS
or JavaScript involved.
Nova Features
-------------
Support for Nova's features has been greatly improved in Essex:
* Support for Nova volumes, including:
* Volumes creation and management.
* Volume snapshots.
* Realtime AJAX updating for volumes in transition states.
* Improved Nova instance display and interactions, including:
* Launching instances from volumes.
* Pausing/suspending instances.
* Displaying instance power states.
* Realtime AJAX updating for instances in transition states.
* Support for managing Floating IP address pools.
* New instance and volume detail views.
Settings
--------
A new "Settings" area was added that offers several useful functions:
* EC2 credentials download.
* OpenStack RC file download.
* User language preference customization.
User Experience Improvements
----------------------------
* Support for batch actions on multiple resources (e.g. terminating multiple
instances at once).
* Modal interactions throughout the entire UI.
* AJAX form submission for in-place validation.
* Improved in-context help for forms (tooltips and validation messages).
Community
---------
* Creation and publication of a set of Human Interface Guidelines (HIG).
* Copious amounts of documentation for developers.
Under The Hood
--------------
* Internationalization fully enabled, with all strings marked for translation.
* Client library changes:
* Full migration to python-novaclient from the deprecated openstackx library.
* Migration to python-keystoneclient from the deprecated keystone portion
of the python-novaclient library.
* Client-side templating capabilities for more easily creating dynamic
interactions.
* Frontend overhaul to use the Bootstrap CSS/JS framework.
* Centralized error handling for vastly improved stability/reliability
across APIs/clients.
* Completely revamped test suite with comprehensive test data.
* Forward-compatibility with Django 1.4 and the option of cookie-based sessions.
Known Issues and Limitations
============================
Quantum
-------
Quantum support has been removed from Horizon for the Essex release. It will be
restored in Folsom in conjunction with Quantum's first release as a core
OpenStack project.
Keystone
--------
Due to the mechanisms by which Keystone determines "admin"-ness for a user, an
admin user interacting with the "Project" dashboard may see some inconsistent
behavior such as all resources being listed instead of only those belonging to
that project, or only being able to return to the "Admin" dashboard while
accessing certain projects.
Exceptions during customization
-------------------------------
Exceptions raised while overriding built-in Horizon behavior via the
"customization_module" setting may trigger a bug in the error handling
which will mask the original exception.
Backwards Compatibility
=======================
The Essex Horizon release is only partially backwards-compatible with Diablo
OpenStack components. While it is largely possible to log in and interact, many
functions in Nova, Glance and Keystone changed too substantially in Essex to
maintain full compatibility.

View File

@ -0,0 +1,159 @@
=======================
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

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

View File

@ -0,0 +1,254 @@
=======================
Horizon 2013.2 "Havana"
=======================
Release Overview
================
The Havana release cycle brings support for *three* new projects, plus
significant new features for several existing projects. On top of that, many
aspects of user experience have been improved for both end users and
administrators. The community continues to grow and expand. The Havana release
is solidly the best release of the OpenStack Dashboard project yet!
Highlights
==========
New Features
------------
Heat
~~~~
The OpenStack Orchestration project (Heat) debuted in Havana, and Horizon
delivers full support for managing your Heat stacks. Highlights include
support for dynamic form generation from supported Heat template formats,
stack topology visualizations, and full stack resource inspection.
Ceilometer
~~~~~~~~~~
Also debuting in Havana is the OpenStack Metering project (Ceilometer). Initial
support for Ceilometer is included in Horizon so that it is possible for an
administrator to query the usage of the cloud through the OpenStack Dashboard
and better understand how the system is functioning and being utilized.
Domains, Groups, and More: Keystone v3 API Support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
With the OpenStack Identity Service (Keystone) v3 API fully fledged in the
Havana release, Horizon has added full support for all the new features such
as Domains and Groups, Role management and assignment to Domains and Groups,
Domain-based authentication, and Domain context switching.
Trove Databases
~~~~~~~~~~~~~~~
The OpenStack Database as a Service project (Trove) graduated from incubation
in the Havana cycle, and thanks to their industriousness they delivered a
set of panels for the OpenStack dashboard to allow for provisioning and managing
your Trove databases and backups. Disclaimer: Given that Trove's first official
release as an integrated project will not be until Icehouse this feature should
still be considered experimental and may be subject to change.
Nova Features
~~~~~~~~~~~~~
The number of OpenStack Compute (Nova) features that are supported in Horizon
continues to grow. New features in the Havana release include:
* Editable default quotas.
* The ability for an administrator to reset the password of a server/instance.
* Availability zone support.
* Improved region support.
* Instance resizing.
* Improved boot-from-volume support.
* Per-project flavor support.
All of these provide a richer set of options for controlling where, when and how
instances are launched, and improving how they're managed once they're up and
running.
Neutron Features
~~~~~~~~~~~~~~~~
A number of important new OpenStack Networking (Neutron) features are showcased
in the Havana release, most notably:
* VPN as a Service.
* Firewall as a Service.
* Editable and interactive network topology visualizations.
* Full security group and quota parity between Neutron and Nova network.
These features allow for tremendous flexibility when constructing
software-defined networks for your cloud using Neutron.
User Experience Improvements
----------------------------
Self-Service Password Change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Empowered by changes to the Keystone API, users can now change their own
passwords without the need to involve an administrator. This is more secure and
takes the hassle out of things for everyone.
Better Admin Information Architecture
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Several sections of the Admin dashboard have been rearranged to more logically
group information together. Additionally, new sources of information have been
added to allow Admins to better understand the state of the hosts in the cloud
and their relationship to host aggregates, availability zones, etc.
Improved Messaging To Users On Logout
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Several new indicators have been added to inform users why they've been logged
out when they land on the login screen unexpectedly. These indicators make it
clear whether the user's session has expired, they timed out due to inactivity,
or they are not authorized for the section of the dashboard they attempted to
access.
Security Group Rule Templates
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Since there are many very common security group rules which users tediously
re-add each time (rules for SSH and ping, for example) the Horizon team has
added pre-configured templates for common rules which a user can select and
add to their security group with two clicks. These rules are configurable
via the ``SECURITY_GROUP_RULES`` setting.
Community
---------
Translation Team
~~~~~~~~~~~~~~~~
The OpenStack Translations team came fully into its own during the Havana cycle
and the quality of the translations in Horizon are the best yet by far.
Congratulations to that team for their success in building the community that
started primarily within the OpenStack Dashboard project.
User Experience Group
~~~~~~~~~~~~~~~~~~~~~
A fledgling OpenStack User Experience Group formed during the Havana cycle with
the mission of improving UX throughout OpenStack. They have quickly made
themselves indispensable to the process of designing and improving features in
the OpenStack Dashboard. Expect significant future improvement in User
Experience now that there are dedicated people actively collaborating in the
open to raise the bar.
Under The Hood
--------------
Less Complicated LESS Compilation: No More NodeJS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Due to outcry from various parties, and made possible by improvements in the
Python community's support for LESS, Horizon has removed all traces of NodeJS
from the project. We now use the ``lesscpy`` module to compile our LESS into
the final stylesheets. This should not affect most users in any way, but it
should make life easier for downstream distributions and the like.
Role-Based Access Controls
~~~~~~~~~~~~~~~~~~~~~~~~~~
Horizon has begun the transition to using the other OpenStack projects'
``policy.json`` files to enforce access controls in the dashboard if the files
are provided. This means access controls are more configurable and can be kept
in sync between the originating project and Horizon. Currently this is only
supported for Keystone and parts of Nova's policy files. Full support will
come in the next release. You will need to set the ``POLICY_FILES_PATH`` and
``POLICY_FILES`` settings in order to enable this feature.
Other Improvements and Fixes
----------------------------
* Swift container and object metadata are now supported.
* New visualizations for utilization and quotas.
* The Cisco N1K Router plugin's additional features are available through a
special additional dashboard when enabled and supported in Neutron.
* Support for self-signed or other specified SSL certificate checking.
* Glance image types are now configurable.
* Sorting has been improved in many places through the dashboard.
* API call efficiency optimizations.
* Required fields in forms are now better indicated.
* Session timeout can now be enabled to log out the user after a period of
inactivity as a security feature.
* Significant PEP8 and code quality compliance improvements.
* Hundreds of bugfixes and minor user experience improvements.
Upgrade Information
===================
Allowed Hosts
-------------
For production deployments of Horizon you must add the ``ALLOWED_HOSTS``
setting to your ``local_settings.py`` file. This setting
was added in Django 1.5 and is an important security feature. For more
information on it please consult the ``local_settings.py.example`` file
or Django's documentation.
Enabling Keystone and Neutron Features
--------------------------------------
If you have existing configurations for the ``OPENSTACK_KEYSTONE_BACKEND``
or ``OPENSTACK_NEUTRON_NETWORK`` settings, you will want to consult the
``local_settings.example.py`` file for information on the new options that
have been added. Existing configurations will continue to work, but may not
have the correct keys to enable some of the new features in Havana.
Known Issues and Limitations
============================
Session Creation and Health Checks
----------------------------------
If you use a health monitoring service that pings the home page combined with
a database-backed session backend you may experience excessive session creation.
This issue is slated to be fixed soon, but in the interim the recommended
solution is to write a periodic job that deletes expired sessions from your
session store on a regular basis.
Deleting large numbers of resources simultaneously
--------------------------------------------------
Using the "select all" checkbox to delete large numbers of resources at once
can cause network timeouts (depending on configuration). This is due to the
underlying APIs not supporting bulk-deletion natively, and consequently Horizon
has to send requests to delete each resource individually behind the scenes.
Conflicting Security Group Names With Neutron
---------------------------------------------
Whereas Nova Network uses only the name of a security group when specifying
security groups at instance launch time, Neutron can accept either a name or
a UUID. In order to support both, Horizon passes in the name of the selected
security groups. However, due to some data-isolation issues in Neutron there is
an issue that can arise if an admin user tries to specify a security group with
the same name as another security group in a different project which they also
have access to. Neutron will find multiple matches for the security group
name and will fail to launch the instance. The current workaround is to treat
security group names as unique for admin users.
Backwards Compatibility
=======================
The Havana Horizon release should be fully compatible with both Havana and
Grizzly versions of the rest of the OpenStack integrated projects (Nova, Swift,
etc.). New features in other OpenStack projects which did not exist in Grizzly
will obviously only work in Horizon if the rest of the stack supports them as
well.
Overall, great effort has been made to maintain compatibility for
third-party developers who have built on Horizon so far.

View File

@ -0,0 +1,190 @@
=========================
Horizon 2014.1 "Icehouse"
=========================
Release Overview
================
The Icehouse release cycle brings several improvements to Horizon's
user experience, improved extensibility, and support for many
additional features in existing projects. The community continues to
grow. Read more for the specifics.
Highlights
==========
New Features
------------
Nova
~~~~
The number of OpenStack Compute (Nova) features that are supported in Icehouse
grew. New features in the Icehouse release include:
* Live Migration Support
* HyperV Console Support
* Disk config extension support
* Improved support for managing host aggregates and availability zones
* Support for easily setting flavor extra specs
Cinder
~~~~~~
In an ongoing effort to implement Role Based Access Support throughout Horizon,
access controls were added in the OpenStack Volume (Cinder) related panels.
Utilization of the Cinder v2 API is now a supported option in the Icehouse
release. The ability to extend volumes is now available as well.
Neutron
~~~~~~~
Display of Router Rules for routers where they are defined is now supported in
Horizon.
Swift
~~~~~
With Icehouse, the ability for users to create containers and mark them as
public is now available. Links are added to download these public containers.
Users can now explicitly create pseudo directories rather than being required to
create them as part of the container creation process.
Heat
~~~~
In Icehouse, Horizon delivers support for updating existing Heat stacks.
Now stacks that have already been deployed can be adjusted and redeployed. The
updated template is also validated when updated. Additionally, support for
adding environment files was included.
Ceilometer
~~~~~~~~~~
Horizon has added support for administrators to query Ceilometer and
view a daily usage report per project across services through the
OpenStack Dashboard to better understand how system resources are being
consumed by individual projects.
Trove Databases
~~~~~~~~~~~~~~~
The OpenStack Database as a Service project (Trove) is part of the
integrated release in the Icehouse cycle. Improvements to the client
connections and overall stability were added in the Icehouse cycle.
User Experience Improvements
----------------------------
Extensible Enhancements
~~~~~~~~~~~~~~~~~~~~~~~
The primary dashboard and panel navigation has been updated from the tab
navigation to an accordion implementation. Dashboards and Panel Groups are now
expandable and collapsible in the page navigation. This change allows for the
addition of more dashboards as well as accommodates the increasing number of
panels in dashboards.
Wizard
~~~~~~
Horizon now provides a Wizard control to complete multi-step interdependent
tasks. This is now utilized in the create network action.
Inline Table Editing
~~~~~~~~~~~~~~~~~~~~
Tables can now be written to support editing fields in the table to reduce the
need for opening separate forms. The first sample of this is in the Admin
dashboard, Projects panel.
Self-Service Password Change
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Leveraging enhancements to Identity API v3 (Keystone), users can now change
their own passwords without the need to involve an administrator. This
functionality was previously only available with Identity API v2.0.
Server Side Table Filtering
~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tables can now easily be wired to filter results from underlying API calls
based on criteria selected by the user rather than just perform an on page
search. The first example of this is in the Admin dashboard, Instances panel.
Under The Hood
--------------
JavaScript
~~~~~~~~~~
In a move to provide a better user experience, Horizon has adopted AngularJS as
the primary JavaScript framework. JavaScript is now a browser requirement to
run the Horizon interface. More to come in Juno.
Plugin Architecture
~~~~~~~~~~~~~~~~~~~
Horizon now boasts dynamic loading/disabling of dashboards, panel groups and
panels. By merely adding a file in the ``enabled`` directory, the selection of
items loaded into Horizon can be altered. Editing the Django settings file is
no longer required.
For more information see :ref:`pluggable-settings-label`
Integration Test Framework
~~~~~~~~~~~~~~~~~~~~~~~~~~
Horizon now supports running integration tests against a working devstack system. There is a limited test suite, but this a great step forward and allows full integration testing.
Django 1.6 Support
~~~~~~~~~~~~~~~~~~
Django versions 1.4 - 1.6 are now supported by Horizon.
Upgrade Information
===================
Beginning with the Icehouse cycle, there is now a requirement for JavaScript
support in browsers used with OpenStack Dashboard.
Page Layout Changes
-------------------
The overall structure of the page layout in Horizon has been altered. Existing
templates by 3rd parties to override page templates may require some rework.
Default Hypervisor Settings Changes
-----------------------------------
The default for ``can_set_password`` is now ``False``. This means that unless
the setting is explicitly set to ``True``, the option to set an
'Admin password' for an instance will not be shown in the Launch Instance
workflow. Not all hypervisors support this feature which created confusion with
users.
The default for ``can_set_mountpoint`` is now ``False``, and should be set to
``True`` in the settings in order to add the option to set the mount point for
volumes in the dashboard. At this point only the Xen hypervisor supports this
feature.
To change the behavior around hypervisor management in Horizon you must add the
``OPENSTACK_HYPERVISOR_FEATURES`` setting to your ``settings.py`` or
``local_settings.py`` file.
For more information see :ref:`hypervisor-settings-label`
Known Issues and Limitations
============================
Multi-Domain Cross Service Support
----------------------------------
While Horizon supports managing Identity v3 entities and authenticating in a
multi-domain Keystone configuration, there is a v3, v2.0 token compatibility
issue when trying to manage resources for users outside the ``default``
domain. For this reason, v2.0 has been restored as the default API version
for OpenStack Identity (Keystone). For a single domain environment, Keystone
v3 API can still be used via the ``OPENSTACK_API_VERSION`` setting.

View File

@ -0,0 +1,180 @@
=====================
Horizon 2014.2 "Juno"
=====================
Release Overview
================
The Juno release cycle brings a significant update to the user experience;
numerous stability improvements; support for Sahara; and significant
enhancements in feature support for networking, volumes, databases and images.
The community continues to grow and gain speed. Read on for more details.
Highlights
==========
New Features
------------
Sahara
~~~~~~
The OpenStack Data Processing project (Sahara) was formally included into the
integrated release in Juno and Horizon includes broad support for managing your
data processing. You can specify and build clusters to utilize several data
types with user specified jobs while tracking the progress of those jobs.
Neutron
~~~~~~~
Neutron added several new features in Juno, including:
* DVR (Distributed Virtual Routing)
* L3 HA support
* IPv6 subnet modes
Horizon provides support for these new features with the Juno release. These
features provide much greater flexibility in specifying software defined
networks.
An existing feature in Neutron that Horizon now supports is the MAC learning
extension.
Glance
~~~~~~
In Juno, Glance introduced the ability to manage a catalog of metadata
definitions where users can register the metadata definitions to be used on
various resource types including images, volumes, aggregates, and flavors.
Support for viewing and editing the assignment of these metadata tags is
included in Horizon.
Cinder
~~~~~~
In a continued effort to provide more complete API support, several
additional features of the Cinder API are now supported in Horizon in the
Juno release.
Some of these features include:
* Creating and restoring volume backups
* Enabling resetting the state of a snapshot
* Enabling resetting the state of a volume
* Supporting upload-to-image
* Volume retype
* QoS (quality of service) support.
Trove
~~~~~
Trove supports using multiple types of datastores, e.g., mysql, redis, mongodb.
Users can now select from the list of datastores supported by the cloud operator
when creating their database instances.
Another addition is support for utilizing and restoring from incremental
database backups.
To improve support for Neutron based clouds, when creating a database instance,
the user can now specify the NIC for the database instance on creation allowing
direct access to the instance by the user.
Nova
~~~~
The new Nova instance actions view provides a list of all actions taken on
all instances in the current project allowing users to view resulting errors or
actions taken by other users on those instances.
Administrators now have the ability to evacuate hosts off hypervisors which can
aid in system maintenance by providing a mechanism to migrate all instances to
other hosts.
Improved Plugin Support
~~~~~~~~~~~~~~~~~~~~~~~
The plugin system in Horizon continued to improve in the Juno release.
Some of those improvements:
* Support for adding plugin specific AngularJS modules
* Support for adding static files, e.g., CSS, JS, images
* Ability to add exceptions
* Fixing ordering issues
* Numerous other bug fixes
Enhanced RBAC support
~~~~~~~~~~~~~~~~~~~~~
In an ongoing effort to support richer role based access control (RBAC) in
Horizon, the views for several more services were enhanced with RBAC checks to
determine user access to actions. The newly supported services are compute,
network and orchestration. These changes allow operators to implement finer
grained access control than just "member" and "admin".
The identity panels (domains, projects, users, roles, groups) have also been
converted to support RBAC at the view level. The identity panels have been
moved from the admin dashboard into their own 'Identity' dashboard and
accessibility is determined by policies alone. This is the first step toward
consolidating the near duplicate content of the project and admin dashboards
into single views supporting a wide range of roles.
UX Changes
~~~~~~~~~~
In Juno, Horizon transitioned to utilizing Bootstrap v3. Horizon had been
pinned to an older version of Bootstrap for several releases. This change now
allows Horizon to pick up numerous bug fixes and overall improvements in the
Bootstrap framework. The look and feel remains mainly consistent with the
Icehouse release.
Under the Hood
--------------
Improved Translatability
~~~~~~~~~~~~~~~~~~~~~~~~
In an effort to improve the translations for Horizon, updates to remove
concatenations and better handle tense were made.
JavaScript Libraries Extracted
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
As part of the Horizon team's ongoing efforts to split the repository into more
logical pieces, all the 3rd party JavaScript libraries that Horizon depends on
have been removed from the Horizon code base and python xstatic packages have
been utilized instead. The xstatic format allows for easy consumption by the
Django framework Horizon is built on. Now JavaScript libraries are utilized
like any other python dependency in Horizon.
Conversion from LESS to SCSS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The supported stylesheets in Horizon have been converted to utilize SCSS rather
than LESS. The change was necessary due to a prevalent lack of support for LESS
compilers in python. This change also allowed us to upgrade to Bootstrap 3, as
parts of the Bootstrap 3 LESS stylesheets were not supported by existing python
based LESS compilers.
Known Issues and Limitations
============================
Rendering issues in extensions
------------------------------
The conversion to utilizing Bootstrap v3 can cause content extensions written
on top of Horizon to have rendering issues. Most of these are fixed by a simple
CSS class name substitutions. These issues are primarily seen with buttons and
panel content widths.
Online Compression
------------------
With the move to SCSS, there may be issues with utilizing online compression in
non-DEBUG mode in Horizon. Offline compression continues to work as in previous
releases.
https://bugs.launchpad.net/horizon/+bug/1379761
Neutron L3 HA
-------------
The HA property is updateable in the UI, however, Neutron API does not allow the
update operation because toggling HA support does not work.
https://bugs.launchpad.net/horizon/+bug/1379761

View File

@ -0,0 +1,59 @@
=======================
Horizon's tests and you
=======================
How to run the tests
====================
Because Horizon is composed of both the ``horizon`` app and the
``openstack_dashboard`` reference project, there are in fact two sets of unit
tests. While they can be run individually without problem, there is an easier
way:
Included at the root of the repository is the ``run_tests.sh`` script
which invokes both sets of tests, and optionally generates analyses on both
components in the process. This script is what Jenkins uses to verify the
stability of the project, so you should make sure you run it and it passes
before you submit any pull requests/patches.
To run the tests::
$ ./run_tests.sh
It's also possible to :doc:`run a subset of unit tests<ref/run_tests>`.
.. seealso::
:doc:`ref/run_tests`
Full reference for the ``run_tests.sh`` script.
By default running the Selenium tests will open your Firefox browser (you have
to install it first, else an error is raised), and you will be able to see the
tests actions.
If you want to run the suite headless, without being able to see them (as they
are ran on Jenkins), you can run the tests:
$ ./run_tests.sh --with-selenium --selenium-headless
Selenium will use a virtual display in this case, instead of your own. In order
to run the tests this way you have to install the dependency `xvfb`, like this:
$ sudo apt-get install xvfb
for a Debian OS flavour, or for Fedora/Red Hat flavours:
$ sudo yum install xorg-x11-server-Xvfb
Writing tests
=============
Horizon uses Django's unit test machinery (which extends Python's ``unittest2``
library) as the core of its test suite. As such, all tests for the Python code
should be written as unit tests. No doctests please.
In general new code without unit tests will not be accepted, and every bugfix
*must* include a regression test.
For a much more in-depth discussion of testing, see the :doc:`testing topic
guide </topics/testing>`.

View File

@ -0,0 +1,283 @@
===================
Customizing Horizon
===================
Themes
======
As of the Kilo release, styling for the OpenStack Dashboard can be altered
through the use of a theme. A theme is a directory containing a
``_variables.scss`` file to override the color codes used throughout the SCSS
and a ``_styles.scss`` file with additional styles to load after dashboard
styles have loaded.
To use a custom theme, set ``CUSTOM_THEME_PATH`` in ``local_settings.py`` to
the directory location for the theme (e.g., ``"static/themes/blue"``). The
path can either be relative to the ``openstack_dashboard`` directory or an
absolute path to an accessible location on the file system. The default
``CUSTOM_THEME_PATH`` is ``static/themes/default``.
Both the Dashboard custom variables and Bootstrap variables can be overridden.
For a full list of the Dashboard SCSS variables that can be changed, see the
variables file at ``openstack_dashboard/static/dashboard/scss/_variables.scss``.
Changing the Logo
-----------------
There are currently two places where the OpenStack logo is pulled in
through the stylesheets. The first is shown at the login screen and the other
on top of the menu bar. To override the logo place your logo in your themes
directory and set the image to use in ``_styles.scss``. For example::
#splash .login {
background-image: url(/static/themes/THEME/logo-splash.png);
}
.topbar {
h1.brand a {
background-image: url(/static/themes/THEME/logo.png);
}
}
``THEME`` should be replaced by the name of your theme directory.
The dimensions should be ``width: 216px, height: 35px`` for a drop in
replacement.
Prior to the Kilo release the images files inside of Horizon needed to be
replaced by your images files or the Horizon stylesheets needed to be altered
to point to the location of your image.
Changing the Site Title
=======================
The OpenStack Dashboard Site Title branding (i.e. "**OpenStack** Dashboard")
can be overwritten by adding the attribute ``SITE_BRANDING``
to ``local_settings.py`` with the value being the desired name.
The file ``local_settings.py`` can be found at the Horizon directory path of
``openstack_dashboard/local/local_settings.py``.
Changing the Brand Link
=======================
The logo also acts as a hyperlink. The default behavior is to redirect to
``horizon:user_home``. By adding the attribute ``SITE_BRANDING_LINK`` with
the desired url target e.g., ``http://sample-company.com`` in
``local_settings.py``, the target of the hyperlink can be changed.
Modifying Existing Dashboards and Panels
========================================
If you wish to alter dashboards or panels which are not part of your codebase,
you can specify a custom python module which will be loaded after the entire
Horizon site has been initialized, but prior to the URLconf construction.
This allows for common site-customization requirements such as:
* Registering or unregistering panels from an existing dashboard.
* Changing the names of dashboards and panels.
* Re-ordering panels within a dashboard or panel group.
To specify the python module containing your modifications, add the key
``customization_module`` to your ``HORIZON_CONFIG`` dictionary in
``local_settings.py``. The value should be a string containing the path to your
module in dotted python path notation. Example::
HORIZON_CONFIG = {
"customization_module": "my_project.overrides"
}
You can do essentially anything you like in the customization module. For
example, you could change the name of a panel::
from django.utils.translation import ugettext_lazy as _
import horizon
# Rename "User Settings" to "User Options"
settings = horizon.get_dashboard("settings")
user_panel = settings.get_panel("user")
user_panel.name = _("User Options")
Or get the instances panel::
projects_dashboard = horizon.get_dashboard("project")
instances_panel = projects_dashboard.get_panel("instances")
And limit access to users with the Keystone Admin role::
permissions = list(getattr(instances_panel, 'permissions', []))
permissions.append('openstack.roles.admin')
instances_panel.permissions = tuple(permissions)
Or just remove it entirely::
projects_dashboard.unregister(instances_panel.__class__)
You can also override existing methods with your own versions::
# Disable Floating IPs
from openstack_dashboard.dashboards.project.access_and_security import tabs
from openstack_dashboard.dashboards.project.instances import tables
NO = lambda *x: False
tabs.FloatingIPsTab.allowed = NO
tables.AssociateIP.allowed = NO
tables.SimpleAssociateIP.allowed = NO
tables.SimpleDisassociateIP.allowed = NO
You could also customize what columns are displayed in an existing
table, by redefining the ``columns`` attribute of its ``Meta``
class. This can be achieved in 3 steps:
#. Extend the table that you wish to modify
#. Redefine the ``columns`` attribute under the ``Meta`` class for this
new table
#. Modify the ``table_class`` attribute for the related view so that it
points to the new table
For example, if you wished to remove the Admin State column from the
:class:`~openstack_dashboard.dashboards.admin.networks.tables.NetworksTable`,
you could do the following::
from openstack_dashboard.dashboards.project.networks import tables
from openstack_dashboard.dashboards.project.networks import views
class MyNetworksTable(tables.NetworksTable):
class Meta(tables.NetworksTable.Meta):
columns = ('name', 'subnets', 'shared', 'status')
views.IndexView.table_class = MyNetworksTable
If you want to add a column you can override the parent table in a
similar way, add the new column definition and then use the ``Meta``
``columns`` attribute to control the column order as needed.
.. NOTE::
``my_project.overrides`` needs to be importable by the python process running
Horizon.
If your module is not installed as a system-wide python package,
you can either make it installable (e.g., with a setup.py)
or you can adjust the python path used by your WSGI server to include its location.
Probably the easiest way is to add a ``python-path`` argument to
the ``WSGIDaemonProcess`` line in Apache's Horizon config.
Assuming your ``my_project`` module lives in ``/opt/python/my_project``,
you'd make it look like the following::
WSGIDaemonProcess [... existing options ...] python-path=/opt/python
Button Icons
============
Horizon uses font icons (glyphicons) from Twitter Bootstrap to add icons to buttons.
Please see http://bootstrapdocs.com/v3.1.1/docs/components/#glyphicons for instructions
how to use icons in the code.
To add icon to Table Action, use icon property. Example:
class CreateSnapshot(tables.LinkAction):
name = "snapshot"
verbose_name = _("Create Snapshot")
icon = "camera"
Additionally, the site-wide default button classes can be configured by
setting ``ACTION_CSS_CLASSES`` to a tuple of the classes you wish to appear
on all action buttons in your ``local_settings.py`` file.
Custom Stylesheets
==================
It is possible to define custom stylesheets for your dashboards. Horizon's base
template ``horizon/templates/base.html`` defines multiple blocks that
can be overridden.
To define custom css files that apply only to a specific dashboard, create
a base template in your dashboard's templates folder, which extends Horizon's
base template e.g. ``openstack_dashboard/dashboards/my_custom_dashboard/
templates/my_custom_dashboard/base.html``.
In this template, redefine ``block css``. (Don't forget to include
``_stylesheets.html`` which includes all Horizon's default stylesheets.)::
{% extends 'base.html' %}
{% block css %}
{% include "_stylesheets.html" %}
{% load compress %}
{% compress css %}
<link href='{{ STATIC_URL }}my_custom_dashboard/scss/my_custom_dashboard.scss' type='text/scss' media='screen' rel='stylesheet' />
{% endcompress %}
{% endblock %}
The custom stylesheets then reside in the dashboard's own ``static`` folder
``openstack_dashboard/dashboards/my_custom_dashboard/static/
my_custom_dashboard/scss/my_custom_dashboard.scss``.
All dashboard's templates have to inherit from dashboard's base.html::
{% extends 'my_custom_dashboard/base.html' %}
...
Custom Javascript
=================
Similarly to adding custom styling (see above), it is possible to include
custom javascript files.
All Horizon's javascript files are listed in the ``horizon/_scripts.html``
partial template, which is included in Horizon's base template in ``block js``.
To add custom javascript files, create an ``_scripts.html`` partial template in
your dashboard ``openstack_dashboard/dashboards/my_custom_dashboard/
templates/my_custom_dashboard/_scripts.html`` which extends
``horizon/_scripts.html``. In this template override the
``block custom_js_files`` including your custom javascript files::
{% extends 'horizon/_scripts.html' %}
{% block custom_js_files %}
<script src='{{ STATIC_URL }}my_custom_dashboard/js/my_custom_js.js' type='text/javascript' charset='utf-8'></script>
{% endblock %}
In your dashboard's own base template ``openstack_dashboard/dashboards/
my_custom_dashboard/templates/my_custom_dashboard/base.html`` override
``block js`` with inclusion of dashboard's own ``_scripts.html``::
{% block js %}
{% include "my_custom_dashboard/_scripts.html" %}
{% endblock %}
The result is a single compressed js file consisting both Horizon and
dashboard's custom scripts.
Additionally, some marketing and analytics scripts require you to place them
within the page's <head> tag. To do this, place them within the
``horizon/_custom_head_js.html`` file. Similar to the ``_scripts.html`` file
mentioned above, you may link to an existing file::
<script src='{{ STATIC_URL }}/my_custom_dashboard/js/my_marketing_js.js' type='text/javascript' charset='utf-8'></script>
or you can paste your script directly in the file, being sure to use
appropriate tags::
<script type="text/javascript">
//some javascript
</script>
Customizing Meta Attributes
===========================
To add custom metadata attributes to your project's base template, include
them in the ``horizon/_custom_meta.html`` file. The contents of this file will be
inserted into the page's <head> just after the default Horizon meta tags.

View File

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

View File

@ -0,0 +1,100 @@
==================
Installing Horizon
==================
This page covers the basic installation of Horizon.
.. _system-requirements-label:
System Requirements
===================
* Python 2.7
* Django 1.6 (1.4 and 1.5 are supported too)
* Minimum required set of running OpenStack services are:
* Nova
* Keystone
* Glance
* Neutron (unless nova-network is used)
* All other services are optional.
Horizon supports the following services in Juno release.
If Keystone endpoint for a service is configured,
Horizon detects it and enables its support automatically.
* Swift
* Cinder
* Heat
* Ceilometer
* Trove
* Sahara
Installation
============
1. Compile translation message catalogs for internationalization.
This step is not required if you do not need to support languages
other than English. GNU ``gettext`` tool is required to compile
message catalogs::
$ sudo apt-get install gettext
$ ./run_tests.sh --compilemessages
This command compiles translation message catalogs within Python
virtualenv named ``.venv``. After this step, you can remove
``.venv`` directory safely.
2. Install Horizon python module into your system. Run the following
in the top directory::
$ sudo pip install .
3. Create ``openstack_dashboard/local/local_settings.py``.
It is usually a good idea to copy
``openstack_dashboard/local/local_settings.py.example`` and edit it.
At least we need to customize the following variables in this file.
* ``ALLOWED_HOSTS`` (unless ``DEBUG`` is ``True``)
* ``OPENSTACK_KEYSTONE_URL``
For more details, please refer to :doc:`deployment` and :doc:`settings`.
4. Optional: Django has a Compressor feature that performs many enhancements
for the delivery of static files, including standardization and
minification/uglification. This processing can be run either online or
offline (pre-processed). Letting the compression process occur at runtime
will incur processing and memory use when the resources are first requested;
doing it ahead of time removes those runtime penalties.
If you want the static files to be processed before server runtime, you'll
need to configure your local_settings.py to specify
``COMPRESS_OFFLINE = True``, then run the following commands::
$ ./manage.py collectstatic
$ ./manage.py compress
5. Set up a web server with WSGI support.
It is optional but recommended in production deployments.
For example, install Apache web server on Ubuntu::
$ sudo apt-get install apache2 libapache2-mod-wsgi
Then configure the web server to host OpenStack Dashboard via WSGI.
For apache2 web server, you may need to create
``/etc/apache2/sites-available/horizon.conf``.
The template in devstack is a good example of the file.
http://git.openstack.org/cgit/openstack-dev/devstack/tree/files/apache-horizon.template
6. Finally, enable the above configuration and restart the web server::
$ sudo a2ensite horizon
$ sudo service apache2 restart
Next Steps
==========
* :doc:`deployment` covers some common questions, concerns and pitfalls you
may encounter when deploying Horizon in a production environment.
* :doc:`settings` lists the available settings for Horizon.
* :doc:`customizing` describes how to customizing Horizon as you want.

View File

@ -0,0 +1,148 @@
============================================================
Horizon Policy Enforcement (RBAC: Role Based Access Control)
============================================================
Introduction
============
Horizon's policy enforcement builds on the oslo-incubator policy engine.
The basis of which is ``openstack_dashboard/openstack/common/policy.py``.
Services in OpenStack use the oslo policy engine to define policy rules
to limit access to APIs based primarily on role grants and resource
ownership.
The Keystone v3 API provides an interface for creating/reading/updating
policy files in the keystone database. However, at this time services
do not load the policy files into Keystone. Thus, the implementation in
Horizon is based on copies of policy.json files found in the service's
source code. The long-term goal is to read/utilize/update these policy
files in Horizon.
The service rules files are loaded into the policy engine to determine
access rights to actions and service APIs.
Horizon Settings
================
There are a few settings that must be in place for the Horizon policy
engine to work.
``POLICY_FILES_PATH``
---------------------
Default: ``os.path.join(ROOT_PATH, "conf")``
Specifies where service based policy files are located. These are used to
define the policy rules actions are verified against. This value must contain
the files listed in ``POLICY_FILES`` or all policy checks will pass.
.. note::
The path to deployment specific policy files can be specified in
``local_settings.py`` to override the default location.
``POLICY_FILES``
----------------
Default: ``{'identity': 'keystone_policy.json', 'compute': 'nova_policy.json'}``
This should essentially be the mapping of the contents of ``POLICY_FILES_PATH``
to service types. When policy.json files are added to the directory
``POLICY_FILES_PATH``, they should be included here too. Without this mapping,
there is no way to map service types with policy rules, thus two policy.json
files containing a "default" rule would be ambiguous.
.. note::
Deployment specific policy files can be specified in ``local_settings.py``
to override the default policy files. It is imperative that these policy
files match those deployed in the target OpenStack installation. Otherwise,
the displayed actions and the allowed action will not match.
``POLICY_CHECK_FUNCTION``
-------------------------
Default: ``policy.check``
This value should not be changed, although removing it would be a means to
bypass all policy checks.
How user's roles are determined
===============================
Each policy check uses information about the user stored on the request to
determine the user's roles. This information was extracted from the scoped
token received from Keystone when authenticating.
Entity ownership is also a valid role. To verify access to specific entities
like a project, the target must be specified. See the section
:ref:`rule targets <rule_targets>` later in this document.
How to Utilize RBAC
===================
The primary way to add role based access control checks to panels is in the
definition of table actions. When implementing a derived action class,
setting the :attr:`~horizon.tables.Action.policy_rules` attribute to valid
policy rules will force a policy check before the
:meth:`horizon.tables.Action.allowed` method is called on the action. These
rules are defined in the policy files pointed to by ``POLICY_PATH`` and
``POLICY_FILES``. The rules are role based, where entity owner is also a
role. The format for the ``policy_rules`` is a list of two item tuples. The
first component of the tuple is the scope of the policy rule, this is the
service type. This informs the policy engine which policy file to reference.
The second component is the rule to enforce from the policy file specified by
the scope. An example tuple is::
("identity", "identity:get_user")
x tuples can be added to enforce x rules.
.. note::
If a rule specified is not found in the policy file, the policy check
will return False and the action will not be allowed.
The secondary way to add a role based check is to directly use the
:meth:`~openstack_dashboard.policy.check` method. The method takes a list
of actions, same format as the :attr:`~horizon.tables.Action.policy_rules`
attribute detailed above; the current request object; and a dictionary of
action targets. This is the method that :class:`horizon.tables.Action` class
utilizes. Examples look like::
from openstack_dashboard import policy
allowed = policy.check((("identity", "identity:get_user"),
("identity", "identity:get_project"),), request)
can_see = policy.check((("identity", "identity:get_user"),), request,
target={"domain_id": domainId})
.. note::
Any time multiple rules are specified in a single `policy.check` method
call, the result is the logical `and` of each rule check. So, if any
rule fails verification, the result is `False`.
.. _rule_targets:
Rule Targets
============
Some rules allow access if the user owns the entity. Policy check targets
specify particular entities to check for user ownership. The target parameter
to the :meth:`~openstack_dashboard.policy.check` method is a simple dictionary.
For instance, the target for checking access a project looks like::
{"project_id": "0905760626534a74979afd3f4a9d67f1"}
If the value matches the ``project_id`` to which the user's token is scoped,
then access is allowed.
When deriving the :class:`horizon.tables.Action` class for use in a table, if
a policy check is desired for a particular target, the implementer should
override the :meth:`horizon.tables.Action.get_policy_target` method. This
allows a programmatic way to specify the target based on the current datum. The
value returned should be the target dictionary.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,300 @@
============================================
Tutorial: Adding a complex action to a table
============================================
This tutorial covers how to add a more complex action to a table, one that requires
an action and form definitions, as well as changes to the view, urls, and table.
This tutorial assumes you have already completed :doc:`Building a Dashboard using
Horizon </topics/tutorial>`. If not, please do so now as we will be modifying the
files created there.
This action will create a snapshot of the instance. When the action is taken,
it will display a form that will allow the user to enter a snapshot name,
and will create that snapshot when the form is closed using the ``Create snapshot``
button.
Defining the view
=================
To define the view, we must create a view class, along with the template (``HTML``)
file and the form class for that view.
The template file
-----------------
The template file contains the HTML that will be used to show the view.
Create a ``create_snapshot.html`` file under the ``mypanel/templates/mypanel``
directory and add the following code::
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Snapshot" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create a Snapshot") %}
{% endblock page_header %}
{% block main %}
{% include 'mydashboard/mypanel/_create_snapshot.html' %}
{% endblock %}
As you can see, the main body will be defined in ``_create_snapshot.html``,
so we must also create that file under the ``mypanel/templates/mypanel``
directory. It should contain the following code::
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% block modal-body-right %}
<h3>{% trans "Description:" %}</h3>
<p>{% trans "Snapshots preserve the disk state of a running instance." %}</p>
{% endblock %}
The form
--------
Horizon provides a :class:`~horizon.forms.base.SelfHandlingForm` class which simplifies
some of the details involved in creating a form. Our form will derive from this
class, adding a character field to allow the user to specify a name for the
snapshot, and handling the successful closure of the form by calling the nova
api to create the snapshot.
Create the ``forms.py`` file under the ``mypanel`` directory and add the following::
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import forms
from openstack_dashboard import api
class CreateSnapshot(forms.SelfHandlingForm):
instance_id = forms.CharField(label=_("Instance ID"),
widget=forms.HiddenInput(),
required=False)
name = forms.CharField(max_length=255, label=_("Snapshot Name"))
def handle(self, request, data):
try:
snapshot = api.nova.snapshot_create(request,
data['instance_id'],
data['name'])
return snapshot
except Exception:
exceptions.handle(request,
_('Unable to create snapshot.'))
The view
--------
Now, the view will tie together the template and the form. Horizon provides a
:class:`~horizon.forms.views.ModalFormView` class which simplifies the creation of a
view that will contain a modal form.
Open the ``views.py`` file under the ``mypanel`` directory and add the code
for the CreateSnapshotView and the necessary imports. The complete
file should now look something like this::
from django.core.urlresolvers import reverse
from django.core.urlresolvers import reverse_lazy
from django.utils.translation import ugettext_lazy as _
from horizon import tabs
from horizon import exceptions
from horizon import forms
from horizon.utils import memoized
from openstack_dashboard import api
from openstack_dashboard.dashboards.mydashboard.mypanel \
import forms as project_forms
from openstack_dashboard.dashboards.mydashboard.mypanel \
import tabs as mydashboard_tabs
class IndexView(tabs.TabbedTableView):
tab_group_class = mydashboard_tabs.MypanelTabs
# A very simple class-based view...
template_name = 'mydashboard/mypanel/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
class CreateSnapshotView(forms.ModalFormView):
form_class = project_forms.CreateSnapshot
template_name = 'mydashboard/mypanel/create_snapshot.html'
success_url = reverse_lazy("horizon:project:images:index")
modal_id = "create_snapshot_modal"
modal_header = _("Create Snapshot")
submit_label = _("Create Snapshot")
submit_url = "horizon:mydashboard:mypanel:create_snapshot"
@memoized.memoized_method
def get_object(self):
try:
return api.nova.server_get(self.request,
self.kwargs["instance_id"])
except Exception:
exceptions.handle(self.request,
_("Unable to retrieve instance."))
def get_initial(self):
return {"instance_id": self.kwargs["instance_id"]}
def get_context_data(self, **kwargs):
context = super(CreateSnapshotView, self).get_context_data(**kwargs)
instance_id = self.kwargs['instance_id']
context['instance_id'] = instance_id
context['instance'] = self.get_object()
context['submit_url'] = reverse(self.submit_url, args=[instance_id])
return context
Adding the url
==============
We must add the url for our new view. Open the ``urls.py`` file under
the ``mypanel`` directory and add the following as a new url pattern::
url(r'^(?P<instance_id>[^/]+)/create_snapshot/$',
views.CreateSnapshotView.as_view(),
name='create_snapshot'),
The complete ``urls.py`` file should look like this::
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.mydashboard.mypanel import views
urlpatterns = patterns('',
url(r'^\?tab=mypanel_tabs_tab$',
views.IndexView.as_view(), name='mypanel_tabs'),
url(r'^(?P<instance_id>[^/]+)/create_snapshot/$',
views.CreateSnapshotView.as_view(),
name='create_snapshot'),
)
Define the action
=================
Horizon provides a :class:`~horizon.tables.LinkAction` class which simplifies
adding an action which can be used to display another view.
We will add a link action to the table that will be accessible from each row
in the table. The action will use the view defined above to create a snapshot
of the instance represented by the row in the table.
To do this, we must edit the ``tables.py`` file under the ``mypanel`` directory
and add the following::
def is_deleting(instance):
task_state = getattr(instance, "OS-EXT-STS:task_state", None)
if not task_state:
return False
return task_state.lower() == "deleting"
class CreateSnapshotAction(tables.LinkAction):
name = "snapshot"
verbose_name = _("Create Snapshot")
url = "horizon:mydashboard:mypanel:create_snapshot"
classes = ("ajax-modal",)
icon = "camera"
# This action should be disabled if the instance
# is not active, or the instance is being deleted
def allowed(self, request, instance=None):
return instance.status in ("ACTIVE") \
and not is_deleting(instance)
We must also add our new action as a row action for the table::
row_actions = (CreateSnapshotAction,)
The complete ``tables.py`` file should look like this::
from django.utils.translation import ugettext_lazy as _
from horizon import tables
def is_deleting(instance):
task_state = getattr(instance, "OS-EXT-STS:task_state", None)
if not task_state:
return False
return task_state.lower() == "deleting"
class CreateSnapshotAction(tables.LinkAction):
name = "snapshot"
verbose_name = _("Create Snapshot")
url = "horizon:mydashboard:mypanel:create_snapshot"
classes = ("ajax-modal",)
icon = "camera"
def allowed(self, request, instance=None):
return instance.status in ("ACTIVE") \
and not is_deleting(instance)
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class InstancesTable(tables.DataTable):
name = tables.Column("name", verbose_name=_("Name"))
status = tables.Column("status", verbose_name=_("Status"))
zone = tables.Column('availability_zone', verbose_name=_("Availability Zone"))
image_name = tables.Column('image_name', verbose_name=_("Image Name"))
class Meta:
name = "instances"
verbose_name = _("Instances")
table_actions = (MyFilterAction,)
row_actions = (CreateSnapshotAction,)
Run and check the dashboard
===========================
We must once again run horizon to verify our dashboard is working::
./run_tests.sh --runserver 0.0.0.0:8877
Go to ``http://<your server>:8877`` using a browser. After login as an admin,
display ``My Panel`` to see the ``Instances`` table. For every ``ACTIVE``
instance in the table, there will be a ``Create Snapshot`` action on the row.
Click on ``Create Snapshot``, enter a snapshot name in the form that is shown,
then click to close the form. The ``Project Images`` view should be shown with
the new snapshot added to the table.
Conclusion
==========
What you've learned here is the fundamentals of how to add a table action that
requires a form for data entry. This can easily be expanded from creating a
snapshot to other API calls that require more complex forms to gather the
necessary information.
If you have feedback on how this tutorial could be improved, please feel free
to submit a bug against ``Horizon`` in `launchpad`_.
.. _launchpad: https://bugs.launchpad.net/horizon

View File

@ -0,0 +1,387 @@
======================
DataTables Topic Guide
======================
Horizon provides the :mod:`horizon.tables` module to provide
a convenient, reusable API for building data-driven displays and interfaces.
The core components of this API fall into three categories: ``DataTables``,
``Actions``, and ``Class-based Views``.
.. seealso::
For a detailed API information check out the :doc:`DataTables Reference
Guide </ref/tables>`.
Tables
======
The majority of interface in a dashboard-style interface ends up being
tabular displays of the various resources the dashboard interacts with.
The :class:`~horizon.tables.DataTable` class exists so you don't have to
reinvent the wheel each time.
Creating your own tables
------------------------
Creating a table is fairly simple:
#. Create a subclass of :class:`~horizon.tables.DataTable`.
#. Define columns on it using :class:`~horizon.tables.Column`.
#. Create an inner ``Meta`` class to contain the special options for
this table.
#. Define any actions for the table, and add them to
:attr:`~horizon.tables.DataTableOptions.table_actions` or
:attr:`~horizon.tables.DataTableOptions.row_actions`.
Examples of this can be found in any of the ``tables.py`` modules included
in the reference modules under ``horizon.dashboards``.
Connecting a table to a view
----------------------------
Once you've got your table set up the way you like it, the next step is to
wire it up to a view. To make this as easy as possible Horizon provides the
:class:`~horizon.tables.DataTableView` class-based view which can be subclassed
to display your table with just a couple lines of code. At its simplest, it
looks like this::
from horizon import tables
from .tables import MyTable
class MyTableView(tables.DataTableView):
table_class = MyTable
template_name = "my_app/my_table_view.html"
def get_data(self):
return my_api.objects.list()
In the template you would just need to include the following to render the
table::
{{ table.render }}
That's it! Easy, right?
Actions
=======
Actions comprise any manipulations that might happen on the data in the table
or the table itself. For example, this may be the standard object CRUD, linking
to related views based on the object's id, filtering the data in the table,
or fetching updated data when appropriate.
When actions get run
--------------------
There are two points in the request-response cycle in which actions can
take place; prior to data being loaded into the table, and after the data
is loaded. When you're using one of the pre-built class-based views for
working with your tables the pseudo-workflow looks like this:
#. The request enters view.
#. The table class is instantiated without data.
#. Any "preemptive" actions are checked to see if they should run.
#. Data is fetched and loaded into the table.
#. All other actions are checked to see if they should run.
#. If none of the actions have caused an early exit from the view,
the standard response from the view is returned (usually the
rendered table).
The benefit of the multi-step table instantiation is that you can use
preemptive actions which don't need access to the entire collection of data
to save yourself on processing overhead, API calls, etc.
Basic actions
-------------
At their simplest, there are three types of actions: actions which act on the
data in the table, actions which link to related resources, and actions that
alter which data is displayed. These correspond to
:class:`~horizon.tables.Action`, :class:`~horizon.tables.LinkAction`, and
:class:`~horizon.tables.FilterAction`.
Writing your own actions generally starts with subclassing one of those
action classes and customizing the designated attributes and methods.
Shortcut actions
----------------
There are several common tasks for which Horizon provides pre-built shortcut
classes. These include :class:`~horizon.tables.BatchAction`, and
:class:`~horizon.tables.DeleteAction`. Each of these abstracts away nearly
all of the boilerplate associated with writing these types of actions and
provides consistent error handling, logging, and user-facing interaction.
It is worth noting that ``BatchAction`` and ``DeleteAction`` are extensions
of the standard ``Action`` class. Some ``BatchAction`` or ``DeleteAction``
classes may cause some unrecoverable results, like deleted images or
unrecoverable instances. It may be helpful to specify specific help_text to
explain the concern to the user, such as "Deleted images are not recoverable".
Preemptive actions
------------------
Action classes which have their :attr:`~horizon.tables.Action.preempt`
attribute set to ``True`` will be evaluated before any data is loaded into
the table. As such, you must be careful not to rely on any table methods that
require data, such as :meth:`~horizon.tables.DataTable.get_object_display` or
:meth:`~horizon.tables.DataTable.get_object_by_id`. The advantage of preemptive
actions is that you can avoid having to do all the processing, API calls, etc.
associated with loading data into the table for actions which don't require
access to that information.
Policy checks on actions
------------------------
The :attr:`~horizon.tables.Action.policy_rules` attribute, when set, will
validate access to the action using the policy rules specified. The attribute
is a list of scope/rule pairs. Where the scope is the service type defining
the rule and the rule is a rule from the corresponding service policy.json
file. The format of :attr:`horizon.tables.Action.policy_rules` looks like::
(("identity", "identity:get_user"),)
Multiple checks can be made for the same action by merely adding more tuples
to the list. The policy check will use information stored in the session
about the user and the result of
:meth:`~horizon.tables.Action.get_policy_target` (which can be overridden in
the derived action class) to determine if the user
can execute the action. If the user does not have access to the action, the
action is not added to the table.
If :attr:`~horizon.tables.Action.policy_rules` is not set, no policy checks
will be made to determine if the action should be visible and will be
displayed solely based on the result of
:meth:`~horizon.tables.Action.allowed`.
For more information on policy based Role Based Access Control see:
:doc:`Horizon Policy Enforcement (RBAC: Role Based Access Control) </topics/policy>`.
Table Cell filters (decorators)
===============================
DataTable displays lists of objects in rows and object attributes in cell.
How should we proceed, if we want to decorate some column, e.g. if we have
column ``memory`` which returns a number e.g. 1024, and we want to show
something like 1024.00 GB inside table?
Decorator pattern
-----------------
The clear anti-pattern is defining the new attributes on object like
``ram_float_format_2_gb`` or to tweak a DataTable in any way for displaying
purposes.
The cleanest way is to use ``filters``. Filters are decorators, following GOF
``Decorator pattern``. This way ``DataTable logic`` and ``displayed object
logic`` are correctly separated from ``presentation logic`` of the object
inside of the various tables. And therefore the filters are reusable in all
tables.
Filter function
---------------
Horizon DatablesTable takes a tuple of pointers to filter functions
or anonymous lambda functions. When displaying a ``Cell``, ``DataTable``
takes ``Column`` filter functions from left to right, using the returned value
of the previous function as a parameter of the following function. Then
displaying the returned value of the last filter function.
A valid filter function takes one parameter and returns the decorated value.
So e.g. these are valid filter functions ::
# Filter function.
def add_unit(v):
return str(v) + " GB"
# Or filter lambda function.
lambda v: str(v) + " GB"
# This is also a valid definition of course, although for the change of the
# unit parameter, function has to be wrapped by lambda
# (e.g. floatformat function example below).
def add_unit(v, unit="GB"):
return str(v) + " " + unit
Using filters in DataTable column
---------------------------------
DataTable takes tuple of filter functions, so e.g. this is valid decorating
of a value with float format and with unit ::
ram = tables.Column(
"ram",
verbose_name=_('Memory'),
filters=(lambda v: floatformat(v, 2),
add_unit))
It always takes tuple, so using only one filter would look like this ::
filters=(lambda v: floatformat(v, 2),)
The decorated parameter doesn't have to be only a string or number, it can
be anything e.g. list or an object. So decorating of object, that has
attributes value and unit would look like this ::
ram = tables.Column(
"ram",
verbose_name=_('Memory'),
filters=(lambda x: getattr(x, 'value', '') +
" " + getattr(x, 'unit', ''),))
Available filters
-----------------
There are a load of filters, that can be used, defined in django already:
https://github.com/django/django/blob/master/django/template/defaultfilters.py
So it's enough to just import and use them, e.g. ::
from django.template import defaultfilters as filters
# code omitted
filters=(filters.yesno, filters.capfirst)
from django.template.defaultfilters import timesince
from django.template.defaultfilters import title
# code omitted
filters=(parse_isotime, timesince)
Inline editing
==============
Table cells can be easily upgraded with in-line editing. With use of
django.form.Field, we are able to run validations of the field and correctly
parse the data. The updating process is fully encapsulated into table
functionality, communication with the server goes through AJAX in JSON format.
The javacript wrapper for inline editing allows each table cell that has
in-line editing available to:
#. Refresh itself with new data from the server.
#. Display in edit mod.
#. Send changed data to server.
#. Display validation errors.
There are basically 3 things that need to be defined in the table in order
to enable in-line editing.
Fetching the row data
---------------------
Defining an ``get_data`` method in a class inherited from ``tables.Row``.
This method takes care of fetching the row data. This class has to be then
defined in the table Meta class as ``row_class = UpdateRow``.
Example::
class UpdateRow(tables.Row):
# this method is also used for automatic update of the row
ajax = True
def get_data(self, request, project_id):
# getting all data of all row cells
project_info = api.keystone.tenant_get(request, project_id,
admin=True)
return project_info
Updating changed cell data
--------------------------
Define an ``update_cell`` method in the class inherited from
``tables.UpdateAction``. This method takes care of saving the data of the
table cell. There can be one class for every cell thanks to the
``cell_name`` parameter. This class is then defined in tables column as
``update_action=UpdateCell``, so each column can have its own updating
method.
Example::
class UpdateCell(tables.UpdateAction):
def allowed(self, request, project, cell):
# Determines whether given cell or row will be inline editable
# for signed in user.
return api.keystone.keystone_can_edit_project()
def update_cell(self, request, project_id, cell_name, new_cell_value):
# in-line update project info
try:
project_obj = datum
# updating changed value by new value
setattr(project_obj, cell_name, new_cell_value)
# sending new attributes back to API
api.keystone.tenant_update(
request,
project_id,
name=project_obj.name,
description=project_obj.description,
enabled=project_obj.enabled)
except Conflict:
# Validation error for naming conflict, raised when user
# choose the existing name. We will raise a
# ValidationError, that will be sent back to the client
# browser and shown inside of the table cell.
message = _("This name is already taken.")
raise ValidationError(message)
except:
# Other exception of the API just goes through standard
# channel
exceptions.handle(request, ignore=True)
return False
return True
Defining a form_field for each Column that we want to be in-line edited.
------------------------------------------------------------------------
Form field should be ``django.form.Field`` instance, so we can use django
validations and parsing of the values sent by POST (in example validation
``required=True`` and correct parsing of the checkbox value from the POST
data).
Form field can be also ``django.form.Widget`` class, if we need to just
display the form widget in the table and we don't need Field functionality.
Then connecting ``UpdateRow`` and ``UpdateCell`` classes to the table.
Example::
class TenantsTable(tables.DataTable):
# Adding html text input for inline editing, with required validation.
# HTML form input will have a class attribute tenant-name-input, we
# can define here any HTML attribute we need.
name = tables.Column('name', verbose_name=_('Name'),
form_field=forms.CharField(required=True),
form_field_attributes={'class':'tenant-name-input'},
update_action=UpdateCell)
# Adding html textarea without required validation.
description = tables.Column(lambda obj: getattr(obj, 'description', None),
verbose_name=_('Description'),
form_field=forms.CharField(
widget=forms.Textarea(),
required=False),
update_action=UpdateCell)
# Id will not be inline edited.
id = tables.Column('id', verbose_name=_('Project ID'))
# Adding html checkbox, that will be shown inside of the table cell with
# label
enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True,
form_field=forms.BooleanField(
label=_('Enabled'),
required=False),
update_action=UpdateCell)
class Meta:
name = "tenants"
verbose_name = _("Projects")
# Connection to UpdateRow, so table can fetch row data based on
# their primary key.
row_class = UpdateRow

View File

@ -0,0 +1,276 @@
===================
Testing Topic Guide
===================
Having good tests in place is absolutely critical for ensuring a stable,
maintainable codebase. Hopefully that doesn't need any more explanation.
However, what defines a "good" test is not always obvious, and there are
a lot of common pitfalls that can easily shoot your test suite in the
foot.
If you already know everything about testing but are fed up with trying to
debug why a specific test failed, you can skip the intro and jump
straight to :ref:`debugging_unit_tests`.
An overview of testing
======================
There are three main types of tests, each with their associated pros and cons:
Unit tests
----------
These are isolated, stand-alone tests with no external dependencies. They are
written from the perspective of "knowing the code", and test the assumptions
of the codebase and the developer.
Pros:
* Generally lightweight and fast.
* Can be run anywhere, anytime since they have no external dependencies.
Cons:
* Easy to be lax in writing them, or lazy in constructing them.
* Can't test interactions with live external services.
Functional tests
----------------
These are generally also isolated tests, though sometimes they may interact
with other services running locally. The key difference between functional
tests and unit tests, however, is that functional tests are written from the
perspective of the user (who knows nothing about the code) and only knows
what they put in and what they get back. Essentially this is a higher-level
testing of "does the result match the spec?".
Pros:
* Ensures that your code *always* meets the stated functional requirements.
* Verifies things from an "end user" perspective, which helps to ensure
a high-quality experience.
* Designing your code with a functional testing perspective in mind helps
keep a higher-level viewpoint in mind.
Cons:
* Requires an additional layer of thinking to define functional requirements
in terms of inputs and outputs.
* Often requires writing a separate set of tests and/or using a different
testing framework from your unit tests.
* Doesn't offer any insight into the quality or status of the underlying code,
only verifies that it works or it doesn't.
Integration Tests
-----------------
This layer of testing involves testing all of the components that your
codebase interacts with or relies on in conjunction. This is equivalent to
"live" testing, but in a repeatable manner.
Pros:
* Catches *many* bugs that unit and functional tests will not.
* Doesn't rely on assumptions about the inputs and outputs.
* Will warn you when changes in external components break your code.
Cons:
* Difficult and time-consuming to create a repeatable test environment.
* Did I mention that setting it up is a pain?
So what should I write?
-----------------------
A few simple guidelines:
#. Every bug fix should have a regression test. Period.
#. When writing a new feature, think about writing unit tests to verify
the behavior step-by-step as you write the feature. Every time you'd
go to run your code by hand and verify it manually, think "could I
write a test to do this instead?". That way when the feature is done
and you're ready to commit it you've already got a whole set of tests
that are more thorough than anything you'd write after the fact.
#. Write tests that hit every view in your application. Even if they
don't assert a single thing about the code, it tells you that your
users aren't getting fatal errors just by interacting with your code.
What makes a good unit test?
============================
Limiting our focus just to unit tests, there are a number of things you can
do to make your unit tests as useful, maintainable, and unburdensome as
possible.
Test data
---------
Use a single, consistent set of test data. Grow it over time, but do everything
you can not to fragment it. It quickly becomes unmaintainable and perniciously
out-of-sync with reality.
Make your test data as accurate to reality as possible. Supply *all* the
attributes of an object, provide objects in all the various states you may want
to test.
If you do the first suggestion above *first* it makes the second one far less
painful. Write once, use everywhere.
To make your life even easier, if your codebase doesn't have a built-in
ORM-like function to manage your test data you can consider building (or
borrowing) one yourself. Being able to do simple retrieval queries on your
test data is incredibly valuable.
Mocking
-------
Mocking is the practice of providing stand-ins for objects or pieces of code
you don't need to test. While convenient, they should be used with *extreme*
caution.
Why? Because overuse of mocks can rapidly land you in a situation where you're
not testing any real code. All you've done is verified that your mocking
framework returns what you tell it to. This problem can be very tricky to
recognize, since you may be mocking things in ``setUp`` methods, other modules,
etc.
A good rule of thumb is to mock as close to the source as possible. If you have
a function call that calls an external API in a view , mock out the external
API, not the whole function. If you mock the whole function you've suddenly
lost test coverage for an entire chunk of code *inside* your codebase. Cut the
ties cleanly right where your system ends and the external world begins.
Similarly, don't mock return values when you could construct a real return
value of the correct type with the correct attributes. You're just adding
another point of potential failure by exercising your mocking framework instead
of real code. Following the suggestions for testing above will make this a lot
less burdensome.
Assertions and verification
---------------------------
Think long and hard about what you really want to verify in your unit test. In
particular, think about what custom logic your code executes.
A common pitfall is to take a known test object, pass it through your code,
and then verify the properties of that object on the output. This is all well
and good, except if you're verifying properties that were untouched by your
code. What you want to check are the pieces that were *changed*, *added*, or
*removed*. Don't check the object's id attribute unless you have reason to
suspect it's not the object you started with. But if you added a new attribute
to it, be damn sure you verify that came out right.
It's also very common to avoid testing things you really care about because
it's more difficult. Verifying that the proper messages were displayed to the
user after an action, testing for form errors, making sure exception handling
is tested... these types of things aren't always easy, but they're extremely
necessary.
To that end, Horizon includes several custom assertions to make these tasks
easier. :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors`,
:meth:`~horizon.test.helpers.TestCase.assertMessageCount`, and
:meth:`~horizon.test.helpers.TestCase.assertNoMessages` all exist for exactly
these purposes. Moreover, they provide useful output when things go wrong so
you're not left scratching your head wondering why your view test didn't
redirect as expected when you posted a form.
.. _debugging_unit_tests:
Debugging Unit Tests
====================
Tips and tricks
---------------
#. Use :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors`
immediately after your ``client.post`` call for tests that handle form views.
This will immediately fail if your form POST failed due to a validation error
and tell you what the error was.
#. Use :meth:`~horizon.test.helpers.TestCase.assertMessageCount` and
:meth:`~horizon.test.helpers.TestCase.assertNoMessages` when a piece of code
is failing inexplicably. Since the core error handlers attach user-facing
error messages (and since the core logging is silenced during test runs)
these methods give you the dual benefit of verifying the output you expect
while clearly showing you the problematic error message if they fail.
#. Use Python's ``pdb`` module liberally. Many people don't realize it works
just as well in a test case as it does in a live view. Simply inserting
``import pdb; pdb.set_trace()`` anywhere in your codebase will drop the
interpreter into an interactive shell so you can explore your test
environment and see which of your assumptions about the code isn't,
in fact, flawlessly correct.
Common pitfalls
---------------
There are a number of typical (and non-obvious) ways to break the unit tests.
Some common things to look for:
#. Make sure you stub out the method exactly as it's called in the code
being tested. For example, if your real code calls
``api.keystone.tenant_get``, stubbing out ``api.tenant_get`` (available
for legacy reasons) will fail.
#. When defining the expected input to a stubbed call, make sure the
arguments are *identical*, this includes ``str`` vs. ``int`` differences.
#. Make sure your test data are completely in line with the expected inputs.
Again, ``str`` vs. ``int`` or missing properties on test objects will
kill your tests.
#. Make sure there's nothing amiss in your templates (particularly the
``{% url %}`` tag and its arguments). This often comes up when refactoring
views or renaming context variables. It can easily result in errors that
you might not stumble across while clicking around the development server.
#. Make sure you're not redirecting to views that no longer exist, e.g.
the ``index`` view for a panel that got combined (such as instances &
volumes).
#. Make sure your mock calls are in order before calling ``mox.ReplayAll``.
The order matters.
#. Make sure you repeat any stubbed out method calls that happen more than
once. They don't automatically repeat, you have to explicitly define them.
While this is a nuisance, it makes you acutely aware of how many API
calls are involved in a particular function.
Understanding the output from ``mox``
-------------------------------------
Horizon uses ``mox`` as its mocking framework of choice, and while it
offers many nice features, its output when a test fails can be quite
mysterious.
Unexpected Method Call
~~~~~~~~~~~~~~~~~~~~~~
This occurs when you stubbed out a piece of code, and it was subsequently
called in a way that you didn't specify it would be. There are two reasons
this tends to come up:
#. You defined the expected call, but a subtle difference crept in. This
may be a string versus integer difference, a string versus unicode
difference, a slightly off date/time, or passing a name instead of an id.
#. The method is actually being called *multiple times*. Since mox uses
a call stack internally, it simply pops off the expected method calls to
verify them. That means once a call is used once, it's gone. An easy way
to see if this is the case is simply to copy and paste your method call a
second time to see if the error changes. If it does, that means your method
is being called more times than you think it is.
Expected Method Never Called
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This one is the opposite of the unexpected method call. This one means you
told mox to expect a call and it didn't happen. This is almost always the
result of an error in the conditions of the test. Using the
:meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors` and
:meth:`~horizon.test.helpers.TestCase.assertMessageCount` will make it readily
apparent what the problem is in the majority of cases. If not, then use ``pdb``
and start interrupting the code flow to see where things are getting off track.

View File

@ -0,0 +1,629 @@
============================================
Tutorial: Building a Dashboard using Horizon
============================================
This tutorial covers how to use the various components in Horizon to build
an example dashboard and a panel with a tab which has a table containing data
from the back end.
As an example, we'll create a new ``My Dashboard`` dashboard with a ``My Panel``
panel that has an ``Instances Tab`` tab. The tab has a table which contains the
data pulled by the Nova instances API.
.. note::
This tutorial assumes you have either a ``devstack`` or ``openstack``
environment up and running.
There are a variety of other resources which may be helpful to read first.
For example, you may want to start
with the :doc:`Horizon quickstart guide </quickstart>` or the
`Django tutorial`_.
.. _Django tutorial: https://docs.djangoproject.com/en/1.6/intro/tutorial01/
Creating a dashboard
====================
The quick version
-----------------
Horizon provides a custom management command to create a typical base
dashboard structure for you. Run the following commands at the same location
where the ``run_tests.sh`` file resides. It generates most of the boilerplate
code you need::
mkdir openstack_dashboard/dashboards/mydashboard
./run_tests.sh -m startdash mydashboard \
--target openstack_dashboard/dashboards/mydashboard
mkdir openstack_dashboard/dashboards/mydashboard/mypanel
./run_tests.sh -m startpanel mypanel \
--dashboard=openstack_dashboard.dashboards.mydashboard \
--target=openstack_dashboard/dashboards/mydashboard/mypanel
You will notice that the directory ``mydashboard`` gets automatically
populated with the files related to a dashboard and the ``mypanel`` directory
gets automatically populated with the files related to a panel.
Structure
---------
If you use the ``tree mydashboard`` command to list the ``mydashboard``
directory in ``openstack_dashboard/dashboards`` , you will see a directory
structure that looks like the following::
mydashboard
├── dashboard.py
├── dashboard.pyc
├── __init__.py
├── __init__.pyc
├── models.py
├── mypanel
│   ├── __init__.py
│   ├── models.py
│   ├── panel.py
│   ├── templates
│   │   └── mypanel
│   │   └── index.html
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── static
│   └── mydashboard
│   ├── css
│   │   └── mydashboard.css
│   └── js
│   └── mydashboard.js
└── templates
└── mydashboard
└── base.html
For this tutorial, we will not deal with the static directory, the ``models.py``
file and tests.py file. Leave them as they are.
With the rest of the files and directories in place, we can move on to add our
own dashboard.
Defining a dashboard
--------------------
Open the ``dashboard.py`` file. You will notice the following code has been
automatically generated::
from django.utils.translation import ugettext_lazy as _
import horizon
class Mydashboard(horizon.Dashboard):
name = _("Mydashboard")
slug = "mydashboard"
panels = () # Add your panels here.
default_panel = '' # Specify the slug of the dashboard's default panel.
horizon.register(Mydashboard)
If you want the dashboard name to be something else, you can change the ``name``
attribute in the ``dashboard.py`` file . For example, you can change it
to be ``My Dashboard`` ::
name = _("My Dashboard")
A dashboard class will usually contain a ``name`` attribute (the display name of
the dashboard), a ``slug`` attribute (the internal name that could be referenced
by other components), a list of panels, default panel, etc. We will cover how
to add a panel in the next section.
Creating a panel
================
We'll create a panel and call it ``My Panel``.
Structure
---------
As described above, the ``mypanel`` directory under
``openstack_dashboard/dashboards/mydashboard`` should look like the following::
mypanel
├── __init__.py
├── models.py
├── panel.py
├── templates
│   └── mypanel
│     └── index.html
├── tests.py
├── urls.py
└── views.py
Defining a panel
----------------
The ``panel.py`` file referenced above has a special meaning. Within a dashboard,
any module name listed in the ``panels`` attribute on the dashboard class will
be auto-discovered by looking for the ``panel.py`` file in a corresponding
directory (the details are a bit magical, but have been thoroughly vetted in
Django's admin codebase).
Open the ``panel.py`` file, you will have the following auto-generated code::
from django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.mydashboard import dashboard
class Mypanel(horizon.Panel):
name = _("Mypanel")
slug = "mypanel"
dashboard.Mydashboard.register(Mypanel)
If you want the panel name to be something else, you can change the ``name``
attribute in the ``panel.py`` file . For example, you can change it to be
``My Panel``::
name = _("My Panel")
Open the ``dashboard.py`` file again, insert the following code above the
``Mydashboard`` class. This code defines the ``Mygroup`` class and adds a panel
called ``mypanel``::
class Mygroup(horizon.PanelGroup):
slug = "mygroup"
name = _("My Group")
panels = ('mypanel',)
Modify the ``Mydashboard`` class to include ``Mygroup`` and add ``mypanel`` as
the default panel::
class Mydashboard(horizon.Dashboard):
name = _("My Dashboard")
slug = "mydashboard"
panels = (Mygroup,) # Add your panels here.
default_panel = 'mypanel' # Specify the slug of the default panel.
The completed ``dashboard.py`` file should look like
the following::
from django.utils.translation import ugettext_lazy as _
import horizon
class Mygroup(horizon.PanelGroup):
slug = "mygroup"
name = _("My Group")
panels = ('mypanel',)
class Mydashboard(horizon.Dashboard):
name = _("My Dashboard")
slug = "mydashboard"
panels = (Mygroup,) # Add your panels here.
default_panel = 'mypanel' # Specify the slug of the default panel.
horizon.register(Mydashboard)
Tables, Tabs, and Views
-----------------------
We'll start with the table, combine that with the tabs, and then build our
view from the pieces.
Defining a table
~~~~~~~~~~~~~~~~
Horizon provides a :class:`~horizon.forms.SelfHandlingForm` :class:`~horizon.tables.DataTable` class which simplifies
the vast majority of displaying data to an end-user. We're just going to skim
the surface here, but it has a tremendous number of capabilities.
Create a ``tables.py`` file under the ``mypanel`` directory and add the
following code::
from django.utils.translation import ugettext_lazy as _
from horizon import tables
class InstancesTable(tables.DataTable):
name = tables.Column("name", verbose_name=_("Name"))
status = tables.Column("status", verbose_name=_("Status"))
zone = tables.Column('availability_zone',
verbose_name=_("Availability Zone"))
image_name = tables.Column('image_name', verbose_name=_("Image Name"))
class Meta:
name = "instances"
verbose_name = _("Instances")
There are several things going on here... we created a table subclass,
and defined four columns that we want to retrieve data and display.
Each of those columns defines what attribute it accesses on the instance object
as the first argument, and since we like to make everything translatable,
we give each column a ``verbose_name`` that's marked for translation.
Lastly, we added a ``Meta`` class which indicates the meta object that describes
the ``instances`` table.
.. note::
This is a slight simplification from the reality of how the instance
object is actually structured. In reality, accessing other attributes
requires an additional step.
Adding actions to a table
~~~~~~~~~~~~~~~~~~~~~~~~~
Horizon provides three types of basic action classes which can be taken
on a table's data:
- :class:`~horizon.tables.Action`
- :class:`~horizon.tables.LinkAction`
- :class:`~horizon.tables.FilterAction`
There are also additional actions which are extensions of the basic Action classes:
- :class:`~horizon.tables.BatchAction`
- :class:`~horizon.tables.DeleteAction`
- :class:`~horizon.tables.UpdateAction`
- :class:`~horizon.tables.FixedFilterAction`
Now let's create and add a filter action to the table. To do so, we will need
to edit the ``tables.py`` file used above. To add a filter action which will
only show rows which contain the string entered in the filter field, we
must first define the action::
class MyFilterAction(tables.FilterAction):
name = "myfilter"
.. note::
The action specified above will default the ``filter_type`` to be ``"query"``.
This means that the filter will use the client side table sorter.
Then, we add that action to the table actions for our table.::
class InstancesTable:
class Meta:
table_actions = (MyFilterAction,)
The completed ``tables.py`` file should look like the following::
from django.utils.translation import ugettext_lazy as _
from horizon import tables
class MyFilterAction(tables.FilterAction):
name = "myfilter"
class InstancesTable(tables.DataTable):
name = tables.Column('name', \
verbose_name=_("Name"))
status = tables.Column('status', \
verbose_name=_("Status"))
zone = tables.Column('availability_zone', \
verbose_name=_("Availability Zone"))
image_name = tables.Column('image_name', \
verbose_name=_("Image Name"))
class Meta:
name = "instances"
verbose_name = _("Instances")
table_actions = (MyFilterAction,)
Defining tabs
~~~~~~~~~~~~~
So we have a table, ready to receive our data. We could go straight to a view
from here, but in this case we're also going to use Horizon's
:class:`~horizon.tabs.TabGroup` class.
Create a ``tabs.py`` file under the ``mypanel`` directory. Let's make a tab
group which has one tab. The completed code should look like the following::
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.mydashboard.mypanel import tables
class InstanceTab(tabs.TableTab):
name = _("Instances Tab")
slug = "instances_tab"
table_classes = (tables.InstancesTable,)
template_name = ("horizon/common/_detail_table.html")
preload = False
def has_more_data(self, table):
return self._has_more
def get_instances_data(self):
try:
marker = self.request.GET.get(
tables.InstancesTable._meta.pagination_param, None)
instances, self._has_more = api.nova.server_list(
self.request,
search_opts={'marker': marker, 'paginate': True})
return instances
except Exception:
self._has_more = False
error_message = _('Unable to get instances')
exceptions.handle(self.request, error_message)
return []
class MypanelTabs(tabs.TabGroup):
slug = "mypanel_tabs"
tabs = (InstanceTab,)
sticky = True
This tab gets a little more complicated. The tab handles data tables (and
all their associated features), and it also uses the ``preload`` attribute to
specify that this tab shouldn't be loaded by default. It will instead be loaded
via AJAX when someone clicks on it, saving us on API calls in the vast majority
of cases.
Additionally, the displaying of the table is handled by a reusable template,
``horizon/common/_detail_table.html``. Some simple pagination code was added
to handle large instance lists.
Lastly, this code introduces the concept of error handling in Horizon.
The :func:`horizon.exceptions.handle` function is a centralized error
handling mechanism that takes all the guess-work and inconsistency out of
dealing with exceptions from the API. Use it everywhere.
Tying it together in a view
~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are lots of pre-built class-based views in Horizon. We try to provide
the starting points for all the common combinations of components.
Open the ``views.py`` file, the auto-generated code is like the following::
from horizon import views
class IndexView(views.APIView):
# A very simple class-based view...
template_name = 'mydashboard/mypanel/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
In this case we want a starting view type that works with both tabs and
tables... that'd be the :class:`~horizon.tabs.TabbedTableView` class. It takes
the best of the dynamic delayed-loading capabilities tab groups provide and
mixes in the actions and AJAX-updating that tables are capable of with almost
no work on the user's end. Change ``views.APIView`` to be
``tabs.TabbedTableView`` and add ``MypanelTabs`` as the tab group class in the
``IndexView`` class::
class IndexView(tabs.TabbedTableView):
tab_group_class = mydashboard_tabs.MypanelTabs
After importing the proper package, the completed ``views.py`` file now looks like
the following::
from horizon import tabs
from openstack_dashboard.dashboards.mydashboard.mypanel \
import tabs as mydashboard_tabs
class IndexView(tabs.TabbedTableView):
tab_group_class = mydashboard_tabs.MypanelTabs
template_name = 'mydashboard/mypanel/index.html'
def get_data(self, request, context, *args, **kwargs):
# Add data to the context here...
return context
URLs
----
The auto-generated ``urls.py`` file is like::
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.mydashboard.mypanel.views \
import IndexView
urlpatterns = patterns(
'',
url(r'^$', IndexView.as_view(), name='index'),
)
Adjust the import of ``IndexView`` to make the code readable::
from openstack_dashboard.dashboards.mydashboard.mypanel import views
Replace the existing ``url`` pattern with the following line::
url(r'^$',
views.IndexView.as_view(), name='index'),
The completed ``urls.py`` file should look like the following::
from django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.mydashboard.mypanel import views
urlpatterns = patterns('',
url(r'^$',
views.IndexView.as_view(), name='index'),
)
The template
~~~~~~~~~~~~
Open the ``index.html`` file in the ``mydashboard/mypanel/templates/mypanel``
directory, the auto-generated code is like the following::
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Mypanel" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Mypanel") %}
{% endblock page_header %}
{% block main %}
{% endblock %}
The ``main`` block must be modified to insert the following code::
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
If you want to change the title of the ``index.html`` file to be something else,
you can change it. For example, change it to be ``My Panel`` in the
``block title`` section. If you want the ``title`` in the ``block page_header``
section to be something else, you can change it. For example, change it to be
``My Panel``. The updated code could be like::
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "My Panel" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("My Panel") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}
This gives us a custom page title, a header, and renders our tab group provided
by the view.
With all our code in place, the only thing left to do is to integrate it into
our OpenStack Dashboard site.
.. note::
For more information about Django views, URLs and templates, please refer
to the `Django documentation`_.
.. _Django documentation: https://docs.djangoproject.com/en/1.6/
Enable and show the dashboard
=============================
In order to make ``My Dashboard`` show up along with the existing dashboards
like ``Project`` or ``Admin`` on Horizon, you need to create a file called
``_50_mydashboard.py`` under ``openstack_dashboard/enabled`` and add the
following::
# The name of the dashboard to be added to HORIZON['dashboards']. Required.
DASHBOARD = 'mydashboard'
# If set to True, this dashboard will not be added to the settings.
DISABLED = False
# A list of applications to be added to INSTALLED_APPS.
ADD_INSTALLED_APPS = [
'openstack_dashboard.dashboards.mydashboard',
]
Run and check the dashboard
===========================
Everything is in place, now run ``Horizon`` on the different port::
./run_tests.sh --runserver 0.0.0.0:8877
Go to ``http://<your server>:8877`` using a browser. After login as an admin
you should be able see ``My Dashboard`` shows up at the left side on Horizon.
Click it, ``My Group`` will expand with ``My Panel``. Click on ``My Panel``,
the right side panel will display an ``Instances Tab`` which has an
``Instances`` table.
If you don't see any instance data, you haven't created any instances yet. Go to
dashboard ``Project`` -> ``Images``, select a small image, for example,
``crioos-0.3.1-x86_64-uec`` , click ``Launch`` and enter an ``Instance Name``,
click the button ``Launch``. It should create an instance if the openstack or
devstack is correctly set up. Once the creation of an instance is successful, go
to ``My Dashboard`` again to check the data.
Adding a complex action to a table
==================================
For a more detailed look into adding a table action, one that requires forms for
gathering data, you can walk through :doc:`Adding a complex action to a table
</topics/table_actions>` tutorial.
Conclusion
==========
What you've learned here is the fundamentals of how to write interfaces for
your own project based on the components Horizon provides.
If you have feedback on how this tutorial could be improved, please feel free
to submit a bug against ``Horizon`` in `launchpad`_.
.. _launchpad: https://bugs.launchpad.net/horizon

View File

@ -0,0 +1,134 @@
======================
Workflows Topic Guide
======================
One of the most challenging aspects of building a compelling user experience
is crafting complex multi-part workflows. Horizon's ``workflows`` module
aims to bring that capability within everyday reach.
.. seealso::
For a detailed API information check out the :doc:`Workflows Reference
Guide </ref/workflows>`.
Workflows
=========
Workflows are complex forms with tabs, each workflow must consist of classes
extending the :class:`~horizon.workflows.Workflow`,
:class:`~horizon.workflows.Step` and :class:`~horizon.workflows.Action`
Complex example of workflow
----------------------------
The following is a complex example of how data are exchanged between
urls, views, workflows and templates:
#. In ``urls.py``, we have the named parameter. E.g. ``resource_class_id``. ::
RESOURCE_CLASS = r'^(?P<resource_class_id>[^/]+)/%s$'
urlpatterns = patterns(
'',
url(RESOURCE_CLASS % 'update', UpdateView.as_view(), name='update'))
#. In ``views.py``, we pass data to the template and to the action(form)
(action can also pass data to the ``get_context_data`` method and to the
template). ::
class UpdateView(workflows.WorkflowView):
workflow_class = UpdateResourceClass
def get_context_data(self, **kwargs):
context = super(UpdateView, self).get_context_data(**kwargs)
# Data from URL are always in self.kwargs, here we pass the data
# to the template.
context["resource_class_id"] = self.kwargs['resource_class_id']
# Data contributed by Workflow's Steps are in the
# context['workflow'].context list. We can use that in the
# template too.
return context
def _get_object(self, *args, **kwargs):
# Data from URL are always in self.kwargs, we can use them here
# to load our object of interest.
resource_class_id = self.kwargs['resource_class_id']
# Code omitted, this method should return some object obtained
# from API.
def get_initial(self):
resource_class = self._get_object()
# This data will be available in the Action's methods and
# Workflow's handle method.
# But only if the steps will depend on them.
return {'resource_class_id': resource_class.id,
'name': resource_class.name,
'service_type': resource_class.service_type}
#. In ``workflows.py`` we process the data, it is just more complex django
form. ::
class ResourcesAction(workflows.Action):
# The name field will be automatically available in all action's
# methods.
# If we want this field to be used in the another Step or Workflow,
# it has to be contributed by this step, then depend on in another
# step.
name = forms.CharField(max_length=255,
label=_("Testing Name"),
help_text="",
required=True)
def handle(self, request, data):
pass
# If we want to use some data from the URL, the Action's step
# has to depend on them. It's then available in
# self.initial['resource_class_id'] or data['resource_class_id'].
# In other words, resource_class_id has to be passed by view's
# get_initial and listed in step's depends_on list.
# We can also use here the data from the other steps. If we want
# the data from the other step, the step needs to contribute the
# data and the steps needs to be ordered properly.
class UpdateResources(workflows.Step):
# This passes data from Workflow context to action methods
# (handle, clean). Workflow context consists of URL data and data
# contributed by other steps.
depends_on = ("resource_class_id",)
# By contributing, the data on these indexes will become available to
# Workflow and to other Steps (if they will depend on them). Notice,
# that the resources_object_ids key has to be manually added in
# contribute method first.
contributes = ("resources_object_ids", "name")
def contribute(self, data, context):
# We can obtain the http request from workflow.
request = self.workflow.request
if data:
# Only fields defined in Action are automatically
# available for contribution. If we want to contribute
# something else, We need to override the contribute method
# and manually add it to the dictionary.
context["resources_object_ids"] =\
request.POST.getlist("resources_object_ids")
# We have to merge new context with the passed data or let
# the superclass do this.
context.update(data)
return context
class UpdateResourceClass(workflows.Workflow):
default_steps = (UpdateResources,)
def handle(self, request, data):
pass
# This method is called as last (after all Action's handle
# methods). All data that are listed in step's 'contributes='
# and 'depends_on=' are available here.
# It can be easier to have the saving logic only here if steps
# are heavily connected or complex.
# data["resources_object_ids"], data["name"] and
# data["resources_class_id"] are available here.

View File

@ -0,0 +1,66 @@
# 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 # noqa
from horizon.base import Horizon # noqa
from horizon.base import Panel # noqa
from horizon.base import PanelGroup # noqa
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:
__all__ = [
"Dashboard",
"Horizon",
"Panel",
"PanelGroup",
"register",
"unregister",
"get_absolute_url",
"get_user_home",
"get_dashboard",
"get_default_dashboard",
"get_dashboards",
"urls",
]

View File

@ -0,0 +1,982 @@
# 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 import include
from django.conf.urls import patterns
from django.conf.urls import url
from django.core.exceptions import ImproperlyConfigured # noqa
from django.core.urlresolvers import reverse
from django.utils.datastructures import SortedDict
from django.utils.functional import SimpleLazyObject # noqa
from django.utils.importlib import import_module # noqa
from django.utils.module_loading import module_has_submodule # noqa
from django.utils.translation import ugettext_lazy as _
from horizon import conf
from horizon.decorators import _current_component # noqa
from horizon.decorators import require_auth # noqa
from horizon.decorators import require_perms # noqa
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)
def access_cached(func):
def inner(self, context):
session = context['request'].session
try:
if session['allowed']['valid_for'] != session.get('token'):
raise KeyError()
except KeyError:
session['allowed'] = {"valid_for": session.get('token')}
key = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
if key not in session['allowed']:
session['allowed'][key] = func(self, context)
session.modified = True
return session['allowed'][key]
return inner
class NotRegistered(Exception):
pass
class HorizonComponent(object):
policy_rules = None
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
@access_cached
def can_access(self, context):
"""Return whether the user has role based access to this component.
This method is not intended to be overridden.
The result of the method is stored in per-session cache.
"""
return self.allowed(context)
def allowed(self, context):
"""Checks if the user is allowed to access this component.
This method should be overridden to return the result of
any policy checks required for the user to access this component
when more complex checks are required.
"""
return self._can_access(context['request'])
def _can_access(self, request):
policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
# this check is an OR check rather than an AND check that is the
# default in the policy engine, so calling each rule individually
if policy_check and self.policy_rules:
for rule in self.policy_rules:
if policy_check((rule,), request):
return True
return False
# default to allowed
return True
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.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")
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
.. 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 dashboard should appear in
automatically-generated navigation. Default: ``True``.
.. 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
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):
"""Returns the specified :class:~horizon.PanelGroup
or None if not registered
"""
return self._panel_groups.get(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 Exception:
# 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
url_slug = panel.slug.replace('.', '/')
urlpatterns += patterns('',
url(r'^%s/' % url_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.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 Exception:
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
def allowed(self, context):
"""Checks for role based access for this dashboard.
Checks for access to any panels in the dashboard and of the the
dashboard itself.
This method should be overridden to return the result of
any policy checks required for the user to access this dashboard
when more complex checks are required.
"""
# if the dashboard has policy rules, honor those above individual
# panels
if not self._can_access(context['request']):
return False
# check if access is allowed to a single panel,
# the default for each panel is True
for panel in self.get_panels():
if panel.can_access(context):
return True
return False
class Workflow(object):
pass
try:
from django.utils.functional import empty # noqa
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 '/' in user_home:
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 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()
# Load the plugin-based panel configuration
self._load_panel_customization()
# 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 Exception:
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.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 Exception:
self._registry = before_import_registry
if module_has_submodule(mod, mod_name):
raise
def _load_panel_customization(self):
"""Applies the plugin-based panel configurations.
This method parses the panel customization from the ``HORIZON_CONFIG``
and make changes to the dashboard accordingly.
It supports adding, removing and setting default panels on the
dashboard. It also support registering a panel group.
"""
panel_customization = self._conf.get("panel_customization", [])
# Process all the panel groups first so that they exist before panels
# are added to them and Dashboard._autodiscover() doesn't wipe out any
# panels previously added when its panel groups are instantiated.
panel_configs = []
for config in panel_customization:
if config.get('PANEL'):
panel_configs.append(config)
elif config.get('PANEL_GROUP'):
self._process_panel_group_configuration(config)
else:
LOG.warning("Skipping %s because it doesn't have PANEL or "
"PANEL_GROUP defined.", config.__name__)
# Now process the panels.
for config in panel_configs:
self._process_panel_configuration(config)
def _process_panel_configuration(self, config):
"""Add, remove and set default panels on the dashboard."""
try:
dashboard = config.get('PANEL_DASHBOARD')
if not dashboard:
LOG.warning("Skipping %s because it doesn't have "
"PANEL_DASHBOARD defined.", config.__name__)
return
panel_slug = config.get('PANEL')
dashboard_cls = self.get_dashboard(dashboard)
panel_group = config.get('PANEL_GROUP')
default_panel = config.get('DEFAULT_PANEL')
# Set the default panel
if default_panel:
dashboard_cls.default_panel = default_panel
# Remove the panel
if config.get('REMOVE_PANEL', False):
for panel in dashboard_cls.get_panels():
if panel_slug == panel.slug:
dashboard_cls.unregister(panel.__class__)
elif config.get('ADD_PANEL', None):
# Add the panel to the dashboard
panel_path = config['ADD_PANEL']
mod_path, panel_cls = panel_path.rsplit(".", 1)
try:
mod = import_module(mod_path)
except ImportError:
LOG.warning("Could not load panel: %s", mod_path)
return
panel = getattr(mod, panel_cls)
dashboard_cls.register(panel)
if panel_group:
dashboard_cls.get_panel_group(panel_group).\
panels.append(panel.slug)
else:
panels = list(dashboard_cls.panels)
panels.append(panel)
dashboard_cls.panels = tuple(panels)
except Exception as e:
LOG.warning('Could not process panel %(panel)s: %(exc)s',
{'panel': panel_slug, 'exc': e})
def _process_panel_group_configuration(self, config):
"""Adds a panel group to the dashboard."""
panel_group_slug = config.get('PANEL_GROUP')
try:
dashboard = config.get('PANEL_GROUP_DASHBOARD')
if not dashboard:
LOG.warning("Skipping %s because it doesn't have "
"PANEL_GROUP_DASHBOARD defined.", config.__name__)
return
dashboard_cls = self.get_dashboard(dashboard)
panel_group_name = config.get('PANEL_GROUP_NAME')
if not panel_group_name:
LOG.warning("Skipping %s because it doesn't have "
"PANEL_GROUP_NAME defined.", config.__name__)
return
# Create the panel group class
panel_group = type(panel_group_slug,
(PanelGroup, ),
{'slug': panel_group_slug,
'name': panel_group_name,
'panels': []},)
# Add the panel group to dashboard
panels = list(dashboard_cls.panels)
panels.append(panel_group)
dashboard_cls.panels = tuple(panels)
# Trigger the autodiscovery to completely load the new panel group
dashboard_cls._autodiscover_complete = False
dashboard_cls._autodiscover()
except Exception as e:
LOG.warning('Could not process panel group %(panel_group)s: '
'%(exc)s',
{'panel_group': panel_group_slug, 'exc': e})
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

@ -0,0 +1,17 @@
# 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.
# Importing non-modules that are not used explicitly
from horizon.browsers.base import ResourceBrowser # noqa
from horizon.browsers.views import ResourceBrowserView # noqa

View File

@ -0,0 +1,145 @@
# 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 # noqa
from horizon.tables import DataTable # noqa
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)
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

@ -0,0 +1,46 @@
# 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

@ -0,0 +1,58 @@
# 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 # noqa
from horizon.utils import memoized
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)
@memoized.memoized_method
def get_browser(self):
browser = self.browser_class(self.request, **self.kwargs)
browser.set_tables(self.get_tables())
if not self.navigation_selection:
ct = browser.content_table
item = browser.navigable_item_name.lower()
ct._no_data_message = _("Select a %s to browse.") % item
return browser
def get_tables(self):
tables = super(ResourceBrowserView, self).get_tables()
# Tells the navigation table what is selected.
navigation_table = tables[
self.browser_class.navigation_table_class._meta.name]
navigation_item = self.kwargs.get(
self.browser_class.navigation_kwarg_name)
navigation_table.current_item_id = navigation_item
return tables
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

@ -0,0 +1,47 @@
# 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 copy
from django.utils.functional import empty # noqa
from django.utils.functional import LazyObject # noqa
class LazySettings(LazyObject):
def _setup(self, name=None):
from django.conf import settings
from horizon.conf.default import HORIZON_CONFIG as DEFAULT_CONFIG # noqa
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

@ -0,0 +1,13 @@
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

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

View File

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

View File

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

View File

@ -0,0 +1,11 @@
{% 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

@ -0,0 +1,47 @@
# 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.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': 'off',
# Enable or disable simplified floating IP address management.
'simple_ip_management': True
}

View File

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

View File

@ -0,0 +1,12 @@
{% load horizon %}{% jstemplate %}[% extends '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 main %]
[% endblock %]
{% endjstemplate %}

View File

@ -0,0 +1,7 @@
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

@ -0,0 +1,11 @@
from django.conf.urls import patterns
from django.conf.urls import url
from {{ dash_path }}.{{ panel_name }}.views \
import IndexView
urlpatterns = patterns(
'',
url(r'^$', IndexView.as_view(), name='index'),
)

View File

@ -0,0 +1,10 @@
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

@ -0,0 +1,42 @@
# 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

@ -0,0 +1,68 @@
# Copyright 2014 IBM Corp.
#
# 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.
# Map Horizon languages to datepicker locales
LOCALE_MAPPING = {
'ar': 'ar',
'az': 'az',
'bg': 'bg',
'ca': 'ca',
'cs': 'cs',
'cy': 'cy',
'da': 'da',
'de': 'de',
'el': 'el',
'es': 'es',
'et': 'et',
'fa': 'fa',
'fi': 'fi',
'fr': 'fr',
'gl': 'gl',
'he': 'he',
'hr': 'hr',
'hu': 'hu',
'id': 'id',
'is': 'is',
'it': 'it',
'ja': 'ja',
'ka': 'ka',
'kk': 'kk',
'ko': 'kr', # difference between horizon and datepicker
'lt': 'lt',
'lv': 'lv',
'mk': 'mk',
'ms': 'ms',
'nb': 'nb',
'nl-be': 'nl-BE',
'nl': 'nl',
'no': 'no',
'pl': 'pl',
'pt-br': 'pt-BR',
'pt': 'pt',
'ro': 'ro',
'rs-latin': 'rs-latin',
'sr': 'rs', # difference between horizon and datepicker
'ru': 'ru',
'sk': 'sk',
'sl': 'sl',
'sq': 'sq',
'sv': 'sv',
'sw': 'sw',
'th': 'th',
'tr': 'tr',
'ua': 'ua',
'vi': 'vi',
'zh-cn': 'zh-CN',
'zh-tw': 'zh-TW',
}

View File

@ -0,0 +1,92 @@
# 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 # noqa
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 # noqa
@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 # noqa
# 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

@ -0,0 +1,364 @@
# 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
import six
from django.core.management import color_style # noqa
from django.http import HttpRequest # noqa
from django.utils import encoding
from django.utils.translation import ugettext_lazy as _
from django.views.debug import CLEANSED_SUBSTITUTE # noqa
from django.views.debug import SafeExceptionReporterFilter # noqa
from horizon.conf import HORIZON_CONFIG # noqa
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 Conflict(HorizonException):
"""Generic error to replace all "Conflict"-type API errors."""
status_code = 409
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 ConfigurationError(HorizonException):
"""Exception to be raised when invalid settings have been provided."""
pass
class NotAvailable(HorizonException):
"""Exception to be raised when something is not available."""
pass
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, Conflict, NotAvailable, ServiceCatalogException)
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_unauthorized(request, message, redirect, ignore, escalate, handled,
force_silence, force_log,
log_method, log_entry, log_level):
if ignore:
return NotAuthorized
if not force_silence and not handled:
log_method(error_color("Unauthorized: %s" % log_entry))
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:
# Prevents creation of circular import. django.contrib.auth
# requires openstack_dashboard.settings to be loaded (by trying to
# access settings.CACHES in in django.core.caches) while
# openstack_dashboard.settings requires django.contrib.auth to be
# loaded while importing openstack_auth.utils
from django.contrib.auth import logout # noqa
logout(request)
raise NotAuthorized
# Otherwise continue and present our "unauthorized" error message.
return NotAuthorized
def handle_notfound(request, message, redirect, ignore, escalate, handled,
force_silence, force_log,
log_method, log_entry, log_level):
if not force_silence and not handled and (not ignore or force_log):
log_method(error_color("Not Found: %s" % log_entry))
if not ignore and not handled:
messages.error(request, message or log_entry)
if redirect:
raise Http302(redirect)
if not escalate:
return NotFound # return to normal code flow
def handle_recoverable(request, message, redirect, ignore, escalate, handled,
force_silence, force_log,
log_method, log_entry, log_level):
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" % log_entry))
if not ignore and not handled:
messages.error(request, message or log_entry)
if redirect:
raise Http302(redirect)
if not escalate:
return RecoverableError # return to normal code flow
HANDLE_EXC_METHODS = [
{'exc': UNAUTHORIZED, 'handler': handle_unauthorized, 'set_wrap': False},
{'exc': NOT_FOUND, 'handler': handle_notfound, 'set_wrap': True},
{'exc': RECOVERABLE, 'handler': handle_recoverable, 'set_wrap': True},
]
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
log_entry = encoding.force_text(exc_value)
# 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 = encoding.force_text(message) % {"exc": log_entry}
if message:
message = encoding.force_text(message)
for exc_handler in HANDLE_EXC_METHODS:
if issubclass(exc_type, exc_handler['exc']):
if exc_handler['set_wrap']:
wrap = True
handler = exc_handler['handler']
ret = handler(request, message, redirect, ignore, escalate,
handled, force_silence, force_log,
log_method, log_entry, log_level)
if ret:
return ret # 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])
six.reraise(exc_type, exc_value, exc_traceback)

View File

@ -0,0 +1,76 @@
# 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.
# Importing non-modules that are not used explicitly
# FIXME(gabriel): Legacy imports for API compatibility.
from django.core.exceptions import ValidationError # noqa
from django.forms.fields import * # noqa
from django.forms.forms import * # noqa
from django.forms import widgets
from django.forms.widgets import * # noqa
# Convenience imports for public API components.
from horizon.forms.base import DateForm # noqa
from horizon.forms.base import SelfHandlingForm # noqa
from horizon.forms.base import SelfHandlingMixin # noqa
from horizon.forms.fields import DynamicChoiceField # noqa
from horizon.forms.fields import DynamicTypedChoiceField # noqa
from horizon.forms.fields import IPField # noqa
from horizon.forms.fields import IPv4 # noqa
from horizon.forms.fields import IPv6 # noqa
from horizon.forms.fields import MultiIPField # noqa
from horizon.forms.fields import SelectWidget # noqa
from horizon.forms.views import ModalFormMixin # noqa
from horizon.forms.views import ModalFormView # noqa
__all__ = [
"SelfHandlingMixin",
"SelfHandlingForm",
"DateForm",
"ModalFormView",
"ModalFormMixin",
"DynamicTypedChoiceField",
"DynamicChoiceField",
"IPField",
"IPv4",
"IPv6",
"MultiIPField",
"SelectWidget"
# From django.forms
"ValidationError",
# From django.forms.fields
'Field', 'CharField', 'IntegerField', 'DateField', 'TimeField',
'DateTimeField', 'TimeField', 'RegexField', 'EmailField', 'FileField',
'ImageField', 'URLField', 'BooleanField', 'NullBooleanField',
'ChoiceField', 'MultipleChoiceField', 'ComboField', 'MultiValueField',
'FloatField', 'DecimalField', 'SplitDateTimeField', 'IPAddressField',
'GenericIPAddressField', 'FilePathField', 'SlugField', 'TypedChoiceField',
'TypedMultipleChoiceField',
# From django.forms.widgets
"widgets",
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput', 'FileInput',
'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
# From django.forms.forms
'BaseForm', 'Form',
]

View File

@ -0,0 +1,62 @@
# 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 # noqa
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.
"""
required_css_class = 'required'
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])
def set_warning(self, message):
"""Sets a warning on the form.
Unlike NON_FIELD_ERRORS, this doesn't fail form validation.
"""
self.warnings = 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

@ -0,0 +1,253 @@
# 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 re
import netaddr
from django.core.exceptions import ValidationError # noqa
from django.core import urlresolvers
from django.forms import fields
from django.forms.util import flatatt # noqa
from django.forms import widgets
from django.utils.encoding import force_text
from django.utils.functional import Promise # noqa
from django.utils import html
from django.utils.translation import ugettext_lazy as _
ip_allowed_symbols_re = re.compile(r'^[a-fA-F0-9:/\.]+$')
IPv4 = 1
IPv6 = 2
class IPField(fields.Field):
"""Form field for entering IP/range values, with validation.
Supports IPv4/IPv6 in the format:
.. xxx.xxx.xxx.xxx
.. xxx.xxx.xxx.xxx/zz
.. ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
.. ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/zz
and all compressed forms. Also the short forms
are supported:
xxx/yy
xxx.xxx/yy
.. attribute:: version
Specifies which IP version to validate,
valid values are 1 (fields.IPv4), 2 (fields.IPv6) or
both - 3 (fields.IPv4 | fields.IPv6).
Defaults to IPv4 (1)
.. attribute:: mask
Boolean flag to validate subnet masks along with IP address.
E.g: 10.0.0.1/32
.. attribute:: mask_range_from
Subnet range limitation, e.g. 16
That means the input mask will be checked to be in the range
16:max_value. Useful to limit the subnet ranges
to A/B/C-class networks.
"""
invalid_format_message = _("Incorrect format for IP address")
invalid_version_message = _("Invalid version for IP address")
invalid_mask_message = _("Invalid subnet mask")
max_v4_mask = 32
max_v6_mask = 128
def __init__(self, *args, **kwargs):
self.mask = kwargs.pop("mask", None)
self.min_mask = kwargs.pop("mask_range_from", 0)
self.version = kwargs.pop('version', IPv4)
super(IPField, self).__init__(*args, **kwargs)
def validate(self, value):
super(IPField, self).validate(value)
if not value and not self.required:
return
try:
if self.mask:
self.ip = netaddr.IPNetwork(value)
else:
self.ip = netaddr.IPAddress(value)
except Exception:
raise ValidationError(self.invalid_format_message)
if not any([self.version & IPv4 > 0 and self.ip.version == 4,
self.version & IPv6 > 0 and self.ip.version == 6]):
raise ValidationError(self.invalid_version_message)
if self.mask:
if self.ip.version == 4 and \
not self.min_mask <= self.ip.prefixlen <= self.max_v4_mask:
raise ValidationError(self.invalid_mask_message)
if self.ip.version == 6 and \
not self.min_mask <= self.ip.prefixlen <= self.max_v6_mask:
raise ValidationError(self.invalid_mask_message)
def clean(self, value):
super(IPField, self).clean(value)
return str(getattr(self, "ip", ""))
class MultiIPField(IPField):
"""Extends IPField to allow comma-separated lists of addresses."""
def validate(self, value):
self.addresses = []
if value:
addresses = value.split(',')
for ip in addresses:
super(MultiIPField, self).validate(ip)
self.addresses.append(ip)
else:
super(MultiIPField, self).validate(value)
def clean(self, value):
super(MultiIPField, self).clean(value)
return str(','.join(getattr(self, "addresses", [])))
class SelectWidget(widgets.Select):
"""Customizable select widget, that allows to render
data-xxx attributes from choices. This widget also
allows user to specify additional html attributes
for choices.
.. attribute:: data_attrs
Specifies object properties to serialize as
data-xxx attribute. If passed ('id', ),
this will be rendered as:
<option data-id="123">option_value</option>
where 123 is the value of choice_value.id
.. attribute:: transform
A callable used to render the display value
from the option object.
.. attribute:: transform_html_attrs
A callable used to render additional HTML attributes
for the option object. It returns a dictionary
containing the html attributes and their values.
For example, to define a title attribute for the
choices:
helpText = { 'Apple': 'This is a fruit',
'Carrot': 'This is a vegetable' }
def get_title(data):
text = helpText.get(data, None)
if text:
return {'title': text}
else:
return {}
....
....
widget=forms.SelectWidget( attrs={'class': 'switchable',
'data-slug': 'source'},
transform_html_attrs=get_title )
self.fields[<field name>].choices =
([
('apple','Apple'),
('carrot','Carrot')
])
"""
def __init__(self, attrs=None, choices=(), data_attrs=(), transform=None,
transform_html_attrs=None):
self.data_attrs = data_attrs
self.transform = transform
self.transform_html_attrs = transform_html_attrs
super(SelectWidget, self).__init__(attrs, choices)
def render_option(self, selected_choices, option_value, option_label):
option_value = force_text(option_value)
other_html = (u' selected="selected"'
if option_value in selected_choices else '')
if callable(self.transform_html_attrs):
html_attrs = self.transform_html_attrs(option_label)
other_html += flatatt(html_attrs)
if not isinstance(option_label, (basestring, Promise)):
for data_attr in self.data_attrs:
data_value = html.conditional_escape(
force_text(getattr(option_label,
data_attr, "")))
other_html += ' data-%s="%s"' % (data_attr, data_value)
if callable(self.transform):
option_label = self.transform(option_label)
return u'<option value="%s"%s>%s</option>' % (
html.escape(option_value), other_html,
html.conditional_escape(force_text(option_label)))
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[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

View File

@ -0,0 +1,196 @@
# 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.conf import settings
from django import http
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import views
ADD_TO_FIELD_HEADER = "HTTP_X_HORIZON_ADD_TO_FIELD"
class ModalBackdropMixin(object):
"""This mixin class is to be used for together with ModalFormView and
WorkflowView classes to augment them with modal_backdrop context data.
.. attribute: modal_backdrop (optional)
The appearance and behavior of backdrop under the modal element.
Possible options are:
* 'true' - show backdrop element outside the modal, close the modal
after clicking on backdrop (the default one);
* 'false' - do not show backdrop element, do not close the modal after
clicking outside of it;
* 'static' - show backdrop element outside the modal, do not close
the modal after clicking on backdrop.
"""
modal_backdrop = 'static'
def __init__(self):
super(ModalBackdropMixin, self).__init__()
config = getattr(settings, 'HORIZON_CONFIG', {})
if 'modal_backdrop' in config:
self.modal_backdrop = config['modal_backdrop']
def get_context_data(self, **kwargs):
context = super(ModalBackdropMixin, self).get_context_data(**kwargs)
context['modal_backdrop'] = self.modal_backdrop
return context
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(ModalBackdropMixin, ModalFormMixin, views.HorizonFormView):
"""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.
.. attribute: modal_id (recommended)
The HTML element id of this modal.
.. attribute: modal_header (recommended)
The title of this modal.
.. attribute: form_id (recommended)
The HTML element id of the form in this modal.
.. attribute: submit_url (required)
The url for a submit action.
.. attribute: submit_label (optional)
The label for the submit button. This label defaults to ``Submit``.
This button should only be visible if the action_url is defined.
Clicking on this button will post to the action_url.
.. attribute: cancel_label (optional)
The label for the cancel button. This label defaults to ``Cancel``.
Clicking on this button will redirect user to the cancel_url.
.. attribute: cancel_url (optional)
The url for a cancel action. This url defaults to the success_url
if omitted. Note that the cancel_url redirect is nullified when
shown in a modal dialog.
"""
modal_id = None
modal_header = ""
form_id = None
submit_url = None
submit_label = _("Submit")
cancel_label = _("Cancel")
cancel_url = None
def get_context_data(self, **kwargs):
context = super(ModalFormView, self).get_context_data(**kwargs)
context['modal_id'] = self.modal_id
context['modal_header'] = self.modal_header
context['form_id'] = self.form_id
context['submit_url'] = self.submit_url
context['submit_label'] = self.submit_label
context['cancel_label'] = self.cancel_label
context['cancel_url'] = self.get_cancel_url()
return context
def get_cancel_url(self):
return self.cancel_url or self.success_url
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 Exception:
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

@ -0,0 +1,68 @@
# 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.
"""
Wrapper for loading templates from "templates" directories in panel modules.
"""
import os
import django
from django.conf import settings
from django.template.base import TemplateDoesNotExist # noqa
if django.get_version() >= '1.8':
from django.template.engine import Engine
from django.template.loaders.base import Loader as tLoaderCls
else:
from django.template.loader import BaseLoader as tLoaderCls # noqa
from django.utils._os import safe_join # noqa
# Set up a cache of the panel directories to search.
panel_template_dirs = {}
class TemplateLoader(tLoaderCls):
is_usable = True
def get_template_sources(self, template_name):
bits = template_name.split('/', 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:
with open(path) as file:
return (file.read().decode(settings.FILE_CHARSET), path)
except IOError:
pass
raise TemplateDoesNotExist(template_name)
if django.get_version() >= '1.8':
e = Engine()
_loader = TemplateLoader(e)
else:
_loader = TemplateLoader()

View File

@ -0,0 +1,512 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Andreas Jaeger <jaegerandi@gmail.com>, 2014
# Robert Simai, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
"PO-Revision-Date: 2015-04-01 22:52+0000\n"
"Last-Translator: Robert Simai\n"
"Language-Team: German (http://www.transifex.com/projects/p/horizon/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:475
msgid "Other"
msgstr "Andere"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Navigations-Eintrag"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Wähle %s zum Durchsuchen."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "Password wurde nicht akzeptiert"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Bitte melden Sie sich an um fortzufahren."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "Sie sind nicht berechtigt für den Zugriff auf %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "Ein(e) %(resource)s mit dem Namen \"%(name)s\" existiert bereits."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "Nicht berechtigt: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "Nicht autorisiert. Bitte melden Sie sich erneut an."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Ungültiges Format der IP-Adresse"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Ungültige Version der IP-Adresse"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Ungültige Subnetzmaske"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Abschicken"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Abbrechen"
#: middleware.py:103
msgid "Session timed out."
msgstr "Die Sitzung ist abgelaufen."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Filter"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "Diese Aktion kann nicht rückgängig gemacht werden."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "Sie haben keine Berechtigung für %(action)s: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Nicht möglich: %(action)s: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Löschen"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Gelöscht"
#: tables/actions.py:948
msgid "Update"
msgstr "Aktualisieren"
#: tables/actions.py:949
msgid "Updated"
msgstr "Aktualisiert"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "Das Attribut %(attr)s existiert nicht für %(obj)s."
#: tables/base.py:990
msgid "No items to display."
msgstr "Keine Einträge zum anzeigen."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Aktionen"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Keine Übereinstimmung für die ID \"%s\"."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Bitte wählen Sie vor dem Ausführen dieser Aktion eine Zeile aus."
#: tables/base.py:1570
msgid "N/A"
msgstr "Nicht verfügbar"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Angemeldet als: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Hilfe"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Abmelden"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\nWenn Sie nicht sicher sind, welche Authentifizierungsmethode zu verwenden ist, kontaktieren Sie Ihren Administrator."
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Anmelden"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "Sie haben keine Zugriffsrechte auf die Ressource:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Melden Sie sich als anderer Benutzer an oder gehen Sie zurück auf die <a href=\"%(home_url)s\"> Startseite</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Anmelden"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Verbinden"
#: templates/auth/login.html:4
msgid "Login"
msgstr "Login"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Information:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Warnung:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Erfolg:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Fehler:"
#: templates/horizon/common/_data_table.html:63
msgid "Summary"
msgstr "Zusammenfassung"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Zeige %(counter)s Eintrag"
msgstr[1] "Zeige %(counter)s Einträge"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Zurück"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Weiter&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "Weitere Aktionen"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Eine Zeile hinzufügen"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Übersicht Begrenzungen"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Instanzen"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span> %(used)s </span> von <span> %(available)s </span> benutzt"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPUs"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "Floating IPs"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span> %(used)s </span> von <span> %(available)s </span> zugewiesen"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Sicherheitsgruppen"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Datenträger"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Datenträger-Speicher"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
msgstr "Speichern"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Zeige %(nav_items)s Eintrag"
msgstr[1] "Zeige %(nav_items)s Einträge"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Zeige %(content_items)s Eintrag"
msgstr[1] "Zeige %(content_items)s Einträge"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Nutzungsübersicht"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Wählen Sie einen Zeitbereich, um die Auslastung abzurufen:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n <label>Von:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n <label>Bis:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "Das Datum sollte im YYYY-mm-dd Format sein."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Aktive Instanzen:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "Aktiver RAM:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "VCPU-Stunden in diesem Zeitraum:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "GB-Stunden in diesem Zeitraum:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "RAM-Stunden in diesem Zeitraum:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Zurück"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Weiter"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "Keine Begrenzung"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "Verfügbar"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d Byte"
msgstr[1] "%(size)d Bytes"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 Bytes"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "Sell Puppy"
msgstr[1] "Sell Puppies"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "Sold Puppy"
msgstr[1] "Sold Puppies"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Fälschung"
#: utils/filters.py:49
msgid "Never"
msgstr "Niemals"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Keine gültige Port-Nummer"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Keine gültige IP-Protokollnummer"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "Im Port-Bereich ist nur ein Doppelpunkt erlaubt"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "Port-Nummer muss ganzzahlig sein"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "Die Zeichenkette darf nur druckbare ASCII-Zeichen enthalten."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Verarbeite..."
#: workflows/base.py:475
msgid "All available"
msgstr "Alle verfügbaren"
#: workflows/base.py:476
msgid "Members"
msgstr "Mitglieder"
#: workflows/base.py:477
msgid "None available."
msgstr "Keine verfügbar."
#: workflows/base.py:478
msgid "No members."
msgstr "Keine Mitglieder."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s erfolgreich abgeschlossen."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s nicht abgeschlossen."

View File

@ -0,0 +1,621 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Andreas Jaeger <jaegerandi@gmail.com>, 2015
# Ettore Atalan <atalanttore@googlemail.com>, 2015
# Robert Simai, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-11 11:47-0500\n"
"PO-Revision-Date: 2015-04-12 03:25+0000\n"
"Last-Translator: Ettore Atalan <atalanttore@googlemail.com>\n"
"Language-Team: German (http://www.transifex.com/projects/p/horizon/language/de/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: de\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "Die Aktion kann nicht ausgeführt werden. Der Inhalt dieser Spalte enthält Fehler oder es fehlen Informationen."
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "Detail-Informationen"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "Sie können Ressourcen-Metadaten bestimmen, indem Sie Positionen aus der linken Spalte in die rechte bewegen. In der linken Spalte finden Sie Metadaten-Definitionen aus dem Glace Metadaten-Katalog. Verwenden Sie die \"Andere\" Option, um Metadaten mit dem Schlüssel Ihrer Wahl hinzuzufügen."
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "Min"
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "Max"
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "Minimale Länge"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "Maximale Länge"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "Muster-Übereinstimmungsfehler"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "Ganzzahl erforderlich"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "Dezimalzahl erforderlich"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "Erforderlich"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "Doppelte Schlüssel sind nicht erlaubt"
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "Filter"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "Verfügbare Metadaten"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "Vorhandene Metadaten"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "Angepasst"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "Keine Metadaten vorhanden"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "Keine existierenden Metadaten"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "Abschicken"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "Abbrechen"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "Zugewiesen"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "Verfügbar"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "Eines auswählen"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "Wählen Sie einen Eintrag aus den verfügbaren Positionen aus"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "Keine verfügbaren Positionen"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "Erweitern, um zugewiesene Positionen zu sehen"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "Erweitern, um verfügbare Positionen zu sehen"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "Klicken zum zeigen oder verbergen"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "Umsortieren der Positionen durch ziehen und ablegen"
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "Klicken um mehr Details zu sehen"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "%(found)s von %(total)s gefunden"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "Klicken Sie hier um die Zeile zu erweitern und die Fehler zu sehen."
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "Zurück"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "Weiter"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "Beenden"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "Verbinde"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "Öffnen"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "Schließen"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "Geschlossen"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "Status: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "Ja"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "Nein"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s Bytes"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "Zeige %s Eintrag"
msgstr[1] "Zeige %s Einträge"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "Datenträger können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "Datenträger-Schattenkopien können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "Benutzerkonfiguration kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "Admin-Konfiguration kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "Einstellungen können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "Einstellung ist nicht aktiviert: %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "Abbild kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "Abbilder können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "Namensräume können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "Benutzer können nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "Benutzer kann nicht angelegt werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "Benutzer können nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "Aktuelle Benutzersitzung kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "Benutzer kann nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "Benutzer kann nicht bearbeitet werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "Benutzer kann nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "Rolle kann nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "Die Rolle kann nicht erstellt werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "Die Rollen können nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "Die Rolle kann nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "Die Rolle kann nicht bearbeitet werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "Die Rolle kann nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "Domänen können nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "Die Domäne kann nicht erstellt werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "Die Domänen können nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "Die Domäne kann nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "Die Domäne kann nicht bearbeitet werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "Die Domäne kann nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "Projekte können nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "Das Projekt kann nicht erstellt werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "Die Projekte können nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "Das Projekt kann nicht abgerufen werden"
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "Das Projekt kann nicht bearbeitet werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "Das Projekt kann nicht gelöscht werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "Die Rolle kann nicht gewährt werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "Die Dienstekatalog kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "Dienst-Typ ist nicht aktiviert: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "Dienstekatalog kann nicht von Keystone abgerufen werden."
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "Netzwerke können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "Netzwerk kann nicht angelegt werden."
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "Subnetze können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "Subnetz kann nicht erzeugt werden."
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "Ports können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "Schlüsselpaare können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "Das Schlüsselpaar kann nicht importiert werden."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "Das Schlüsselpaar kann nicht erstellt werden."
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "Verfügbarkeitszonen können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "Begrenzungen können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "Der Server kann nicht erstellt werden."
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "Server kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "Erweiterungen können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "Varianten können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "Variante kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "Extraspezifikationen zur Variante können nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "Nicht aktivierte Erweiterung: %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "Liste der Nova-Erweiterungen kann nicht abgerufen werden."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "Richtlinienüberprüfung fehlgeschlagen."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "Sicherheitsgruppen können nicht abgerufen werden."
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "Lade"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "Keine Daten verfügbar."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "Es gab ein Problem bei der Kommunikation mit dem Server. Bitte versuchen Sie es noch einmal."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "Die Datei konnte nicht gelesen werden"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "Das Passwort konnte nicht entschlüsselt werden"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "Keine Rollen"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Rollen"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "Gefahr:"
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "Warnung:"
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "Nachricht:"
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "Erfolg:"
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "Fehler:"
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "In Arbeit"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "Es gab einen Fehler beim Abschicken des Formulars. Bitte versuchen Sie es noch einmal."
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "Keine"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "Löschen"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "STATUS"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "Schnittstellen"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "Schnittstelle löschen"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "Konsole öffnen"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "Details anzeigen"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "Router löschen"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "Router-Details anzeigen"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "Schnittstelle hinzufügen"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "Instanz terminieren"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "Instanz-Details anzeigen"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "Kein Eintrag zum anzeigen."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "Ein Fehler ist beim Aktualisieren aufgetreten."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "Sie haben %s ausgewählt."
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "%s bestätigen"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "Bitte bestätigen Sie Ihre Auswahl."
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "Keine Berechtigung für diese Aktion."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "Die Passwörter stimmen nicht überein."

View File

@ -0,0 +1,510 @@
# 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: 2015-04-07 01:16-0500\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"
#: base.py:475
msgid "Other"
msgstr ""
#: browsers/base.py:88
msgid "Navigation Item"
msgstr ""
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr ""
#: conf/default.py:41
msgid "Password is not accepted"
msgstr ""
#: decorators.py:53
msgid "Please log in to continue."
msgstr ""
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr ""
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr ""
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr ""
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr ""
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr ""
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr ""
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr ""
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr ""
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr ""
#: middleware.py:103
msgid "Session timed out."
msgstr ""
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr ""
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr ""
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr ""
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr ""
#: tables/actions.py:915
msgid "Delete"
msgstr ""
#: tables/actions.py:917
msgid "Deleted"
msgstr ""
#: tables/actions.py:948
msgid "Update"
msgstr ""
#: tables/actions.py:949
msgid "Updated"
msgstr ""
#: tables/base.py:305
msgid "-"
msgstr ""
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr ""
#: tables/base.py:990
msgid "No items to display."
msgstr ""
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr ""
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr ""
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr ""
#: tables/base.py:1570
msgid "N/A"
msgstr ""
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr ""
#: templates/_header.html:7
msgid "Help"
msgstr ""
#: templates/_header.html:9
msgid "Sign Out"
msgstr ""
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your "
"administrator.\n"
" "
msgstr ""
#: templates/auth/_login.html:5
msgid "Log In"
msgstr ""
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr ""
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home page</a>"
msgstr ""
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr ""
#: templates/auth/_login.html:46
msgid "Connect"
msgstr ""
#: templates/auth/login.html:4
msgid "Login"
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:63
msgid "Summary"
msgstr ""
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] ""
msgstr[1] ""
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr ""
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr ""
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr ""
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr ""
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr ""
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr ""
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr ""
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr ""
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr ""
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr ""
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr ""
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr ""
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr ""
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr ""
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr ""
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr ""
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
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/_usage_summary.html:3
msgid "Usage Summary"
msgstr ""
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr ""
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr ""
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr ""
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr ""
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr ""
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr ""
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr ""
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr ""
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr ""
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr ""
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr ""
#: templatetags/branding.py:34
msgid "Horizon"
msgstr ""
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr ""
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr ""
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] ""
msgstr[1] ""
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr ""
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr ""
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr ""
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr ""
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr ""
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr ""
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] ""
msgstr[1] ""
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] ""
msgstr[1] ""
#: test/tests/views.py:59
msgid "Fake"
msgstr ""
#: utils/filters.py:49
msgid "Never"
msgstr ""
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr ""
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr ""
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr ""
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr ""
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr ""
#: workflows/base.py:71
msgid "Processing..."
msgstr ""
#: workflows/base.py:475
msgid "All available"
msgstr ""
#: workflows/base.py:476
msgid "Members"
msgstr ""
#: workflows/base.py:477
msgid "None available."
msgstr ""
#: workflows/base.py:478
msgid "No members."
msgstr ""
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr ""
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr ""

View File

@ -0,0 +1,618 @@
# 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: 2015-04-12 01:16-0500\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/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr ""
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr ""
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr ""
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr ""
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr ""
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr ""
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr ""
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr ""
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr ""
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr ""
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr ""
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr ""
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr ""
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr ""
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] ""
msgstr[1] ""
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr ""
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr ""
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr ""
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr ""
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr ""
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr ""
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr ""
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr ""
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr ""
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr ""
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr ""
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr ""
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr ""
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr ""
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr ""
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr ""
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr ""
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr ""
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr ""
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr ""
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr ""
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr ""
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr ""
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr ""
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr ""
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr ""
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr ""
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr ""
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr ""
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr ""
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr ""
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr ""
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr ""
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr ""
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr ""
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr ""
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr ""
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr ""
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr ""
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr ""
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr ""
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr ""

View File

@ -0,0 +1,511 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Tom Fifield <tom@openstack.org>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-07 01:16-0500\n"
"PO-Revision-Date: 2015-04-20 07:07+0000\n"
"Last-Translator: Tom Fifield <tom@openstack.org>\n"
"Language-Team: English (Australia) (http://www.transifex.com/projects/p/horizon/language/en_AU/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_AU\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: base.py:475
msgid "Other"
msgstr "Other"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Navigation Item"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Select a %s to browse."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "Password is not accepted"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Please log in to continue."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "You are not authorised to access %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "A %(resource)s with the name \"%(name)s\" already exists."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "Unauthorised: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "Unauthorised. Please try logging in again."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Incorrect format for IP address"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Invalid version for IP address"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Invalid subnet mask"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Submit"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Cancel"
#: middleware.py:103
msgid "Session timed out."
msgstr "Session timed out."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Filter"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "This action cannot be undone."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "You are not allowed to %(action)s: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Unable to %(action)s: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Delete"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Deleted"
#: tables/actions.py:948
msgid "Update"
msgstr "Update"
#: tables/actions.py:949
msgid "Updated"
msgstr "Updated"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, 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:990
msgid "No items to display."
msgstr "No items to display."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Actions"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "No match returned for the id \"%s\"."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Please select a row before taking that action."
#: tables/base.py:1570
msgid "N/A"
msgstr "N/A"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Logged in as: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Help"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Sign Out"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\n If you are not sure which authentication method to use, contact your administrator.\n "
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Log In"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "You do not have permission to access the resource:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Login as different user or go back to <a href=\"%(home_url)s\"> home page</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Sign In"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Connect"
#: templates/auth/login.html:4
msgid "Login"
msgstr "Login"
#: 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:63
msgid "Summary"
msgstr "Summary"
#: templates/horizon/common/_data_table.html:72
#, 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.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Prev"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Next&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "More Actions"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Add a row"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Limit Summary"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Instances"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Used <span> %(used)s </span> of <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPUs"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "Floating IPs"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Security Groups"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Volumes"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Volume Storage"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
msgstr "Save"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Displaying %(nav_items)s item"
msgstr[1] "Displaying %(nav_items)s items"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Displaying %(content_items)s item"
msgstr[1] "Displaying %(content_items)s items"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Usage Summary"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Select a period of time to query its usage:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n <label>From:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n <label>To:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "The date should be in YYYY-mm-dd format."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Active Instances:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "Active RAM:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "This Period's VCPU-Hours:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "This Period's GB-Hours:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "This Period's RAM-Hours:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Back"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Next"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "No Limit"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "Available"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d Byte"
msgstr[1] "%(size)d Bytes"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 Bytes"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "Sell Puppy"
msgstr[1] "Sell Puppies"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "Sold Puppy"
msgstr[1] "Sold Puppies"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Fake"
#: utils/filters.py:49
msgid "Never"
msgstr "Never"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Not a valid port number"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Not a valid IP protocol number"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "One colon allowed in port range"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "Port number must be integer"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "The string may only contain ASCII printable characters."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Processing..."
#: workflows/base.py:475
msgid "All available"
msgstr "All available"
#: workflows/base.py:476
msgid "Members"
msgstr "Members"
#: workflows/base.py:477
msgid "None available."
msgstr "None available."
#: workflows/base.py:478
msgid "No members."
msgstr "No members."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s completed successfully."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s did not complete."

View File

@ -0,0 +1,619 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Tom Fifield <tom@openstack.org>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
"Last-Translator: Tom Fifield <tom@openstack.org>\n"
"Language-Team: English (Australia) (http://www.transifex.com/projects/p/horizon/language/en_AU/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: en_AU\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "The action cannot be performed. The contents of this row have errors or are missing information."
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "Detail Information"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "You can specify resource metadata by moving items from the left column to the right column. In the left columns there are metadata definitions from the Glance Metadata Catalog. Use the \"Other\" option to add metadata with the key of your choice."
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "Min"
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "Max"
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "Min length"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "Max length"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "Pattern mismatch"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "Integer required"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "Decimal required"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "Required"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "Duplicate keys are not allowed"
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "Filter"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "Available Metadata"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "Existing Metadata"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "Custom"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "No available metadata"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "No existing metadata"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "Submit"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "Cancel"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "Allocated"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "Available"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "Select one"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "Select an item from Available items below"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "No available items"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "Expand to see allocated items"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "Expand to see available items"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "Click to show or hide"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "Re-order items using drag and drop"
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "Click to see more details"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "Found %(found)s of %(total)s"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "Click here to expand the row and view the errors."
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "Back"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "Next"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "Finish"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "Connecting"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "Open"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "Closing"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "Closed"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "Status: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "Yes"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "No"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s bytes"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "Displaying %s item"
msgstr[1] "Displaying %s items"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "Unable to retrieve volumes."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "Unable to retrieve volume snapshots."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "Unable to retrieve user configuration."
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "Unable to retrieve admin configuration."
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "Unable to retrieve settings."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "Setting is not enabled: %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "Unable to retrieve image."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "Unable to retrieve images."
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "Unable to retrieve namespaces."
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "Unable to retrieve users"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "Unable to create the user."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "Unable to delete the users."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "Unable to retrieve the current user session."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "Unable to retrieve the user"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "Unable to edit the user."
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "Unable to delete the user."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "Unable to retrieve role"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "Unable to create the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "Unable to delete the roles."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "Unable to retrieve the role"
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "Unable to edit the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "Unable to delete the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "Unable to retrieve domains"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "Unable to create the domain."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "Unable to delete the domains."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "Unable to retrieve the domain"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "Unable to edit the domain."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "Unable to delete the domain."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "Unable to retrieve projects"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "Unable to create the project."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "Unable to delete the projects."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "Unable to retrieve the project"
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "Unable to edit the project."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "Unable to delete the project."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "Unable to grant the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "Unable to fetch the service catalog."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "Service type is not enabled: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "Cannot get service catalog from keystone."
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "Unable to retrieve networks."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "Unable to create the network."
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "Unable to retrieve subnets."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "Unable to create the subnet."
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "Unable to retrieve ports."
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "Unable to retrieve keypairs."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "Unable to import the keypair."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "Unable to create the keypair."
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "Unable to retrieve availability zones."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "Unable to retrieve limits."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "Unable to create the server."
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "Unable to retrieve server."
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "Unable to retrieve extensions."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "Unable to retrieve flavors."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "Unable to retrieve flavor."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "Unable to retrieve flavor extra specs."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "Extension is not enabled: %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "Cannot get nova extension list."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "Policy check failed."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "Unable to retrieve security groups."
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "Loading"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "No data available."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "An error occurred. Please try again later."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "There was a problem communicating with the server, please try again."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "Could not read the file"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "Could not decrypt the password"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "No roles"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Roles"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "Danger: "
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "Warning: "
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "Notice: "
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "Success: "
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "Error: "
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "Working"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "There was an error submitting the form. Please try again."
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "None"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "Delete"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "STATUS"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "Interfaces"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "Delete Interface"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "Open Console"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "View Details"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "Delete Router"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "View Router Details"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "Add Interface"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "Terminate Instance"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "View Instance Details"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "No items to display."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "An error occurred while updating."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "You have selected %s. "
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "Confirm %s"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "Please confirm your selection. "
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "Not authorised to do this operation."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "Passwords do not match."

View File

@ -0,0 +1,512 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Andi Chandler <andi@gowling.com>, 2014
# Rob Cresswell <robert.cresswell@outlook.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
"PO-Revision-Date: 2015-04-05 15:01+0000\n"
"Last-Translator: Rob Cresswell <robert.cresswell@outlook.com>\n"
"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/horizon/language/en_GB/)\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:475
msgid "Other"
msgstr "Other"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Navigation Item"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Select a %s to browse."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "Password is not accepted"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Please log in to continue."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "You are not authorised to access %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "A %(resource)s with the name \"%(name)s\" already exists."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "Unauthorised: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "Unauthorised. Please try logging in again."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Incorrect format for IP address"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Invalid version for IP address"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Invalid subnet mask"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Submit"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Cancel"
#: middleware.py:103
msgid "Session timed out."
msgstr "Session timed out."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Filter"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "This action cannot be undone."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "You are not allowed to %(action)s: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Unable to %(action)s: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Delete"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Deleted"
#: tables/actions.py:948
msgid "Update"
msgstr "Update"
#: tables/actions.py:949
msgid "Updated"
msgstr "Updated"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, 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:990
msgid "No items to display."
msgstr "No items to display."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Actions"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "No match returned for the id \"%s\"."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Please select a row before taking that action."
#: tables/base.py:1570
msgid "N/A"
msgstr "N/A"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Logged in as: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Help"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Sign Out"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\n If you are not sure which authentication method to use, contact your administrator.\n "
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Log In"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "You do not have permission to access the resource:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Login as different user or go back to <a href=\"%(home_url)s\"> home page</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Sign In"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Connect"
#: templates/auth/login.html:4
msgid "Login"
msgstr "Login"
#: 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:63
msgid "Summary"
msgstr "Summary"
#: templates/horizon/common/_data_table.html:72
#, 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.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Prev"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Next&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "More Actions"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Add a row"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Limit Summary"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Instances"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Used <span> %(used)s </span> of <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPUs"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "Floating IPs"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Security Groups"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Volumes"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Volume Storage"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
msgstr "Save"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Displaying %(nav_items)s item"
msgstr[1] "Displaying %(nav_items)s items"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Displaying %(content_items)s item"
msgstr[1] "Displaying %(content_items)s items"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Usage Summary"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Select a period of time to query its usage:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n <label>From:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n <label>To:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "The date should be in YYYY-mm-dd format."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Active Instances:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "Active RAM:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "This Period's VCPU-Hours:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "This Period's GB-Hours:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "This Period's RAM-Hours:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Back"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Next"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "No Limit"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "Available"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d Byte"
msgstr[1] "%(size)d Bytes"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 Bytes"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] ""
msgstr[1] "Sell Puppies"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] ""
msgstr[1] "Sold Puppies"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Fake"
#: utils/filters.py:49
msgid "Never"
msgstr "Never"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Not a valid port number"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Not a valid IP protocol number"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "One colon allowed in port range"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "Port number must be integer"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "The string may only contain ASCII printable characters."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Processing..."
#: workflows/base.py:475
msgid "All available"
msgstr "All available"
#: workflows/base.py:476
msgid "Members"
msgstr "Members"
#: workflows/base.py:477
msgid "None available."
msgstr "None available."
#: workflows/base.py:478
msgid "No members."
msgstr "No members."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s completed successfully."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s did not complete."

View File

@ -0,0 +1,620 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Andi Chandler <andi@gowling.com>, 2014
# Rob Cresswell <robert.cresswell@outlook.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 16:36-0500\n"
"PO-Revision-Date: 2015-04-13 07:52+0000\n"
"Last-Translator: Rob Cresswell <robert.cresswell@outlook.com>\n"
"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/horizon/language/en_GB/)\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"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "The action cannot be performed. The contents of this row have errors or are missing information."
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "Detail Information"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "You can specify resource metadata by moving items from the left column to the right column. In the left columns there are metadata definitions from the Glance Metadata Catalog. Use the \"Other\" option to add metadata with the key of your choice."
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "Min"
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "Max"
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "Min length"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "Max length"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "Pattern mismatch"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "Integer required"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "Decimal required"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "Required"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "Duplicate keys are not allowed"
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "Filter"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "Available Metadata"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "Existing Metadata"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "Custom"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "No available metadata"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "No existing metadata"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "Submit"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "Cancel"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "Allocated"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "Available"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "Select one"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "Select an item from Available items below"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "No available items"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "Expand to see allocated items"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "Expand to see available items"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "Click to show or hide"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "Re-order items using drag and drop"
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "Click to see more details"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "Found %(found)s of %(total)s"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "Click here to expand the row and view the errors."
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "Back"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "Next"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "Finish"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "Connecting"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "Open"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "Closing"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "Closed"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "Status: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "Yes"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "No"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s bytes"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "Displaying %s item"
msgstr[1] "Displaying %s items"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "Unable to retrieve volumes."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "Unable to retrieve volume snapshots."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "Unable to retrieve user configuration."
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "Unable to retrieve admin configuration."
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "Unable to retrieve settings."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "Setting is not enabled: %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "Unable to retrieve image."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "Unable to retrieve images."
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "Unable to retrieve namespaces."
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "Unable to retrieve users"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "Unable to create the user."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "Unable to delete the users."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "Unable to retrieve the current user session."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "Unable to retrieve the user"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "Unable to edit the user."
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "Unable to delete the user."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "Unable to retrieve role"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "Unable to create the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "Unable to delete the roles."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "Unable to retrieve the role"
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "Unable to edit the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "Unable to delete the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "Unable to retrieve domains"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "Unable to create the domain."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "Unable to delete the domains."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "Unable to retrieve the domain"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "Unable to edit the domain."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "Unable to delete the domain."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "Unable to retrieve projects"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "Unable to create the project."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "Unable to delete the projects."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "Unable to retrieve the project"
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "Unable to edit the project."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "Unable to delete the project."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "Unable to grant the role."
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "Unable to fetch the service catalogue."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "Service type is not enabled: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "Cannot get service catalogue from keystone."
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "Unable to retrieve networks."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "Unable to create the network."
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "Unable to retrieve subnets."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "Unable to create the subnet."
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "Unable to retrieve ports."
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "Unable to retrieve keypairs."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "Unable to import the keypair."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "Unable to create the keypair."
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "Unable to retrieve availability zones."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "Unable to retrieve limits."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "Unable to create the server."
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "Unable to retrieve server."
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "Unable to retrieve extensions."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "Unable to retrieve flavours."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "Unable to retrieve flavour."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "Unable to retrieve flavour extra specs."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "Extension is not enabled: %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "Cannot get nova extension list."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "Policy check failed."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "Unable to retrieve security groups."
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "Loading"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "No data available."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "An error occurred. Please try again later."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "There was a problem communicating with the server, please try again."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "Could not read the file"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "Could not decrypt the password"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "No roles"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Roles"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "Danger: "
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "Warning: "
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "Notice: "
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "Success: "
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "Error: "
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "Working"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "There was an error submitting the form. Please try again."
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "None"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "Delete"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "STATUS"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "Interfaces"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "Delete Interface"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "Open Console"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "View Details"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "Delete Router"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "View Router Details"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "Add Interface"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "Terminate Instance"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "View Instance Details"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "No items to display."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "An error occurred while updating."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "You have selected %s. "
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "Confirm %s"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "Please confirm your selection. "
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "Not authorised to do this operation."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "Passwords do not match."

View File

@ -0,0 +1,514 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Alberto Molina Coballes <alb.molina@gmail.com>, 2015
# cametiope <aldohcasa@hotmail.com>, 2014
# Orizhial <contact@prunier.es>, 2015
# Marian Tort <marian.tort@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-07 01:16-0500\n"
"PO-Revision-Date: 2015-04-20 07:07+0000\n"
"Last-Translator: Alberto Molina Coballes <alb.molina@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/horizon/language/es/)\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:475
msgid "Other"
msgstr "Otro"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Ítem de navegación"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Seleccione una %s para navegar."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "La contraseña no se ha aceptado"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Inicie sesión para continuar."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "No está autorizado para acceder a %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "Un %(resource)s con el nombre \"%(name)s\" ya existe."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "No autorizado: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "No autorizado. Inicie sesión de nuevo."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Formato de dirección IP incorrecto"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Versión de dirección IP no válida"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Máscara de red no válida"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Enviar"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Cancelar "
#: middleware.py:103
msgid "Session timed out."
msgstr "La sesión ha expirado."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Filtrar"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "No se puede deshacer esta acción."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "No le está permitido %(action)s: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "No ha sido posible %(action)s: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Eliminar"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Eliminado"
#: tables/actions.py:948
msgid "Update"
msgstr "Actualizar"
#: tables/actions.py:949
msgid "Updated"
msgstr "Actualizada"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, 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:990
msgid "No items to display."
msgstr "No hay ítems que mostrar."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Acciones"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Ninguna coincidencia para el id \"%s\"."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Seleccione una fila antes de realizar la acción."
#: tables/base.py:1570
msgid "N/A"
msgstr "N/A"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Sesión iniciada como: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Ayuda"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Salir"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\nSi no está seguro del método de autenticación a utilizar, contacte con su administrador."
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Iniciar sesión"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "No tiene permisos para acceder al recurso:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Inicie sesión con otro usuario o volver a la <a href=\"%(home_url)s\"> página de inicio</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Iniciar sesión"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Conectar"
#: templates/auth/login.html:4
msgid "Login"
msgstr "Usuario"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Info:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Advertencia:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Correcto:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Error: "
#: templates/horizon/common/_data_table.html:63
msgid "Summary"
msgstr "Resumen"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Mostrando %(counter)s articulo"
msgstr[1] "Mostrando %(counter)s articulos"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Prev"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Siguiente&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "Más acciones"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Agregar una fila"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Resumen"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Instancias"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span> %(used)s </span> usado de <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPU"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "IPs flotantes"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Asignados <span> %(used)s </span> de <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Grupos de seguridad"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Volúmenes"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Almacenamiento de volúmenes"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
msgstr "Guardar"
#: 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 articulo"
msgstr[1] "Mostrando %(nav_items)s articulos"
#: 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 articulo"
msgstr[1] "Mostrando %(content_items)s articulos"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Resumen del uso"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Seleccione un periodo de tiempo para consultar su uso: "
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n <label>De:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n <label>A:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "La fecha debe estar en formato AAAA-MM-DD."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Instancias activas:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "RAM activa:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "Este periodo en horas VCPU:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "Este periodo en horas GB:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "Horas-RAM de este periodo:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Anterior"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Siguiente"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "Sin límite"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "Disponible"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d bite"
msgstr[1] "%(size)d bites"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 Bytes"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "Vender Mascota"
msgstr[1] "Vender Mascotas"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "Mascota Vendida"
msgstr[1] "Mascotas Vendidas"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Falso"
#: utils/filters.py:49
msgid "Never"
msgstr "Nunca"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Número de puerto no válido"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Número de protocolo IP no válido"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "Un punto permitido en el rango de puerto"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "El número de puerto debe ser un entero"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "La cadena sólo puede incluir caracteres imprimibles ASCII."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Procesando..."
#: workflows/base.py:475
msgid "All available"
msgstr "Todos los disponibles"
#: workflows/base.py:476
msgid "Members"
msgstr "Miembros"
#: workflows/base.py:477
msgid "None available."
msgstr "Ninguno disponible."
#: workflows/base.py:478
msgid "No members."
msgstr "Sin miembros."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s completado correctamente."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s no completado."

View File

@ -0,0 +1,621 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Alberto Molina Coballes <alb.molina@gmail.com>, 2015
# cametiope <aldohcasa@hotmail.com>, 2014
# Marian Tort <marian.tort@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
"Last-Translator: Alberto Molina Coballes <alb.molina@gmail.com>\n"
"Language-Team: Spanish (http://www.transifex.com/projects/p/horizon/language/es/)\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"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "Esta acción no se puede llevar a cabo. Esta columna contiene errores o carece de información."
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "Información detallada"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "Puede especificar los metadatos de los recursos moviendo los ítems de la columna de la izquierda a la de la derecha. En las columnas de la izquierda hay definiciones de metadatos del Glance Metadata Catalog. Utilice la opcion \"Otro\" para añadir metadatos con la clave que desee. "
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "Mín."
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "Máx."
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "Longitud mín."
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "Longitud máx."
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "Discrepancia en el patrón"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "Entero obligatorio"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "Decimal obligatorio"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "Obligatorio"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "Las claves duplicadas no están permitidas"
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "Filtrar"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "Metadatos disponibles"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "Metadatos existentes"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "Personalizar"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "Metadatos no disponibles"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "No hay metadatos existentes"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "Enviar"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "Cancelar "
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "Asignados"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "Disponible"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "Seleccione uno"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "Seleccione un ítem de los disponibles abajo"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "No hay ítems disponibles"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "Expandir para ver los ítems asociados"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "Expandir para ver los ítems disponibles"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "Haga click para mostrar u ocultar"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "Reordene los ítems arrastrando y soltando."
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "Haga click para ver más detalles"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "%(found)s de %(total)s encontrado"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "Haga click aquí para ver los errores."
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "Anterior"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "Siguiente"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "Finalizar"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "Conectando"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "Abierta"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "Cerrando"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "Cerrada"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "Estado: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "Sí"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "No"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s bytes"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "Mostrando %s articulo"
msgstr[1] "Mostrando %s articulos"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "No ha sido posible obtener volúmenes."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "No ha sido posible obtener las snapshots de volúmenes."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "No ha sido posible obtener la configuración de usuario."
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "No ha sido posible obtener la configuración de admin."
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "No ha sido posible obtener los ajustes."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "Ajuste no habilitado: %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "No ha sido posible obtener la imagen."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "No ha sido posible obtener las imágenes."
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "No ha sido posible obtener los espacios de nombres."
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "No ha sido posible obtener los usuarios"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "No ha sido posible crear el usuario."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "No ha sido posible borrar los usuarios."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "No ha sido posible obtener la sesión de usuario actual."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "No ha sido posible obtener el usuario"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "No ha sido posible editar el usuario."
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "No ha sido posible borrar el usuario."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "No ha sido posible obtener el rol"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "No ha sido posible crear el rol."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "No ha sido posible borrar los roles."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "No ha sido posible obtener el rol"
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "No ha sido posible editar el rol."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "No ha sido posible borrar el rol."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "No ha sido posible obtener los dominios"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "No ha sido posible crear el dominio."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "No ha sido posible borrar los dominios."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "No ha sido posible obtener el dominio"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "No ha sido posible editar el dominio."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "No ha sido posible borrar el dominio."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "No ha sido posible obtener los proyectos"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "No ha sido posible crear el proyecto."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "No ha sido posible borrar los proyectos."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "No ha sido posible obtener el proyecto"
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "No ha sido posible editar el proyecto."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "No ha sido posible borrar el proyecto."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "No ha sido posible asignar el rol."
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "No ha sido posible obtener el catálogo de servicios."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "Tipo de servicio no habilitado: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "No se puede obtener el catálogo de servicios desde keystone."
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "No ha sido posible obtener las redes."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "No ha sido posible crear la red."
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "No ha sido posible obtener las subredes."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "No ha sido posible crear la subred."
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "No ha sido posible obtener los puertos."
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "No ha sido posible obtener los pares de claves."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "No ha sido posible importar el par de claves."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "No ha sido posible crear el par de claves."
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "No ha sido posible obtener las zonas de disponibilidad."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "No ha sido posible obtener los límites."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "No ha sido posible crear el servidor."
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "No ha sido posible obtener el servidor."
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "No ha sido posible obtener las extensiones."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "No ha sido posible obtener los sabores."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "No ha sido posible obtener el sabor."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "No ha sido posible obtener las especificaciones extra del sabor."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "Extensión no habilitada: %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "No se puede obtener la lista de extensiones de nova."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "Ha fallado la comprobación de política."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "No ha sido posible obtener los grupos de seguridad."
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "Cargando"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "No hay datos disponibles."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "Ha ocurrido un error. Inténtelo de nuevo más tarde."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "Ha ocurrido un problema en la comunicación con el servidor, inténtelo de nuevo."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "No se ha podido leer el fichero"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "No se ha podido descifrar la contraseña"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "Sin roles"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Roles"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "Peligro:"
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "Advertencia:"
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "Aviso:"
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "Correcto:"
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "Error: "
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "Trabajando"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "Ha ocurrido un error al enviar el formulario. Inténtelo de nuevo."
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "Ninguno"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "Eliminar"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "ESTADO"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "Interfaces"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "Eliminar interfaz"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "Abrir consola"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "Ver detalles"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "Eliminar router"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "Ver detalles del router"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "Añadir interfaz"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "Terminar instancia"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "Ver detalles de la instancia"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "No hay ítems que mostrar."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "Ha ocurrido un error durante la actualización."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "Ha seleccionado %s."
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "Confirmar %s"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "Confirme su selección."
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "No tiene autorización para realizar esta operación."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "Las contraseñas no coinciden."

View File

@ -0,0 +1,514 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Bruno Cornec <bruno.cornec@hp.com>, 2015
# François Bureau, 2015
# Frédéric <frosmont@free.fr>, 2014
# Maxime COQUEREL <max.coquerel@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
"PO-Revision-Date: 2015-04-03 13:28+0000\n"
"Last-Translator: François Bureau\n"
"Language-Team: French (http://www.transifex.com/projects/p/horizon/language/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: base.py:475
msgid "Other"
msgstr "Autre"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Elément de navigation"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Sélectionner une %s à parcourir."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "Le mot de passe n'est pas accepté"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Merci de vous connecter pour continuer."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "Vous n'êtes pas autorisé à accéder à %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "Des %(resource)s avec le nom \"%(name)s\" existent déjà."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "%s : non autorisé"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "Accès non autorisé. Merci de vous reconnecter."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Format d'adresse IP incorrect"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Version d'adresse IP invalide"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Masque de sous-réseau invalide"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Envoyer"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Annuler"
#: middleware.py:103
msgid "Session timed out."
msgstr "La session a expiré."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Filtrer"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "Cette action ne peut pas être réalisée."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "Vous n'êtes pas autorisé à %(action)s : %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Impossible de %(action)s : %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s : %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Supprimer"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Supprimée"
#: tables/actions.py:948
msgid "Update"
msgstr "Mettre à jour"
#: tables/actions.py:949
msgid "Updated"
msgstr "Mis à jour"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "L'attribut %(attr)s n'existe pas sur %(obj)s."
#: tables/base.py:990
msgid "No items to display."
msgstr "Aucun élément à afficher."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Actions"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Aucun résultat retourné pour l'id \"%s\"."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Merci de sélectionner une ligne avant de faire cette action."
#: tables/base.py:1570
msgid "N/A"
msgstr "N/D"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Utilisateur connecté : %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Aide"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Se déconnecter"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\nSi vous n'êtes pas sûr de la méthode d'identification à utiliser, veuillez contacter votre administrateur"
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Se connecter"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "Vous n'avez pas la permission d'accéder à la ressource :"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Connectez-vous avec un autre nom d'utilisateur ou revenez à <a href=\"%(home_url)s\"> la page daccueil</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Se connecter"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Connection"
#: templates/auth/login.html:4
msgid "Login"
msgstr "Identifiant"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Information :"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Avertissement :"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Succès :"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Erreur :"
#: templates/horizon/common/_data_table.html:63
msgid "Summary"
msgstr "Résumé"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Affichage de %(counter)s item"
msgstr[1] "Affichage de %(counter)s items"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Aperçu"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Suivant&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "Plus d'Actions"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s : "
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Ajouter une ligne"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s : %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Synthèse des Quotas"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Instances"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span> %(used)s </span> utilisé(es) sur <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPUs"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "IP flottantes"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Alloué <span> %(used)s </span> de <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Groupes de sécurité"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Volumes"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Stockage de volumes"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
msgstr "Enregistrer"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Affichage de l'item %(nav_items)s"
msgstr[1] "Affichage des items %(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] "Affichage de l'item %(content_items)s"
msgstr[1] "Affichage des items %(content_items)s"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Résumé de l'Utilisation"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Sélectionnez une période de temps pour interroger son utilisation :"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n<label>Du :</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n<label>au :</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "La date doit être au format AAAA-mm-jj"
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Instances Actives :"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "RAM Active :"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "VCPU-Heures de cette Période :"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "GB-Heures de cette période :"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "Dans cette période RAM-Heures:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Retour"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Suivant"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "Pas de limite"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "disponible(s)"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d Octet"
msgstr[1] "%(size)d Octets"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s Ko"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s Mo"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s Go"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s To"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s Po"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 Octets"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "Chiot à vendre"
msgstr[1] "Chiots à vendre"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "Chiot vendu"
msgstr[1] "Chiots Vendus"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Faux"
#: utils/filters.py:49
msgid "Never"
msgstr "Jamais"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Numéro de port invalide"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Numéro de protocole IP invalide "
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "Un seul caractère deux-points autorisé dans une plage de ports"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "Le numéro de port doit être un nombre entier"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "La chaîne ne peut contenir que des caractères ASCII imprimables."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Traitement en cours..."
#: workflows/base.py:475
msgid "All available"
msgstr "Disponibles"
#: workflows/base.py:476
msgid "Members"
msgstr "Membres"
#: workflows/base.py:477
msgid "None available."
msgstr "Aucun disponible."
#: workflows/base.py:478
msgid "No members."
msgstr "Aucun membre."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s terminé avec succès."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s ne s'est pas terminé."

View File

@ -0,0 +1,627 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# alexandre ignjatovic <alexandre.ignjatovic@gmail.com>, 2015
# Bruno Cornec <bruno.cornec@hp.com>, 2015
# Corina Roe <croe@redhat.com>, 2015
# EVEILLARD <stephane.eveillard@gmail.com>, 2015
# François Bureau, 2015
# Frédéric <frosmont@free.fr>, 2014
# JF Taltavull <jftalta@gmail.com>, 2015
# Maxime COQUEREL <max.coquerel@gmail.com>, 2015
# Patte D <pattedeph@gmail.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
"PO-Revision-Date: 2015-04-23 12:36+0000\n"
"Last-Translator: JF Taltavull <jftalta@gmail.com>\n"
"Language-Team: French (http://www.transifex.com/projects/p/horizon/language/fr/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: fr\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "L'action n'a pas pu être effectuée. Le contenu de cette ligne contient des erreurs ou des informations manquantes."
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "Informations détaillées"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "Vous pouvez spécifier les ressources de métadonnées en déplaçant les éléments de la colonne de gauche vers la colonne de droite. Les colonnes de gauche contiennent des définitions de métadonnées du Catalogue de métadonnées Glance. Utiliser l'option \"Autre\" pour ajouter des métadonnées avec la clé de votre choix."
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "Min"
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "Max"
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "Longueur mini"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "Longueur max"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "Modèle ne correspondant pas"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "Entier requis"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "Nombre décimal requis"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "Requis"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "Les clés dupliquées ne sont pas autorisées"
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "Filtrer"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "Métadonnées disponibles"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "Métadonnées existantes"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "Personnaliser"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "Pas de métadonnées disponibles"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "Pas de métadonnées existantes"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "Envoyer"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "Annuler"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "Alloué"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "disponible(s)"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "Sélectionner un"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "Sélectionner un élément depuis les éléments disponibles ci-dessous"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "Pas d'élément disponible"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "Développer pour voir les éléments alloués"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "Développer pour voir les éléments disponibles"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "Cliquer pour montrer ou cacher"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "Ordonnancer les éléments à nouveau en utilisant l'opération glisser-déplacer"
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "Cliquer pour une vue plus détaillée"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "Résultat %(found)s parmi %(total)s"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "Cliquez ici pour étendre la ligne et voir les erreurs"
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "Retour"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "Suivant"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "Terminer"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "En cours de connexion"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "Ouvert"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "En cours de fermeture"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "Fermé"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "État : %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "Oui"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "Non"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s Go"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s Mo"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s To"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s Ko"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s octets"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "Affichage de %s élément"
msgstr[1] "Affichage de %s éléments"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "Impossible de récupérer les volumes."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "Impossible de récupérer les instantanés de volume."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "Impossible de récupérer la configuration utilisateur."
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "Impossible d'extraire la configuration admin."
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "Impossible de récupérer les paramètres."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "Le paramètre n'est pas activé : %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "Impossible de récupérer l'image."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "Impossible de récupérer les images."
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "Impossible de récupérer les espaces de nommage"
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "Impossible de récupérer les utilisateurs"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "Impossible de créer l'utilisateur."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "Impossible de supprimer les utilisateurs."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "Impossible de récupérer les informations de session de l'utilisateur courant."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "Impossible de récupérer l'utilisateur"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "Impossible d'éditer l'utilisateur"
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "Impossible de supprimer l'utilisateur."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "Impossible de récupérer le rôle."
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "Impossible de créer le rôle."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "Impossible de supprimer les rôles."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "Impossible de récupérer le rôle."
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "Impossible d'éditer le rôle."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "Impossible de supprimer le rôle."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "Impossible de récupérer les domaines."
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "Impossible de créer le domaine."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "Impossible de supprimer les domaines."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "Impossible de récupérer le domaine."
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "Impossible d'éditer le domaine."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "Impossible de supprimer le domaine."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "Impossible de récupérer les projets."
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "Impossible de créer le projet."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "Impossible de supprimer les projets."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "Impossible de récupérer le projet."
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "Impossible d'éditer le projet."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "Impossible de supprimer le projet."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "Impossible d'accorder le rôle"
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "Impossible d'extraire le catalogue de services."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "Type de service non activé: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "Impossible d'obtenir le catalogue de services Keystone"
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "Impossible de récupérer les réseaux."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "Impossible de créer le réseau."
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "Impossible de récupérer les sous-réseaux."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "Impossible de créer le sous-réseau."
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "Impossible de récupérer les ports."
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "Impossible de récupérer les paires de clés."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "Impossible d'importer la paire de clés."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "Impossible de créer la paire de clés"
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "Impossible de récupérer les zones de disponibilité."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "Impossible de récupérer les limites."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "Impossible de créer le serveur."
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "Impossible de récupérer le serveur."
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "Impossible d'obtenir les extensions."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "Impossible de récupérer les gabarits."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "Impossible de récupérer le gabarit."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "Impossible de récupérer les paramètres supplémentaires du gabarit."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "L'extension n'est pas activée : %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "Impossible d'obtenir la liste des extensions Nova."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "La vérification de la stratégie a échoué."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "Impossible de récupérer les groupes de sécurité."
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "Chargement..."
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "Pas de données disponibles."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "Une erreur s'est produite. Veuillez réessayer ultérieurement."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "Problème de communication avec le serveur, veuillez réessayer."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "Le fichier n'a pu être lu"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "Le mot de passe n'a pu être déchiffré"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "Aucun rôle"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Rôles"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "Danger :"
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "Avertissement :"
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "Notification :"
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "Succès :"
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "Erreur :"
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "Traitement en cours..."
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "Erreur lors de la soumission du formulaire. Veuillez réessayer. "
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "Aucun"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "Supprimer"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "STATUT"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "Interfaces"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "Supprimer l'Interface"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "Ouvrir une Console"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "Voir les détails"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "Supprimer un Routeur"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "Voir les Détails du Routeur"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "Ajouter une interface"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "Terminer l'Instance"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "Voir les Détails de l'Instance"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "Aucun élément à afficher."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "Une erreur s'est produite durant la mise à jour."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "Vous avez sélectionné %s."
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "Confirmez %s"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "Merci de confirmer votre sélection."
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "Vous n'êtes pas autorisé à effectuer cette opération."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "Les mots de passe ne correspondent pas."

View File

@ -0,0 +1,507 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Akihiro Motoki <amotoki@gmail.com>, 2014-2015
# myamamot <myamamot@redhat.com>, 2014-2015
# ykatabam <ykatabam@redhat.com>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
"PO-Revision-Date: 2015-04-06 23:21+0000\n"
"Last-Translator: myamamot <myamamot@redhat.com>\n"
"Language-Team: Japanese (http://www.transifex.com/projects/p/horizon/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:475
msgid "Other"
msgstr "その他"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "ナビゲーション項目"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "表示する %s を選択してください。"
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "パスワードを受け付けられません"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "続行するには、ログインしてください。"
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "%s へのアクセスが許可されていません。"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "名前が \"%(name)s\" の %(resource)s がすでに存在します。"
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "権限がありません: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "認証されていません。もう一度ログインしてください。"
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "不正な形式の IP アドレス"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "無効な IP アドレスのバージョン"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "無効なサブネットマスク"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "送信"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "取り消し"
#: middleware.py:103
msgid "Session timed out."
msgstr "セッションがタイムアウトしました。"
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "フィルター"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "この操作は取り消せません。"
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(data_type)sの%(action)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(data_type)sの%(action)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "%(action)s は許可されていません: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "%(action)s を実行できません: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "削除"
#: tables/actions.py:917
msgid "Deleted"
msgstr "削除"
#: tables/actions.py:948
msgid "Update"
msgstr "更新"
#: tables/actions.py:949
msgid "Updated"
msgstr "更新日時"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "%(obj)s に 属性 %(attr)s が存在しません。"
#: tables/base.py:990
msgid "No items to display."
msgstr "表示する項目がありません"
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "アクション"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "ID \"%s\" に一致するものがありません。"
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "このアクションを実行する前に、対象を選択してください。"
#: tables/base.py:1570
msgid "N/A"
msgstr "N/A"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "%(username)s としてログイン中"
#: templates/_header.html:7
msgid "Help"
msgstr "ヘルプ"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "ログアウト"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\n 使用する認証メソッドが不明な場合は、管理者に問い合わせてください。\n "
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "ログイン"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "リソースにアクセスする権限がありません:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "他のユーザーとしてログインするか、<a href=\"%(home_url)s\">ホームページ</a>に戻ってください。"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "ログイン"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "接続"
#: templates/auth/login.html:4
msgid "Login"
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:63
msgid "Summary"
msgstr "概要"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "%(counter)s件表示"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;前へ"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "次へ&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "その他のアクション"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "行の追加"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "利用可能リソース概要"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "インスタンス"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span>%(used)s</span> / <span>%(available)s</span> 使用中"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "仮想 CPU"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "メモリー"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "Floating IP"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span> %(used)s </span> / <span> %(available)s </span> 割り当て済み"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "セキュリティーグループ"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "ボリューム"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "ボリューム容量"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
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/_usage_summary.html:3
msgid "Usage Summary"
msgstr "使用状況の概要"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "使用状況を照会する期間を選択してください:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n%(start)s <label>から</label>"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n%(end)s <label>まで</label>"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "日付は YYYY-mm-dd 形式にする必要があります。"
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "稼働中のインスタンス:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "使用中のメモリー:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "指定期間中の仮想 CPU 時間:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "指定期間中の GB 時間:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "指定期間中のメモリー時間"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "戻る"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "次へ"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "制限なし"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "利用可能"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)dB"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 バイト"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "子犬を売る"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "子犬を売りました"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Fake"
#: utils/filters.py:49
msgid "Never"
msgstr "なし"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "有効なポート番号ではありません"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "有効な IP プロトコル番号ではありません"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "ポート範囲で使用できるコロンは 1 つだけです。"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "ポート番号は整数でなければなりません"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "文字列に含めることができるのは ASCII 印字可能文字のみです。"
#: workflows/base.py:71
msgid "Processing..."
msgstr "処理中..."
#: workflows/base.py:475
msgid "All available"
msgstr "利用可能な全項目"
#: workflows/base.py:476
msgid "Members"
msgstr "メンバー"
#: workflows/base.py:477
msgid "None available."
msgstr "利用可能な項目がありません。"
#: workflows/base.py:478
msgid "No members."
msgstr "メンバーがいません。"
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s が正常に完了しました。"
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s が完了しませんでした。"

View File

@ -0,0 +1,621 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Akihiro Motoki <amotoki@gmail.com>, 2014-2015
# myamamot <myamamot@redhat.com>, 2015
# Tom Fifield <tom@openstack.org>, 2015
# ykatabam <ykatabam@redhat.com>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
"Last-Translator: ykatabam <ykatabam@redhat.com>\n"
"Language-Team: Japanese (http://www.transifex.com/projects/p/horizon/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"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "操作を実行できません。この行の内容にエラーがあるか、情報が見つかりません。"
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "詳細情報"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "左の列から右の列にアイテムを移動して、リソースのメタデータを指定できます。左の列には、Glance のメタデータカタログに登録されているメタデータの定義が表示されています。任意のキーのメタデータを追加するには、\"Other\" オプションを使ってください。"
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "最小値"
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "最大値"
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "最小長"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "最大長"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "パターンが一致しません。"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "整数を指定する必要があります。"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "十進数を指定する必要があります。"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "必須"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "重複するキーは使用できません"
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "フィルター"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "利用可能なメタデータ"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "選択済みのメタデータ"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "カスタム"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "利用可能なメタデータはありません"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "選択済みのメタデータはありません"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "送信"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "取り消し"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "割り当て済み"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "利用可能"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "1 つ選択してください。"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "以下の利用可能なアイテムから 1 つ選択してください。"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "利用できるアイテムがありません。"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "割り当て済みのアイテムを表示するには展開してください。"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "利用可能なアイテムを表示するには展開してください。"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "クリックして表示/非表示を切り替えてください。"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "順番を並べ替えるには、アイテムをドラッグ&ドロップしてください。"
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "クリックすると詳しい情報が表示されます。"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "%(total)s 中 %(found)s 見つかりました。"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "行を展開してエラーを表示するには、ここをクリックしてください。"
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "戻る"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "次へ"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "完了"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "接続中"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "オープン"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "切断中"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "切断済み"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "ステータス: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "はい"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "いいえ"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s バイト"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "%s件表示"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "ボリューム情報を取得できません。"
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "ボリュームスナップショットの一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "ユーザーの設定を取得できません。"
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "管理者の設定を取得できません。"
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "設定を取得できません。"
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "設定 %(setting)s が有効になっていません。"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "イメージ情報を取得できません。"
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "イメージ一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "名前空間を取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "ユーザー一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "ユーザーを作成できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "ユーザーを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "現在のユーザーセッションを取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "ユーザーを取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "ユーザーを編集できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "ユーザーを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "ロールを取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "ロールを作成できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "ロールを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "ロールを取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "ロールを編集できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "ロールを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "ドメイン一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "ドメインを作成できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "ドメインを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "ドメインを取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "ドメインを編集できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "ドメインを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "プロジェクト一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "プロジェクトを作成できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "プロジェクトを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "プロジェクトを取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "プロジェクトを編集できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "プロジェクトを削除できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "ロールを許可できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "サービスカタログを取得できません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "サービスタイプ %(desiredType)s が有効になっていません。"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "keystone からサービスカタログを取得できません。"
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "ネットワーク一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "ネットワークを作成できません。"
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "サブネットを取得できません。"
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "サブネットを作成できません。"
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "ポートを取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "キーペアの一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "キーペアをインポートできません。"
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "キーペアを取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "アベイラビリティーゾーンを取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "上限を取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "サーバーを作成できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "サーバーを取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "拡張機能を取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "フレーバーの一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "フレーバーを取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "フレーバーの追加スペックを取得できません。"
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "拡張機能 %(extension)s が有効ではありません。"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "nova の拡張機能一覧を取得できません。"
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "ポリシーチェックに失敗しました。"
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "セキュリティーグループの一覧を取得できません。"
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "読み込み中"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "データがありません。"
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "エラーが発生しました。後からもう一度お試しください。"
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "サーバーとの通信中に問題がありました。再度お試しください。"
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "ファイルを読み取ることができませんでした。"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "パスワードの復号化できませんでした。"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "ロールがありません"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "ロール"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "危険:"
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "警告: "
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "注意:"
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "成功: "
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "エラー: "
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "反映中"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "フォームの送信中にエラーが発生しました。再度お試しください。"
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "なし"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "削除"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "ステータス"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "インターフェース"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "インタフェースの削除"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "コンソールを開く"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "詳細の表示"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "ルーターの削除"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "ルーターの詳細の表示"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "インターフェースの追加"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "インスタンスの終了"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "インスタンスの詳細の表示"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "表示する項目がありません"
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "更新中にエラーが発生しました。"
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "%s を選択しました。"
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "%sの確認"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "選択内容を確認してください。"
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "この操作を行う権限がありません。"
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "パスワードが一致しません。"

View File

@ -0,0 +1,508 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Ian Y. Choi <ianyrchoi@gmail.com>, 2015
# Ian Y. Choi <ianyrchoi@gmail.com>, 2015
# jaekwon.park <jaekwon.park@rockplace.co.kr>, 2014
# Sungjin Kang <potopro@gmail.com>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
"PO-Revision-Date: 2015-04-06 17:01+0000\n"
"Last-Translator: Sungjin Kang <potopro@gmail.com>\n"
"Language-Team: Korean (Korea) (http://www.transifex.com/projects/p/horizon/language/ko_KR/)\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:475
msgid "Other"
msgstr "기타"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "네비게이션 항목"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "브라우져에서 %s를 선택하십시오."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "허용되지 않는 비밀번호입니다."
#: decorators.py:53
msgid "Please log in to continue."
msgstr "계속 진행하려면 로그인하십시오."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "%s에 접근 권한이 없습니다. "
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "이름 \"%(name)s\"과 %(resource)s이 이미 존재합니다."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "권한이 없습니다: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "권한이 없습니다. 다시 로그인 해주십시오."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "IP 주소 형식이 잘못 되었습니다."
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "IP 주소 버전이 잘못되었습니다."
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "서브넷 마스크가 잘못되었습니다."
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "제출"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "취소"
#: middleware.py:103
msgid "Session timed out."
msgstr "세션 타임 아웃."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "필터"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "이 작업은 취소할 수 없습니다."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(data_type)s %(action)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(data_type)s %(action)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr " %(action)s 이(가) 허용되지 않습니다: %(objs)s "
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "%(action)s을(를) 할 수 없습니다.: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "삭제"
#: tables/actions.py:917
msgid "Deleted"
msgstr "삭제 완료"
#: tables/actions.py:948
msgid "Update"
msgstr "업데이트"
#: tables/actions.py:949
msgid "Updated"
msgstr "업데이트 완료"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "%(obj)s 에 %(attr)s 속성이 없습니다."
#: tables/base.py:990
msgid "No items to display."
msgstr "표시할 항목이 없습니다."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "작업"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "ID \"%s\" 에 일치되는 항목이 없습니다."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "해당 작업을 실행하기 전에 열(row)을 선택하십시오."
#: tables/base.py:1570
msgid "N/A"
msgstr "N/A"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "로그인됨: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "도움말"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "로그아웃"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\n당신이 인증 방법을 모르는경우, 관리자에게 문의하십시오."
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "로그인"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "당신은 리소스에 접근할 권한이 없습니다.:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "다른 사용자로 로그인하거나 <a href=\"%(home_url)s\">홈페이지</a>로 되돌아가기"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "등록"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "연결"
#: templates/auth/login.html:4
msgid "Login"
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:63
msgid "Summary"
msgstr "요약"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "%(counter)s 항목 표시"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;이전"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "다음&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "기타 작업"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "열 추가"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "간략한 요약"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "인스턴스"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span> %(available)s </span> 중에서 <span> %(used)s </span> 사용"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPUs"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "유동 IP"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "<span> %(available)s </span> 중에서 <span> %(used)s </span> 할당"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "보안 그룹"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "볼륨"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "볼륨 스토리지"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
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/_usage_summary.html:3
msgid "Usage Summary"
msgstr "사용량 요약"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "사용량을 조회할 기간을 선택하세요:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n<label>시작:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n<label>끝:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "날짜는 YYYY-mm-dd 형식이어야 합니다."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "동작 중인 인스턴스:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "사용 중인 RAM:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "이 기간의 VCPU 사용 시간:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "이 기간의 GB-Hours 사용 시간:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "이 기간의 RAM 사용 시간:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "뒤로"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "다음"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "제한 없음"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "사용 가능"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d 바이트"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 Bytes"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "강아지 판매"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "강아지 판매함"
#: test/tests/views.py:59
msgid "Fake"
msgstr "페이크"
#: utils/filters.py:49
msgid "Never"
msgstr "없음"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "유효하지 않은 포트 번호"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "유효하지 않은 IP 프로토콜 번호"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "포트 범위에서 콜론은 하나만 사용할 수 있습니다."
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "포트 번호는 정수이어야 합니다."
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "문자열은 ASCII 인쇄 문자를 포함할 수 있습니다."
#: workflows/base.py:71
msgid "Processing..."
msgstr "작업 중..."
#: workflows/base.py:475
msgid "All available"
msgstr "모두 사용가능"
#: workflows/base.py:476
msgid "Members"
msgstr "구성원"
#: workflows/base.py:477
msgid "None available."
msgstr "사용할 수 있는 것이 없음."
#: workflows/base.py:478
msgid "No members."
msgstr "구성원이 없습니다."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s가 성공적으로 완료되었습니다."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s가 완료되지 않았습니다."

View File

@ -0,0 +1,621 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Ian Y. Choi <ianyrchoi@gmail.com>, 2015
# jaekwon.park <jaekwon.park@rockplace.co.kr>, 2014
# jaekwon.park <jaekwon.park@rockplace.co.kr>, 2014
# Sungjin Kang <potopro@gmail.com>, 2014-2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-11 11:47-0500\n"
"PO-Revision-Date: 2015-04-12 02:21+0000\n"
"Last-Translator: Sungjin Kang <potopro@gmail.com>\n"
"Language-Team: Korean (Korea) (http://www.transifex.com/projects/p/horizon/language/ko_KR/)\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"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "작업을 진행할 수 없습니다. 이 행의 내용에 오류가 있거나 정보가 누락되어 있습니다."
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "상세 정보"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "왼쪽 열에서 오른쪽 열로 항목을 이동하여 리소스 메타데이터를 지정할 수 있습니다. 왼쪽 열에서 Glance 메타데이터 카탈로그에서 메타데이터를 정의할 수 있습니다. \"Other\" 옵션을 사용하여 선택한 키를 메타데이터에 추가할 수 있습니다."
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "최소"
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "최대"
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "최소 길이"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "최대 길이"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "패턴 불일치"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "정수 필요"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "실수 필요"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "필요"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "중복 키를 허용하지 않습니다."
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "필터"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "사용 가능한 메타데이터"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "기존 메타데이터"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "사용자 지정"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "사용 가능한 메타데이터가 없습니다."
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "기존 메타데이터가 없습니다."
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "제출"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "취소"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "할당됨"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "사용 가능"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "하나 선택"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "아래의 사용 가능한 항목에서 항목을 선택"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "사용 가능한 항목 없음"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "할당된 항목을 볼 수 있도록 확장"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "사용 가능한 항목을 볼 수 있도록 확장"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "클릭하여 보기 또는 감추기"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "다시 주문할 아이템은 드래그 앤 드롭으로 가져옵니다."
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "클릭하여 세부 사항 보기"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "%(total)s 의 %(found)s 찾기"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "클릭하여 열을 확장하고 에러를 확인할 수 있습니다."
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "뒤로"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "다음"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "완료"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "연결중"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "열림"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "닫는중"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "닫음"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "상태: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "예"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "아니오"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s 바이트"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "%s 항목 표시"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "볼륨을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "볼륨 스냅샷을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "사용자 구성을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "관리자 구성을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "설정을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "설정을 활성하지 못했습니다: %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "이미지를 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "이미지를 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "네임스페이스를 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "사용자를 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "사용자를 만들지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "사용자를 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "현재 사용자 섹션을 찾을 수 없습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "사용자를 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "사용자를 수정하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "사용자를 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "Role을 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "Role을 만들지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "Role을 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "Role을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "Role을 수정하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "Role을 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "도메인을 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "도메인을 만들지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "도메인을 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "도메인을 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "도메인을 수정하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "도메인을 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "프로젝트를 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "프로젝트를 만들지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "프로젝트를 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "프로젝트를 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "프로젝트를 수정하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "프로젝트를 삭제하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "Role을 부여하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "서비스 카탈로그를 가져올 수 없습니다."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "서비스 타입을 활성화하지 못 했습니다: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "Keystone으로부터 서비스 목록을 가져올 수 없습니다."
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "네트워크를 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "네트워크를 생성하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "서브넷을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "서브넷을 생성하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "포트를 검색하지 못했습니다"
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "Keypair를 찾을 수 없습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "키페어를 가져올 수 없습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "Keypair를 생성하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "가용성 존에 대한 정보를 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "제한을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "서버를 생성하지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "서버를 찾지 못했습니다"
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "확장을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "Flavor를 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "Flavor를 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "Flavor 확장 스팩을 찾지 못했습니다."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "확장을 활성화하지 못 했습니다: %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "Nova 확장 목록을 가져올 수 없습니다."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "정책 확인 실패함."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "시큐리티 그룹을 찾지 못했습니다."
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "불러오는 중"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "데이터가 없습니다."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "오류가 발생했습니다. 나중에 다시 시도하십시오."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "서버와의 통신에 문제가 발생하였으니, 다시 시도하세요."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "파일을 읽을 수 없습니다"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "암호를 해독할 수 없습니다."
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "Role 없음"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Roles"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "위험:"
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "경고:"
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "주의:"
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "완료:"
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "오류:"
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "작동 중"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "양식을 제출하는 동안 오류가 발생하였습니다. 다시 시도하세요."
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "None"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "삭제"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "STATUS"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "인터페이스"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "인터페이스 삭제"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "콘솔 열기"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "세부 정보 보기"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "라우터 삭제"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "라우터 정보 자세히 보기"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "인터페이스 추가"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "인스턴스 종료"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "인스턴스 정보 자세히 보기"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "표시할 항목이 없습니다."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "업데이트 도중 오류가 발생하였습니다."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "%s를 선택하였습니다. "
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "%s를 확인하세요."
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "선택 사항을 확인하십시오."
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "이 작업에 대한 권한이 없습니다."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "비밀번호가 일치하지 않습니다."

View File

@ -0,0 +1,517 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Łukasz Jernaś <deejay1@srem.org>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-07 01:16-0500\n"
"PO-Revision-Date: 2015-04-21 12:54+0000\n"
"Last-Translator: Łukasz Jernaś <deejay1@srem.org>\n"
"Language-Team: Polish (Poland) (http://www.transifex.com/projects/p/horizon/language/pl_PL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl_PL\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: base.py:475
msgid "Other"
msgstr "Inne"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Pozycja nawigacyjna"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Wybierz %s by przeglądać."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "Hasło nie zostało zaakceptowane"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Należy się zalogować, aby można było kontynuować."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "Brak uprawnień dostępu do %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "%(resource)s o nazwie \"%(name)s\" już istnieje."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "Nieupoważniono: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "Brak uprawnień. Proszę spróbować się wylogować i zalogować ponownie."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Błędny format adresu IP"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Błędna wersja adresu IP"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Błędna maska podsieci"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Wyślij"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Anuluj"
#: middleware.py:103
msgid "Session timed out."
msgstr "Sesja wygasła."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Filtr"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "Tej czynności nie można cofnąć."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "Brak uprawnień do %(action)s: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Nie można %(action)s: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Usuń"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Usunięto"
#: tables/actions.py:948
msgid "Update"
msgstr "Aktualizuj"
#: tables/actions.py:949
msgid "Updated"
msgstr "Zaktualizowany"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "Atrybut %(attr)s nie istnieje dla %(obj)s."
#: tables/base.py:990
msgid "No items to display."
msgstr "Brak pozycji do wyświetlenia."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Czynności"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Nie znaleziono wyników dla identyfikatora „%s”."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Należy wybrać wiersz przed wykonaniem tej czynności."
#: tables/base.py:1570
msgid "N/A"
msgstr "n.d."
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Zalogowano jako: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Pomoc"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Wyloguj"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\nJeśli nie wiadomo, którego sposobu uwierzytelniania użyć, należy skontaktować się z administratorem."
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Zaloguj"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "Brak uprawnień do zasobu:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Należy zalogować się jako inny użytkownik lub wrócić do <a href=\"%(home_url)s\"> strony domowej</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Wpisz się"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Połącz"
#: templates/auth/login.html:4
msgid "Login"
msgstr "Login"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Informacja:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Ostrzeżenie:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Powodzenie:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Błąd:"
#: templates/horizon/common/_data_table.html:63
msgid "Summary"
msgstr "Podsumowanie"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Wyświetlanie %(counter)s pozycji"
msgstr[1] "Wyświetlanie %(counter)s pozycji"
msgstr[2] "Wyświetlanie %(counter)s pozycji"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Poprzednie"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Następne&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "Pozostałe akcje"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Dodaj wiersz"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Podsumowanie limitów"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Instancje"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Wykorzystano <span> %(used)s </span> z <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPU"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "Pływające adresy IP"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Przydzielono <span> %(used)s </span> z <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Grupy zabezpieczeń"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Wolumeny"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Przechowywanie danych"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
msgstr "Zapisz"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Wyświetlanie %(nav_items)s pozycję"
msgstr[1] "Wyświetlanie %(nav_items)s pozycji"
msgstr[2] "Wyświetlanie %(nav_items)s pozycji"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Wyświetlanie %(content_items)s pozycji"
msgstr[1] "Wyświetlanie %(content_items)s pozycji"
msgstr[2] "Wyświetlanie %(content_items)s pozycji"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Podsumowanie wykorzystania"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Wybierz okres, za który należy wyświetlić wykorzystanie:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n<label>Od:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n <label>Do:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "Data powinna być podana w formacie YYYY-mm-dd."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Aktywne instancje:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "Aktywna pamięć RAM:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "VCPU-godziny w tym okresie:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "GB-godziny w tym okresie:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "RAM-godziny w tym okresie:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Wstecz"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Następny"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "Bez ograniczeń"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "Dostępne"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d bajt"
msgstr[1] "%(size)d bajty"
msgstr[2] "%(size)d bajtów"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 bajtów"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "Sprzedaj pieska"
msgstr[1] "Sprzedaj pieski"
msgstr[2] "Sprzedaj pieski"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "Sprzedano pieska"
msgstr[1] "Sprzedano pieski"
msgstr[2] "Sprzedano pieski"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Fałszywe"
#: utils/filters.py:49
msgid "Never"
msgstr "Nigdy"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Błędny numer portu"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Błędny numer protokołu IP"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "Tylko jeden dwukropek jest dozwolony w zakresie portów"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "Numer portu musi być liczbą całkowitą"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "Ciąg może zawierać wyłącznie drukowalne znaki ASCII."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Przetwarzanie…"
#: workflows/base.py:475
msgid "All available"
msgstr "Całość dostępna"
#: workflows/base.py:476
msgid "Members"
msgstr "Członkowie"
#: workflows/base.py:477
msgid "None available."
msgstr "Całość wykorzystana"
#: workflows/base.py:478
msgid "No members."
msgstr "Brak członków."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s zakończona pomyślnie."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s nie została zakończona."

View File

@ -0,0 +1,620 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Łukasz Jernaś <deejay1@srem.org>, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
"Last-Translator: Łukasz Jernaś <deejay1@srem.org>\n"
"Language-Team: Polish (Poland) (http://www.transifex.com/projects/p/horizon/language/pl_PL/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pl_PL\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "Nie można wykonać czynności. Zawartość tego wiersza zawiera błędy lub brakujące dane."
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "Szczegółowe informacje"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "Można podać metadane zasobów przesuwając pozycje z lewej kolumny do prawej. W lewej kolumnie znajdują się definicje metadanych z Katalogu Metadanych Glance. Można podać metadane z własnym kluczem, po wybraniu opcji „Inne”."
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "Min."
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "Maks."
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "Min. długość"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "Maks. długość"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "Szablon się nie zgadza"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "Wymagana liczba całkowita"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "Wymagana liczba dziesiętna"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "Wymagane"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "Ponowne użycie nazwy klucza jest niedozwolone."
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "Filtr"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "Dostępne metadane"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "Istniejące metadane"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "Własne"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "Metadane niedostępne"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "Brak metadanych"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "Wyślij"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "Anuluj"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "Przydzielono"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "Dostępne"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "Wybierz jedno"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "Należy wybrać pozycję z listy dostępnych pozycji poniżej"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "Brak dostępnych pozycji"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "Rozszerz, aby zobaczyć przydzielone pozycje"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "Rozszerz, aby zobaczyć dostepne pozycje"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "Kliknij by pokazać lub ukryć"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "Zmień kolejność pozycji poprzez przesuwanie i upuszczanie"
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "Kliknij, aby wyświetlić szczegóły"
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "Znaleziono %(found)s z %(total)s"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "Kliknij tutaj, aby rozszerzyć wiersz i wyświetlić błędy."
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "Wstecz"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "Następny"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "Zakończ"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "Łączenie"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "Otwórz"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "Zamykanie"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "Zamknięto"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "Stan: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "Tak"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "Nie"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s bajtów"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "Wyświetlanie %s pozycji"
msgstr[1] "Wyświetlanie %s pozycji"
msgstr[2] "Wyświetlanie %s pozycji"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "Nie można pobrać wolumenów."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "Nie można pobrać migawek wolumenów."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "Nie można pobrać ustawień użytkownika."
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "Nie można pobrać ustawień administratora."
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "Nie można pobrać ustawień."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "Ustawienie jest wyłączone: %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "Nie można pobrać obrazu."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "Nie można pobrać obrazów"
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "Nie można pobrać przestrzeni nazw."
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "Nie można pobrać użytkowników"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "Nie można utworzyć użytkownika."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "Nie mozna usunąć użytkowników."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "Nie można pobrać bieżącej sesji użytkownika."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "Nie można pobrać użytkownika"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "Nie można edytować użytkownika."
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "Nie można usunąć użytkownika."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "Nie można pobrać roli"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "Nie można utworzyć roli."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "Nie mozna usunąć ról."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "Nie można pobrać roli"
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "Nie można edytować roli."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "Nie można usunąć roli."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "Nie można pobrać domen"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "Nie można utworzyć domeny."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "Nie można usunąć domen."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "Nie można pobrać domeny"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "Nie można edytować domeny."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "Nie można usunąć domeny."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "Nie można pobrać projektów"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "Nie można utworzyć projektu."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "Nie można usunąć projektów."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "Nie można pobrać projektu."
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "Nie można edytować projektu."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "Nie można usunąć projektu."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "Nie można nadać roli."
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "Nie można pobrać katalogu usług."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "Typ usługi nie jest włączony: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "Nie można pobrać katalogu usług z keystone."
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "Nie można pobrać sieci."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "Nie można utworzyć sieci."
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "Nie można pobrać podsieci."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "Nie można utworzyć podsieci."
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "Nie można pobrać portów."
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "Nie można pobrać par kluczy."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "Nie można zaimportować pary kluczy."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "Nie można utworzyć pary kluczy."
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "Nie można pobrać stref dostępności."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "Nie można pobrać ograniczeń."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "Nie można utworzyć serwera."
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "Nie można pobrać serwera."
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "Nie można pobrać rozszerzeń."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "Nie można pobrać odmian."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "Nie można pobrać odmiany."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "Nie można pobrać dodatkowych danych odmiany."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "Rozszerzenie nie jest włączone: %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "Nie można pobrać listy rozszerzeń novy."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "Sprawdzenie polityki się nie powiodło."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "Nie można pobrać grup zabezpieczeń."
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "Wczytywanie"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "Brak dostępnych danych."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "Wystąpił błąd. Proszę spróbować później."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "Wystąpił problem w komunikacji z serwerem, proszę spróbować ponownie."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "Nie można odczytać pliku"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "Nie można odszyfrować hasła"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "Brak ról"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Role"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "Zagrożenie:"
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "Ostrzeżenie:"
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "Powiadomienie:"
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "Powodzenie:"
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "Błąd:"
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "Praca"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "Wystąpił błąd podczas wysyłania formularza. Proszę spróbować ponownie."
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "Brak"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "Usuń"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "STATUS"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "Interfejsy"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "Usuń interfejs"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "Otwórz konsolę"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "Wyświetl szczegóły"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "Usuń router"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "Wyświetl szczegóły routera"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "Dodaj interfejs"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "Zniszcz instancję"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "Wyświetl szczegóły instancji"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "Brak pozycji do wyświetlenia."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "Wystąpił błąd podczas aktualizacji."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "Wybrano %s."
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "Potwierdź %s"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "Należy potwierdzić wybór."
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "Nie upoważniono do przeprowadzenie tej czynności."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "Hasła nie pasują"

View File

@ -0,0 +1,514 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Fernando F. Rodrigues <rodrigues_fernando@hotmail.com>, 2015
# Gabriel Wainer, 2015
# Remulo Carvalho <remulo@gmail.com>, 2015
# Rodrigo Felix de Almeida <rodrigofelixdealmeida@gmail.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
"PO-Revision-Date: 2015-04-07 15:52+0000\n"
"Last-Translator: Remulo Carvalho <remulo@gmail.com>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/horizon/language/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: base.py:475
msgid "Other"
msgstr "Outro"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Item de Navegação"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Selecione um %s para navegar."
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "Senha não foi aceita"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Por favor faça login para continuar."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "Você não está autorizado a acessar %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "Um %(resource)s com o nome \"%(name)s\" já existe."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "Não autorizado: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "Não autorizado. Por favor tente efetuar login novamente."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Formato incorreto para o endereço IP"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Versão inválida para o endereço IP"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Máscara de sub-rede inválida"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Enviar"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Cancelar"
#: middleware.py:103
msgid "Session timed out."
msgstr "Tempo limite da sessão esgotou."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Filtro"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "Esta ação não pode ser desfeita."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "Você não possui permissão para %(action)s: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Não foi possível %(action)s: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Excluir"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Excluído"
#: tables/actions.py:948
msgid "Update"
msgstr "Atualizar"
#: tables/actions.py:949
msgid "Updated"
msgstr "Atualizado"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "O atributo %(attr)s não existe em %(obj)s."
#: tables/base.py:990
msgid "No items to display."
msgstr "Sem itens para exibir."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Ações"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Não foi encontrada correspondência para o ID \"%s\"."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Por favor selecione uma linha antes de realizar esta ação."
#: tables/base.py:1570
msgid "N/A"
msgstr "N/A"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Logado como: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Ajuda"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Sair"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\nSe você não tem certeza do método de autenticação a ser utilizado, entre em contato com o seu administrador."
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Entrar"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "Você não tem permissão para acessar o recurso:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Faça login com um usuário diferente ou volte para a <a href=\"%(home_url)s\"> página inicial</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Entrar"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Conectado"
#: templates/auth/login.html:4
msgid "Login"
msgstr "Logar"
#: templates/horizon/_messages.html:7
msgid "Info: "
msgstr "Informação:"
#: templates/horizon/_messages.html:13
msgid "Warning: "
msgstr "Alerta:"
#: templates/horizon/_messages.html:19
msgid "Success: "
msgstr "Sucesso:"
#: templates/horizon/_messages.html:25
msgid "Error: "
msgstr "Erro:"
#: templates/horizon/common/_data_table.html:63
msgid "Summary"
msgstr "Resumo"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Exibindo %(counter)s item"
msgstr[1] "Exibindo %(counter)s itens"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Prev"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Next&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "Mais Ações"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Adicionar uma linha"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Resumo de Limites"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Instâncias"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Utilizado <span> %(used)s </span> de <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "vCPUs"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "IPs Flutuantes"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Alocado <span> %(used)s </span> de <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Grupos de Segurança"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Volumes"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Armazenamento de volume"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
msgstr "Salvar"
#: templates/horizon/common/_resource_browser.html:10
#, python-format
msgid "Displaying %(nav_items)s item"
msgid_plural "Displaying %(nav_items)s items"
msgstr[0] "Exibindo %(nav_items)s item"
msgstr[1] "Exibindo %(nav_items)s itens"
#: templates/horizon/common/_resource_browser.html:11
#, python-format
msgid "Displaying %(content_items)s item"
msgid_plural "Displaying %(content_items)s items"
msgstr[0] "Exibindo %(content_items)s item"
msgstr[1] "Exibindo %(content_items)s itens"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Resumo de Utilização"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Selecione um período de tempo para consultar seu uso:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n <label>De:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n <label>Para:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "A data deve estar no formato YYYY-mm-dd."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Instâncias ativas:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "RAM ativa:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "VCPU-Horas desse Período:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "GB-Horas desse Período:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "Quantidade de RAM-Horas deste período:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Voltar"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Próximo"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "Sem Limite"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "Disponível"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d Byte"
msgstr[1] "%(size)d Bytes"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s KB"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s MB"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s GB"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s TB"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s PB"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 Bytes"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] "Vender Puppy"
msgstr[1] "Vender Puppies"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] "Puppy Vendido"
msgstr[1] "Puppies Vendidos"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Falso"
#: utils/filters.py:49
msgid "Never"
msgstr "Nunca"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Não é um número de porta válido"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Não é um número de protocolo IP válido"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "Uma pontução de dois pontos permitida no intervalo de portas"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "Número de porta deve ser inteiro"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "A string somente deve conter caracteres ASCII imprimíveis."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Processando..."
#: workflows/base.py:475
msgid "All available"
msgstr "Tudo disponível"
#: workflows/base.py:476
msgid "Members"
msgstr "Membros"
#: workflows/base.py:477
msgid "None available."
msgstr "Nenhum disponível."
#: workflows/base.py:478
msgid "No members."
msgstr "Sem membros."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s concluído com sucesso."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s não completou."

View File

@ -0,0 +1,622 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Fernando Pimenta <fernando.c.pimenta@gmail.com>, 2015
# Gabriel Wainer, 2015
# maurosr <maurosmrodrigues@gmail.com>, 2015
# Rodrigo Felix de Almeida <rodrigofelixdealmeida@gmail.com>, 2014
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 16:36-0500\n"
"PO-Revision-Date: 2015-04-14 01:40+0000\n"
"Last-Translator: Fernando Pimenta <fernando.c.pimenta@gmail.com>\n"
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/horizon/language/pt_BR/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: pt_BR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: static/angular/action-list/button-tooltip.js:15
msgid ""
"The action cannot be performed. The contents of this row have errors or are "
"missing information."
msgstr "A ação não pôde ser executada. O conteúdo desta linha possui erros ou informações indisponíveis"
#: static/angular/metadata-display/metadata-display.js:33
msgid "Detail Information"
msgstr "Informações delhadas"
#: static/angular/metadata-tree/metadata-tree.js:35
msgid ""
"You can specify resource metadata by moving items from the left column to "
"the right column. In the left columns there are metadata definitions from "
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
"the key of your choice."
msgstr "Você pode especificar metadados de recurso movendo itens da coluna da esquerda para a coluna da direita. Nas colunas da esquerda existem definições de metadados do catálogo de metadados do Glance. Utilize a opção \"Outros\" para adicionar metadados com a chave de sua escolha."
#: static/angular/metadata-tree/metadata-tree.js:36
msgid "Min"
msgstr "Mín. "
#: static/angular/metadata-tree/metadata-tree.js:37
msgid "Max"
msgstr "Máx."
#: static/angular/metadata-tree/metadata-tree.js:38
msgid "Min length"
msgstr "Tamanho mínimo"
#: static/angular/metadata-tree/metadata-tree.js:39
msgid "Max length"
msgstr "Tamanho máximo"
#: static/angular/metadata-tree/metadata-tree.js:40
msgid "Pattern mismatch"
msgstr "Incompatibilidade de padrão"
#: static/angular/metadata-tree/metadata-tree.js:41
msgid "Integer required"
msgstr "Inteiro requerido"
#: static/angular/metadata-tree/metadata-tree.js:42
msgid "Decimal required"
msgstr "Decimal requerido"
#: static/angular/metadata-tree/metadata-tree.js:43
msgid "Required"
msgstr "Requerido"
#: static/angular/metadata-tree/metadata-tree.js:44
msgid "Duplicate keys are not allowed"
msgstr "Chaves duplicadas não são permitidas"
#: static/angular/metadata-tree/metadata-tree.js:45
#: static/angular/table/basic-table.js:6
#: static/horizon/js/horizon.forms.js:184
msgid "Filter"
msgstr "Filtro"
#: static/angular/metadata-tree/metadata-tree.js:46
msgid "Available Metadata"
msgstr "Metadados disponível"
#: static/angular/metadata-tree/metadata-tree.js:47
msgid "Existing Metadata"
msgstr "Metadados existente"
#: static/angular/metadata-tree/metadata-tree.js:48
msgid "Custom"
msgstr "Customizado"
#: static/angular/metadata-tree/metadata-tree.js:49
msgid "No available metadata"
msgstr "Metadados não disponível"
#: static/angular/metadata-tree/metadata-tree.js:50
msgid "No existing metadata"
msgstr "Metadados não existente"
#: static/angular/modal/modal.js:83
msgid "Submit"
msgstr "Enviar"
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
#: static/horizon/js/horizon.modals.js:33
msgid "Cancel"
msgstr "Cancelar"
#: static/angular/transfer-table/transfer-table.js:39
msgid "Allocated"
msgstr "Alocado"
#: static/angular/transfer-table/transfer-table.js:40
msgid "Available"
msgstr "Disponível"
#: static/angular/transfer-table/transfer-table.js:41
msgid "Select one"
msgstr "Selecione"
#: static/angular/transfer-table/transfer-table.js:42
msgid "Select an item from Available items below"
msgstr "Selecione um item dentre os disponíveis abaixo"
#: static/angular/transfer-table/transfer-table.js:43
msgid "No available items"
msgstr "Itens indisponíveis"
#: static/angular/transfer-table/transfer-table.js:44
msgid "Expand to see allocated items"
msgstr "Expanda para ver os itens alocados"
#: static/angular/transfer-table/transfer-table.js:45
msgid "Expand to see available items"
msgstr "Expanda para ver os itens disponíveis"
#: static/angular/transfer-table/transfer-table.js:46
msgid "Click to show or hide"
msgstr "Clique para exibir ou esconder"
#: static/angular/transfer-table/transfer-table.js:47
msgid "Re-order items using drag and drop"
msgstr "reordene os itens clicando em um deles e arrastando-o"
#: static/angular/transfer-table/transfer-table.js:48
msgid "Click to see more details"
msgstr "Clique para ver mais detalhes."
#: static/angular/transfer-table/transfer-table.js:100
msgid "Found %(found)s of %(total)s"
msgstr "Encontrado %(found)s de %(total)s"
#: static/angular/transfer-table/transfer-table.js:166
msgid "Click here to expand the row and view the errors."
msgstr "Clique aqui para expandir a linha e ver os erros."
#: static/angular/wizard/wizard.js:12
msgid "Back"
msgstr "Voltar"
#: static/angular/wizard/wizard.js:13
msgid "Next"
msgstr "Próximo"
#: static/angular/wizard/wizard.js:14
msgid "Finish"
msgstr "Encerrar"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Connecting"
msgstr "Conectando"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Open"
msgstr "Abrir"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closing"
msgstr "Encerramento"
#: static/horizon/js/angular/directives/serialConsole.js:23
msgid "Closed"
msgstr "Encerrado"
#: static/horizon/js/angular/directives/serialConsole.js:85
#, c-format
msgid "Status: %s"
msgstr "Status: %s"
#: static/horizon/js/angular/filters/filters.js:37
msgid "Yes"
msgstr "Sim"
#: static/horizon/js/angular/filters/filters.js:37
msgid "No"
msgstr "Não"
#: static/horizon/js/angular/filters/filters.js:53
#: static/horizon/js/angular/filters/filters.js:140
#, c-format
msgid "%s GB"
msgstr "%s GB"
#: static/horizon/js/angular/filters/filters.js:70
#: static/horizon/js/angular/filters/filters.js:142
#, c-format
msgid "%s MB"
msgstr "%s MB"
#: static/horizon/js/angular/filters/filters.js:138
#, c-format
msgid "%s TB"
msgstr "%s TB"
#: static/horizon/js/angular/filters/filters.js:144
#, c-format
msgid "%s KB"
msgstr "%s KB"
#: static/horizon/js/angular/filters/filters.js:146
#, c-format
msgid "%s bytes"
msgstr "%s bytes"
#: static/horizon/js/angular/filters/filters.js:163
#: static/horizon/js/horizon.tables.js:393
#, c-format
msgid "Displaying %s item"
msgid_plural "Displaying %s items"
msgstr[0] "Exibindo %s item"
msgstr[1] "Exibindo %s itens"
#: static/horizon/js/angular/services/hz.api.cinder.js:47
msgid "Unable to retrieve volumes."
msgstr "Não foi possível obter a lista de volumes."
#: static/horizon/js/angular/services/hz.api.cinder.js:74
msgid "Unable to retrieve volume snapshots."
msgstr "Não é possível recuperar snapshots de volume."
#: static/horizon/js/angular/services/hz.api.config.js:43
msgid "Unable to retrieve user configuration."
msgstr "Não foi possível recuperar as configurações de usuário"
#: static/horizon/js/angular/services/hz.api.config.js:62
msgid "Unable to retrieve admin configuration."
msgstr "Não foi possível recuperar as configurações de administrador"
#: static/horizon/js/angular/services/hz.api.config.js:105
msgid "Unable to retrieve settings."
msgstr "Não é possível recuperar as configurações."
#: static/horizon/js/angular/services/hz.api.config.js:289
msgid "Setting is not enabled: %(setting)s"
msgstr "Configuração não está habilitada: %(setting)s"
#: static/horizon/js/angular/services/hz.api.glance.js:38
msgid "Unable to retrieve image."
msgstr "Não foi possível recuperar a imagem."
#: static/horizon/js/angular/services/hz.api.glance.js:81
msgid "Unable to retrieve images."
msgstr "Não foi possível obter as imagems"
#: static/horizon/js/angular/services/hz.api.glance.js:144
msgid "Unable to retrieve namespaces."
msgstr "Não é possível recuperar namespaces."
#: static/horizon/js/angular/services/hz.api.keystone.js:24
msgid "Unable to retrieve users"
msgstr "Não foi possível obter os usuários"
#: static/horizon/js/angular/services/hz.api.keystone.js:31
msgid "Unable to create the user."
msgstr "Não foi possível criar o usuário."
#: static/horizon/js/angular/services/hz.api.keystone.js:38
msgid "Unable to delete the users."
msgstr "Não foi possível excluir os usuários."
#: static/horizon/js/angular/services/hz.api.keystone.js:73
msgid "Unable to retrieve the current user session."
msgstr "Não é possível recuperar a sessão atual do usuário."
#: static/horizon/js/angular/services/hz.api.keystone.js:80
msgid "Unable to retrieve the user"
msgstr "Não foi possível obter o usuário"
#: static/horizon/js/angular/services/hz.api.keystone.js:88
msgid "Unable to edit the user."
msgstr "Não foi possível editar o usuário."
#: static/horizon/js/angular/services/hz.api.keystone.js:95
msgid "Unable to delete the user."
msgstr "Não foi possível excluir o usuário."
#: static/horizon/js/angular/services/hz.api.keystone.js:103
msgid "Unable to retrieve role"
msgstr "Não foi possível obter o papel"
#: static/horizon/js/angular/services/hz.api.keystone.js:110
msgid "Unable to create the role."
msgstr "Não foi possível criar o papel."
#: static/horizon/js/angular/services/hz.api.keystone.js:117
msgid "Unable to delete the roles."
msgstr "Não foi possível excluir os papéis."
#: static/horizon/js/angular/services/hz.api.keystone.js:124
msgid "Unable to retrieve the role"
msgstr "Não foi possível obter o papel"
#: static/horizon/js/angular/services/hz.api.keystone.js:132
msgid "Unable to edit the role."
msgstr "Não foi possível editar o papel."
#: static/horizon/js/angular/services/hz.api.keystone.js:139
msgid "Unable to delete the role."
msgstr "Não foi possível excluir o papel."
#: static/horizon/js/angular/services/hz.api.keystone.js:147
msgid "Unable to retrieve domains"
msgstr "Não foi possível obter os domínios"
#: static/horizon/js/angular/services/hz.api.keystone.js:154
msgid "Unable to create the domain."
msgstr "Não foi possível criar o domínio."
#: static/horizon/js/angular/services/hz.api.keystone.js:161
msgid "Unable to delete the domains."
msgstr "Não foi possível excluir os domínios."
#: static/horizon/js/angular/services/hz.api.keystone.js:168
msgid "Unable to retrieve the domain"
msgstr "Não foi possível obter o domínio"
#: static/horizon/js/angular/services/hz.api.keystone.js:176
msgid "Unable to edit the domain."
msgstr "Não foi possível editar o domínio."
#: static/horizon/js/angular/services/hz.api.keystone.js:183
msgid "Unable to delete the domain."
msgstr "Não foi possível excluir o domínio."
#: static/horizon/js/angular/services/hz.api.keystone.js:192
msgid "Unable to retrieve projects"
msgstr "Não foi possível obter os projetos"
#: static/horizon/js/angular/services/hz.api.keystone.js:199
msgid "Unable to create the project."
msgstr "Não foi possível criar o projeto."
#: static/horizon/js/angular/services/hz.api.keystone.js:206
msgid "Unable to delete the projects."
msgstr "Não foi possível excluir os projetos."
#: static/horizon/js/angular/services/hz.api.keystone.js:213
msgid "Unable to retrieve the project"
msgstr "Não foi possível obter o projeto"
#: static/horizon/js/angular/services/hz.api.keystone.js:221
msgid "Unable to edit the project."
msgstr "Não foi possível editar o projeto."
#: static/horizon/js/angular/services/hz.api.keystone.js:228
msgid "Unable to delete the project."
msgstr "Não foi possível excluir o projeto."
#: static/horizon/js/angular/services/hz.api.keystone.js:236
msgid "Unable to grant the role."
msgstr "Não foi possível permitir o papel."
#: static/horizon/js/angular/services/hz.api.keystone.js:250
msgid "Unable to fetch the service catalog."
msgstr "Não é possível obter o catálogo de serviços."
#: static/horizon/js/angular/services/hz.api.keystone.js:385
msgid "Service type is not enabled: %(desiredType)s"
msgstr "Tipo de serviço não está habilitado: %(desiredType)s"
#: static/horizon/js/angular/services/hz.api.keystone.js:392
msgid "Cannot get service catalog from keystone."
msgstr "Não é possível obter o catálogo de serviços a partir do keystone."
#: static/horizon/js/angular/services/hz.api.neutron.js:39
msgid "Unable to retrieve networks."
msgstr "Não é possível recuperar redes."
#: static/horizon/js/angular/services/hz.api.neutron.js:88
msgid "Unable to create the network."
msgstr "Não foi possível criar a rede"
#: static/horizon/js/angular/services/hz.api.neutron.js:108
msgid "Unable to retrieve subnets."
msgstr "Não é possível recuperar a subredes."
#: static/horizon/js/angular/services/hz.api.neutron.js:172
msgid "Unable to create the subnet."
msgstr "Não foi possível criar sub-rede"
#: static/horizon/js/angular/services/hz.api.neutron.js:192
msgid "Unable to retrieve ports."
msgstr "Não foi possível obter lista de portas."
#: static/horizon/js/angular/services/hz.api.nova.js:40
msgid "Unable to retrieve keypairs."
msgstr "Não é possível recuperar pares de chaves."
#: static/horizon/js/angular/services/hz.api.nova.js:62
msgid "Unable to import the keypair."
msgstr "Não é possível importar o par de chaves."
#: static/horizon/js/angular/services/hz.api.nova.js:64
msgid "Unable to create the keypair."
msgstr "Não foi possível criar o par de chaves."
#: static/horizon/js/angular/services/hz.api.nova.js:83
msgid "Unable to retrieve availability zones."
msgstr "Não é possível recuperar todas zonas de disponibilidade."
#: static/horizon/js/angular/services/hz.api.nova.js:121
msgid "Unable to retrieve limits."
msgstr "Não foi possível obter informações de limite."
#: static/horizon/js/angular/services/hz.api.nova.js:148
msgid "Unable to create the server."
msgstr "Não foi possível criar a instância"
#: static/horizon/js/angular/services/hz.api.nova.js:162
msgid "Unable to retrieve server."
msgstr "Não é possível recuperar servidor."
#: static/horizon/js/angular/services/hz.api.nova.js:192
msgid "Unable to retrieve extensions."
msgstr "Não é possível recuperar extensões."
#: static/horizon/js/angular/services/hz.api.nova.js:237
msgid "Unable to retrieve flavors."
msgstr "Não foi possível recuperar flavors."
#: static/horizon/js/angular/services/hz.api.nova.js:255
msgid "Unable to retrieve flavor."
msgstr "Não é possível recuperar flavor."
#: static/horizon/js/angular/services/hz.api.nova.js:269
msgid "Unable to retrieve flavor extra specs."
msgstr "Não é possível recuperar especificações extras do flavor."
#: static/horizon/js/angular/services/hz.api.nova.js:311
msgid "Extension is not enabled: %(extension)s"
msgstr "Extensão não está habilitada: %(extension)s"
#: static/horizon/js/angular/services/hz.api.nova.js:318
msgid "Cannot get nova extension list."
msgstr "Não é possível obter lista de extensão do nova."
#: static/horizon/js/angular/services/hz.api.policy.js:65
msgid "Policy check failed."
msgstr "Verificação de política falhou."
#: static/horizon/js/angular/services/hz.api.security-group.js:64
msgid "Unable to retrieve security groups."
msgstr "Não é possível recuperar grupos de segurança"
#: static/horizon/js/horizon.accordion_nav.js:78
#: static/horizon/js/horizon.modals.js:315
#: static/horizon/js/horizon.tabs.js:21
msgid "Loading"
msgstr "Carregando"
#: static/horizon/js/horizon.d3linechart.js:394
#: static/horizon/js/horizon.d3linechart.js:404
msgid "No data available."
msgstr "Não há dados disponíveis."
#: static/horizon/js/horizon.d3linechart.js:410
#: static/horizon/js/horizon.modals.js:334
#: static/horizon/js/horizon.tables_inline_edit.js:94
#: static/horizon/js/horizon.tables_inline_edit.js:157
msgid "An error occurred. Please try again later."
msgstr "Um erro ocorreu. Por favor tente novamente mais tarde."
#: static/horizon/js/horizon.firewalls.js:32
#: static/horizon/js/horizon.instances.js:31
msgid "There was a problem communicating with the server, please try again."
msgstr "Houve um problema ao comunicar-se com o servidor, por favor tente novamente."
#: static/horizon/js/horizon.instances.js:273
msgid "Could not read the file"
msgstr "Não foi possivel ler o arquivo"
#: static/horizon/js/horizon.instances.js:279
#: static/horizon/js/horizon.instances.js:308
msgid "Could not decrypt the password"
msgstr "Não foi possível descriptografar a senha"
#: static/horizon/js/horizon.membership.js:190
msgid "No roles"
msgstr "Sem papéis"
#: static/horizon/js/horizon.membership.js:222
msgid "Roles"
msgstr "Papéis"
#: static/horizon/js/horizon.messages.js:9
msgid "Danger: "
msgstr "Perigo:"
#: static/horizon/js/horizon.messages.js:10
msgid "Warning: "
msgstr "Alerta:"
#: static/horizon/js/horizon.messages.js:11
msgid "Notice: "
msgstr "Notificação:"
#: static/horizon/js/horizon.messages.js:12
msgid "Success: "
msgstr "Sucesso:"
#: static/horizon/js/horizon.messages.js:13
msgid "Error: "
msgstr "Erro:"
#: static/horizon/js/horizon.modals.js:229
#: static/horizon/js/horizon.tables.js:218
msgid "Working"
msgstr "Trabalhando"
#: static/horizon/js/horizon.modals.js:263
msgid "There was an error submitting the form. Please try again."
msgstr "Houve um erro ao enviar o formulário. Por favor tente novamente."
#: static/horizon/js/horizon.networktopology.js:530
#: static/horizon/js/horizon.networktopology.js:536
msgid "None"
msgstr "Nenhum"
#: static/horizon/js/horizon.networktopology.js:549
msgid "Delete"
msgstr "Excluir"
#: static/horizon/js/horizon.networktopology.js:552
msgid "STATUS"
msgstr "STATUS"
#: static/horizon/js/horizon.networktopology.js:553
msgid "ID"
msgstr "ID"
#: static/horizon/js/horizon.networktopology.js:554
msgid "Interfaces"
msgstr "Interfaces"
#: static/horizon/js/horizon.networktopology.js:555
msgid "Delete Interface"
msgstr "Excluir Interface"
#: static/horizon/js/horizon.networktopology.js:556
msgid "Open Console"
msgstr "Abrir Console"
#: static/horizon/js/horizon.networktopology.js:557
msgid "View Details"
msgstr "Ver Detalhes"
#: static/horizon/js/horizon.networktopology.js:560
msgid "Delete Router"
msgstr "Excluir Roteador"
#: static/horizon/js/horizon.networktopology.js:561
msgid "View Router Details"
msgstr "Ver Detalhes do Roteador"
#: static/horizon/js/horizon.networktopology.js:564
msgid "Add Interface"
msgstr "Adicionar Interface"
#: static/horizon/js/horizon.networktopology.js:570
msgid "Terminate Instance"
msgstr "Terminar Instância"
#: static/horizon/js/horizon.networktopology.js:571
msgid "View Instance Details"
msgstr "Ver Detalhes da Instância"
#: static/horizon/js/horizon.tables.js:39
#: static/horizon/js/horizon.tables.js:406
msgid "No items to display."
msgstr "Sem itens para exibir."
#: static/horizon/js/horizon.tables.js:52
#: static/horizon/js/horizon.tables.js:120
msgid "An error occurred while updating."
msgstr "Um erro ocorreu ao atualizar."
#: static/horizon/js/horizon.tables.js:201
#, c-format
msgid "You have selected %s. "
msgstr "Você selecionou %s."
#: static/horizon/js/horizon.tables.js:203
#, c-format
msgid "Confirm %s"
msgstr "Confirma %s"
#: static/horizon/js/horizon.tables.js:204
msgid "Please confirm your selection. "
msgstr "Por favor confirme a sua seleção."
#: static/horizon/js/horizon.tables_inline_edit.js:88
#: static/horizon/js/horizon.tables_inline_edit.js:151
msgid "Not authorized to do this operation."
msgstr "Não autorizado para realizar esta operação."
#: static/horizon/js/horizon.users.js:18
msgid "Passwords do not match."
msgstr "As senhas não conferem."

View File

@ -0,0 +1,518 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
#
# Translators:
# Ilya Alekseyev <ilyaalekseyev@acm.org>, 2015
# Nikita Burtsev, 2015
msgid ""
msgstr ""
"Project-Id-Version: Horizon\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-04-12 16:36-0500\n"
"PO-Revision-Date: 2015-04-13 12:42+0000\n"
"Last-Translator: Ilya Alekseyev <ilyaalekseyev@acm.org>\n"
"Language-Team: Russian (http://www.transifex.com/projects/p/horizon/language/ru/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
#: base.py:475
msgid "Other"
msgstr "Другое"
#: browsers/base.py:88
msgid "Navigation Item"
msgstr "Элемент навигации"
#: browsers/views.py:41
#, python-format
msgid "Select a %s to browse."
msgstr "Выберите %s для просмотра"
#: conf/default.py:41
msgid "Password is not accepted"
msgstr "Пароль не принят"
#: decorators.py:53
msgid "Please log in to continue."
msgstr "Войдите в систему для продолжения."
#: decorators.py:85
#, python-format
msgid "You are not authorized to access %s"
msgstr "Нет права доступа к %s"
#: exceptions.py:163
#, python-format
msgid "A %(resource)s with the name \"%(name)s\" already exists."
msgstr "%(resource)s с именем\"%(name)s\" уже существует."
#: exceptions.py:235
#, python-format
msgid "Unauthorized: %s"
msgstr "Не авторизован: %s"
#: exceptions.py:238
msgid "Unauthorized. Please try logging in again."
msgstr "Вы не авторизованы. Попробуйте войти в систему еще раз."
#: forms/fields.py:64
msgid "Incorrect format for IP address"
msgstr "Неправильный формат IP-адреса"
#: forms/fields.py:65
msgid "Invalid version for IP address"
msgstr "Неправильная версия IP-адреса"
#: forms/fields.py:66
msgid "Invalid subnet mask"
msgstr "Неправильная маска подсети"
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
msgid "Submit"
msgstr "Отправить"
#: forms/views.py:133
#: templates/horizon/common/_modal_form_update_metadata.html:25
#: templates/horizon/common/_workflow.html:49
msgid "Cancel"
msgstr "Отмена"
#: middleware.py:103
msgid "Session timed out."
msgstr "Время сеанса истекло."
#: tables/actions.py:460
#: templates/horizon/common/_data_table_table_actions.html:21
#: templates/horizon/common/_data_table_table_actions.html:33
#: templates/horizon/common/_workflow_step_update_members.html:14
#: templates/horizon/common/_workflow_step_update_members.html:23
msgid "Filter"
msgstr "Фильтр"
#: tables/actions.py:645
msgid "This action cannot be undone."
msgstr "Это действие не может быть отменено."
#: tables/actions.py:767
#, python-format
msgctxt "past"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:769
#, python-format
msgctxt "present"
msgid "%(action)s %(data_type)s"
msgstr "%(action)s %(data_type)s"
#: tables/actions.py:832
#, python-format
msgid "You are not allowed to %(action)s: %(objs)s"
msgstr "Вам не разрешено выполнение: %(action)s: %(objs)s"
#: tables/actions.py:839
#, python-format
msgid "Unable to %(action)s: %(objs)s"
msgstr "Невозможно выполнить %(action)s: %(objs)s"
#: tables/actions.py:845
#, python-format
msgid "%(action)s: %(objs)s"
msgstr "%(action)s: %(objs)s"
#: tables/actions.py:915
msgid "Delete"
msgstr "Удалить"
#: tables/actions.py:917
msgid "Deleted"
msgstr "Удалено"
#: tables/actions.py:948
msgid "Update"
msgstr "Обновить"
#: tables/actions.py:949
msgid "Updated"
msgstr "Обновлено"
#: tables/base.py:305
msgid "-"
msgstr "-"
#: tables/base.py:361
#, python-format
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
msgstr "Атрибут %(attr)s не существует для %(obj)s."
#: tables/base.py:990
msgid "No items to display."
msgstr "Нет элементов для отображения."
#: tables/base.py:1099
#: templates/horizon/common/_data_table_table_actions.html:47
msgid "Actions"
msgstr "Действия"
#: tables/base.py:1329
#, python-format
msgid "No match returned for the id \"%s\"."
msgstr "Нет совпадений для id \"%s\"."
#: tables/base.py:1486
msgid "Please select a row before taking that action."
msgstr "Выберите строку перед выполнением этого действия."
#: tables/base.py:1570
msgid "N/A"
msgstr "Н/Д"
#: templates/_header.html:5
#, python-format
msgid "Logged in as: %(username)s"
msgstr "Пользователь: %(username)s"
#: templates/_header.html:7
msgid "Help"
msgstr "Помощь"
#: templates/_header.html:9
msgid "Sign Out"
msgstr "Выход"
#: templates/auth/_description.html:9
msgid ""
"\n"
" If you are not sure which authentication method to use, contact your administrator.\n"
" "
msgstr "\n Если вы не уверены какой метод аутентификации выбрать, свяжитесь с вашим системным администратором.\n "
#: templates/auth/_login.html:5
msgid "Log In"
msgstr "Войти"
#: templates/auth/_login.html:27
msgid "You do not have permission to access the resource:"
msgstr "Вы не имеете права на доступ к ресурсу:"
#: templates/auth/_login.html:29
#, python-format
msgid ""
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
"page</a>"
msgstr "Войдите под другим пользователем или вернитесь на<a href=\"%(home_url)s\"> домашнюю страницу</a>"
#: templates/auth/_login.html:45
msgid "Sign In"
msgstr "Вход"
#: templates/auth/_login.html:46
msgid "Connect"
msgstr "Подключиться"
#: templates/auth/login.html:4
msgid "Login"
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:63
msgid "Summary"
msgstr "Итого"
#: templates/horizon/common/_data_table.html:72
#, python-format
msgid "Displaying %(counter)s item"
msgid_plural "Displaying %(counter)s items"
msgstr[0] "Показан %(counter)s элемент"
msgstr[1] "Показано %(counter)s элементов"
msgstr[2] "Показано %(counter)s элементов"
#: templates/horizon/common/_data_table.html:77
msgid "&laquo;&nbsp;Prev"
msgstr "&laquo;&nbsp;Предыдущее"
#: templates/horizon/common/_data_table.html:80
msgid "Next&nbsp;&raquo;"
msgstr "Следующее&nbsp;&raquo;"
#: templates/horizon/common/_data_table_table_actions.html:45
msgid "More Actions"
msgstr "Еще действия"
#: templates/horizon/common/_domain_page_header.html:6
#, python-format
msgid "%(context_name)s:"
msgstr "%(context_name)s:"
#: templates/horizon/common/_formset_table.html:35
msgid "Add a row"
msgstr "Добавить строку"
#: templates/horizon/common/_formset_table_row.html:15
#, python-format
msgid "%(name)s: %(error)s"
msgstr "%(name)s: %(error)s"
#: templates/horizon/common/_limit_summary.html:4
msgid "Limit Summary"
msgstr "Сводка лимитов"
#: templates/horizon/common/_limit_summary.html:7
msgid "Instances"
msgstr "Машины"
#: templates/horizon/common/_limit_summary.html:8
#: templates/horizon/common/_limit_summary.html:15
#: templates/horizon/common/_limit_summary.html:22
#: templates/horizon/common/_limit_summary.html:36
#: templates/horizon/common/_limit_summary.html:43
#: templates/horizon/common/_limit_summary.html:50
#, python-format
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Использовано <span> %(used)s </span> из <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:14
msgid "VCPUs"
msgstr "VCPU"
#: templates/horizon/common/_limit_summary.html:21
msgid "RAM"
msgstr "RAM"
#: templates/horizon/common/_limit_summary.html:28
msgid "Floating IPs"
msgstr "Назначаемые IP"
#: templates/horizon/common/_limit_summary.html:29
#, python-format
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
msgstr "Выделено <span> %(used)s </span> из <span> %(available)s </span>"
#: templates/horizon/common/_limit_summary.html:35
msgid "Security Groups"
msgstr "Группы безопасности"
#: templates/horizon/common/_limit_summary.html:42
msgid "Volumes"
msgstr "Диски"
#: templates/horizon/common/_limit_summary.html:49
msgid "Volume Storage"
msgstr "Хранилище"
#: templates/horizon/common/_modal_form_update_metadata.html:24
#: workflows/base.py:594
msgid "Save"
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 элемент"
msgstr[1] "Показано %(nav_items)s элементов"
msgstr[2] "Показано %(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 элемент"
msgstr[1] "Показано %(content_items)s элементов"
msgstr[2] "Показано %(content_items)s элементов"
#: templates/horizon/common/_usage_summary.html:3
msgid "Usage Summary"
msgstr "Сводка использования"
#: templates/horizon/common/_usage_summary.html:7
msgid "Select a period of time to query its usage:"
msgstr "Выберите временной интервал для запроса использования:"
#: templates/horizon/common/_usage_summary.html:9
#, python-format
msgid ""
"\n"
" <label>From:</label> %(start)s"
msgstr "\n <label>От:</label> %(start)s"
#: templates/horizon/common/_usage_summary.html:13
#, python-format
msgid ""
"\n"
" <label>To:</label>%(end)s"
msgstr "\n <label>До:</label>%(end)s"
#: templates/horizon/common/_usage_summary.html:17
msgid "The date should be in YYYY-mm-dd format."
msgstr "Дата должна иметь формат YYYY-mm-dd."
#: templates/horizon/common/_usage_summary.html:20
msgid "Active Instances:"
msgstr "Активные машины:"
#: templates/horizon/common/_usage_summary.html:21
msgid "Active RAM:"
msgstr "Используемая RAM:"
#: templates/horizon/common/_usage_summary.html:22
msgid "This Period's VCPU-Hours:"
msgstr "vCPU-часов за период:"
#: templates/horizon/common/_usage_summary.html:23
msgid "This Period's GB-Hours:"
msgstr "ГБ-часов за период:"
#: templates/horizon/common/_usage_summary.html:24
msgid "This Period's RAM-Hours:"
msgstr "RAM-часов за период:"
#: templates/horizon/common/_workflow.html:40
msgid "Back"
msgstr "Назад"
#: templates/horizon/common/_workflow.html:43
msgid "Next"
msgstr "Следующий"
#: templatetags/branding.py:34
msgid "Horizon"
msgstr "Horizon"
#: templatetags/horizon.py:137 templatetags/horizon.py:148
msgid "No Limit"
msgstr "Без ограничений"
#: templatetags/horizon.py:140 templatetags/horizon.py:142
msgid "Available"
msgstr "Доступно"
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
#, python-format
msgid "%(size)d Byte"
msgid_plural "%(size)d Bytes"
msgstr[0] "%(size)d байт"
msgstr[1] "%(size)d байт"
msgstr[2] "%(size)d байт"
#: templatetags/sizeformat.py:59
#, python-format
msgid "%s KB"
msgstr "%s КБ"
#: templatetags/sizeformat.py:61
#, python-format
msgid "%s MB"
msgstr "%s МБ"
#: templatetags/sizeformat.py:63
#, python-format
msgid "%s GB"
msgstr "%s ГБ"
#: templatetags/sizeformat.py:65
#, python-format
msgid "%s TB"
msgstr "%s ТБ"
#: templatetags/sizeformat.py:66
#, python-format
msgid "%s PB"
msgstr "%s ПБ"
#: templatetags/sizeformat.py:74
msgid "0 Bytes"
msgstr "0 байт"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:31
msgid "Sell Puppy"
msgid_plural "Sell Puppies"
msgstr[0] ""
msgstr[1] ""
msgstr[2] "Sell Puppies"
#. Translators: test code, don't really have to translate
#: test/test_dashboards/dogs/puppies/tables.py:40
msgid "Sold Puppy"
msgid_plural "Sold Puppies"
msgstr[0] ""
msgstr[1] ""
msgstr[2] "Sold Puppies"
#: test/tests/views.py:59
msgid "Fake"
msgstr "Fake"
#: utils/filters.py:49
msgid "Never"
msgstr "Никогда"
#: utils/validators.py:26 utils/validators.py:50
msgid "Not a valid port number"
msgstr "Недопустимый номер порта"
#: utils/validators.py:31
msgid "Not a valid IP protocol number"
msgstr "Недопустимый номер IP-протокола"
#: utils/validators.py:45
msgid "One colon allowed in port range"
msgstr "В списке портов допустима одна запятая"
#: utils/validators.py:52
msgid "Port number must be integer"
msgstr "Номер порта должен быть целым числом"
#: utils/validators.py:59
msgid "The string may only contain ASCII printable characters."
msgstr "Строка может содержать только печатные ASCII символы."
#: workflows/base.py:71
msgid "Processing..."
msgstr "Обработка…"
#: workflows/base.py:475
msgid "All available"
msgstr "Все доступные"
#: workflows/base.py:476
msgid "Members"
msgstr "Участники"
#: workflows/base.py:477
msgid "None available."
msgstr "Нет доступных."
#: workflows/base.py:478
msgid "No members."
msgstr "Нет участников."
#: workflows/base.py:595
#, python-format
msgid "%s completed successfully."
msgstr "%s успешно завершено."
#: workflows/base.py:596
#, python-format
msgid "%s did not complete."
msgstr "%s не завершено."

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