Unifies Horizon conf.

Centralizes all of Horizon's configuration options so that
they're all uniformly accesible from a single place and always
guaranteed to exist.

Implements blueprint unify-config.

Change-Id: I3279b7ccd58302fcff4f0d273f89f282a285c442
This commit is contained in:
Gabriel Hurley 2012-11-17 16:54:48 -08:00
parent 0e328995ec
commit 0065e6642d
12 changed files with 107 additions and 51 deletions

View File

@ -39,26 +39,13 @@ from django.utils.module_loading import module_has_submodule
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from horizon import loaders from horizon import loaders
from horizon import conf
from horizon.decorators import require_auth, require_perms, _current_component from horizon.decorators import require_auth, require_perms, _current_component
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
# Default configuration dictionary. Do not mutate directly. Use copy.copy().
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,
'exceptions': {'unauthorized': [],
'not_found': [],
'recoverable': []}
}
def _decorate_urlconf(urlpatterns, decorator, *args, **kwargs): def _decorate_urlconf(urlpatterns, decorator, *args, **kwargs):
for pattern in urlpatterns: for pattern in urlpatterns:
if getattr(pattern, 'callback', None): if getattr(pattern, 'callback', None):
@ -591,9 +578,7 @@ class Site(Registry, HorizonComponent):
@property @property
def _conf(self): def _conf(self):
conf = copy.copy(HORIZON_CONFIG) return conf.HORIZON_CONFIG
conf.update(getattr(settings, 'HORIZON_CONFIG', {}))
return conf
@property @property
def dashboards(self): def dashboards(self):
@ -633,11 +618,11 @@ class Site(Registry, HorizonComponent):
""" Returns an ordered tuple of :class:`~horizon.Dashboard` modules. """ Returns an ordered tuple of :class:`~horizon.Dashboard` modules.
Orders dashboards according to the ``"dashboards"`` key in Orders dashboards according to the ``"dashboards"`` key in
``settings.HORIZON_CONFIG`` or else returns all registered dashboards ``HORIZON_CONFIG`` or else returns all registered dashboards
in alphabetical order. in alphabetical order.
Any remaining :class:`~horizon.Dashboard` classes registered with Any remaining :class:`~horizon.Dashboard` classes registered with
Horizon but not listed in ``settings.HORIZON_CONFIG['dashboards']`` Horizon but not listed in ``HORIZON_CONFIG['dashboards']``
will be appended to the end of the list alphabetically. will be appended to the end of the list alphabetically.
""" """
if self.dashboards: if self.dashboards:
@ -660,7 +645,7 @@ class Site(Registry, HorizonComponent):
def get_default_dashboard(self): def get_default_dashboard(self):
""" Returns the default :class:`~horizon.Dashboard` instance. """ Returns the default :class:`~horizon.Dashboard` instance.
If ``"default_dashboard"`` is specified in ``settings.HORIZON_CONFIG`` If ``"default_dashboard"`` is specified in ``HORIZON_CONFIG``
then that dashboard will be returned. If not, the first dashboard then that dashboard will be returned. If not, the first dashboard
returned by :func:`~horizon.get_dashboards` will be returned. returned by :func:`~horizon.get_dashboards` will be returned.
""" """
@ -680,7 +665,7 @@ class Site(Registry, HorizonComponent):
An alternative function can be supplied to customize this behavior An alternative function can be supplied to customize this behavior
by specifying a either a URL or a function which returns a URL via by specifying a either a URL or a function which returns a URL via
the ``"user_home"`` key in ``settings.HORIZON_CONFIG``. Each of these the ``"user_home"`` key in ``HORIZON_CONFIG``. Each of these
would be valid:: would be valid::
{"user_home": "/home",} # A URL {"user_home": "/home",} # A URL
@ -741,9 +726,8 @@ class Site(Registry, HorizonComponent):
dash._autodiscover() dash._autodiscover()
# Allow for override modules # Allow for override modules
config = getattr(settings, "HORIZON_CONFIG", {}) if self._conf.get("customization_module", None):
if config.get("customization_module", None): customization_module = self._conf["customization_module"]
customization_module = config["customization_module"]
bits = customization_module.split('.') bits = customization_module.split('.')
mod_name = bits.pop() mod_name = bits.pop()
package = '.'.join(bits) package = '.'.join(bits)

View File

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

30
horizon/conf/default.py Normal file
View File

@ -0,0 +1,30 @@
from django.conf import settings
from django.utils.translation import ugettext 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")}
}

View File

@ -21,7 +21,7 @@
Context processors used by Horizon. Context processors used by Horizon.
""" """
from django.conf import settings from horizon import conf
def horizon(request): def horizon(request):
@ -37,7 +37,7 @@ def horizon(request):
for each template/template fragment which takes context that is used for each template/template fragment which takes context that is used
to render the complete output. to render the complete output.
""" """
context = {"HORIZON_CONFIG": getattr(settings, "HORIZON_CONFIG", {}), context = {"HORIZON_CONFIG": conf.HORIZON_CONFIG,
"True": True, "True": True,
"False": False} "False": False}

View File

@ -22,7 +22,6 @@ import logging
import os import os
import sys import sys
from django.conf import settings
from django.contrib.auth import logout from django.contrib.auth import logout
from django.http import HttpRequest from django.http import HttpRequest
from django.utils import termcolors from django.utils import termcolors
@ -30,6 +29,7 @@ from django.utils.translation import ugettext as _
from django.views.debug import SafeExceptionReporterFilter, CLEANSED_SUBSTITUTE from django.views.debug import SafeExceptionReporterFilter, CLEANSED_SUBSTITUTE
from horizon import messages from horizon import messages
from horizon.conf import HORIZON_CONFIG
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
PALETTE = termcolors.PALETTES[termcolors.DEFAULT_PALETTE] PALETTE = termcolors.PALETTES[termcolors.DEFAULT_PALETTE]
@ -194,12 +194,10 @@ class HandledException(HorizonException):
self.wrapped = wrapped self.wrapped = wrapped
HORIZON_CONFIG = getattr(settings, "HORIZON_CONFIG", {}) UNAUTHORIZED = tuple(HORIZON_CONFIG['exceptions']['unauthorized'])
EXCEPTION_CONFIG = HORIZON_CONFIG.get("exceptions", {}) NOT_FOUND = tuple(HORIZON_CONFIG['exceptions']['not_found'])
UNAUTHORIZED = tuple(EXCEPTION_CONFIG.get('unauthorized', []))
NOT_FOUND = tuple(EXCEPTION_CONFIG.get('not_found', []))
RECOVERABLE = (AlreadyExists,) RECOVERABLE = (AlreadyExists,)
RECOVERABLE += tuple(EXCEPTION_CONFIG.get('recoverable', [])) RECOVERABLE += tuple(HORIZON_CONFIG['exceptions']['recoverable'])
def error_color(msg): def error_color(msg):

View File

@ -2,7 +2,7 @@
* *
* Note: The number of concurrent AJAX connections hanlded in the queue * Note: The number of concurrent AJAX connections hanlded in the queue
* can be configured by setting an "ajax_queue_limit" key in * can be configured by setting an "ajax_queue_limit" key in
* settings.HORIZON_CONFIG to the desired number (or None to disable queue * HORIZON_CONFIG to the desired number (or None to disable queue
* limiting). * limiting).
*/ */
horizon.ajax = { horizon.ajax = {

View File

@ -35,6 +35,7 @@ from django.utils.translation import ugettext_lazy as _
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils import termcolors from django.utils import termcolors
from horizon import conf
from horizon import exceptions from horizon import exceptions
from horizon import messages from horizon import messages
from horizon.utils import html from horizon.utils import html
@ -359,7 +360,7 @@ class Row(html.HTMLElement):
lookup versus the table's "list" lookup). lookup versus the table's "list" lookup).
The automatic update interval is configurable by setting the key The automatic update interval is configurable by setting the key
``ajax_poll_interval`` in the ``settings.HORIZON_CONFIG`` dictionary. ``ajax_poll_interval`` in the ``HORIZON_CONFIG`` dictionary.
Default: ``2500`` (measured in milliseconds). Default: ``2500`` (measured in milliseconds).
.. attribute:: table .. attribute:: table
@ -452,7 +453,7 @@ class Row(html.HTMLElement):
self.cells = SortedDict(cells) self.cells = SortedDict(cells)
if self.ajax: if self.ajax:
interval = settings.HORIZON_CONFIG.get('ajax_poll_interval', 2500) interval = conf.HORIZON_CONFIG['ajax_poll_interval']
self.attrs['data-update-interval'] = interval self.attrs['data-update-interval'] = interval
self.attrs['data-update-url'] = self.get_ajax_update_url() self.attrs['data-update-url'] = self.get_ajax_update_url()
self.classes.append("ajax-update") self.classes.append("ajax-update")

View File

@ -14,12 +14,9 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
horizon_config = getattr(settings, "HORIZON_CONFIG", {}) from horizon import conf
password_config = horizon_config.get("password_validator", {})
def validate_port_range(port): def validate_port_range(port):
@ -28,8 +25,8 @@ def validate_port_range(port):
def password_validator(): def password_validator():
return password_config.get("regex", ".*") return conf.HORIZON_CONFIG["password_validator"]["regex"]
def password_validator_msg(): def password_validator_msg():
return password_config.get("help_text", _("Password is not accepted")) return conf.HORIZON_CONFIG["password_validator"]["help_text"]

View File

@ -183,7 +183,8 @@ def novaclient(request):
request.user.token.id, request.user.token.id,
project_id=request.user.tenant_id, project_id=request.user.tenant_id,
auth_url=url_for(request, 'compute'), auth_url=url_for(request, 'compute'),
insecure=insecure) insecure=insecure,
http_log_debug=settings.DEBUG)
c.client.auth_token = request.user.token.id c.client.auth_token = request.user.token.id
c.client.management_url = url_for(request, 'compute') c.client.management_url = url_for(request, 'compute')
return c return c

View File

@ -2,6 +2,8 @@ import os
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from openstack_dashboard import exceptions
DEBUG = True DEBUG = True
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
@ -12,14 +14,23 @@ TEMPLATE_DEBUG = DEBUG
# https://docs.djangoproject.com/en/1.4/ref/settings/#secure-proxy-ssl-header # https://docs.djangoproject.com/en/1.4/ref/settings/#secure-proxy-ssl-header
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https') # SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
# Default OpenStack Dashboard configuration.
HORIZON_CONFIG = {
'dashboards': ('project', 'admin', 'settings',),
'default_dashboard': 'project',
'user_home': 'openstack_dashboard.views.get_user_home',
'ajax_queue_limit': 10,
'help_url': "http://docs.openstack.org",
'exceptions': {'recoverable': exceptions.RECOVERABLE,
'not_found': exceptions.NOT_FOUND,
'unauthorized': exceptions.UNAUTHORIZED},
}
# Specify a regular expression to validate user passwords. # Specify a regular expression to validate user passwords.
# HORIZON_CONFIG = { # HORIZON_CONFIG["password_validator"] = {
# "password_validator": { # "regex": '.*',
# "regex": '.*', # "help_text": _("Your password does not meet the requirements.")
# "help_text": _("Your password does not meet the requirements.") # },
# },
# 'help_url': "http://docs.openstack.org"
# }
LOCAL_PATH = os.path.dirname(os.path.abspath(__file__)) LOCAL_PATH = os.path.dirname(os.path.abspath(__file__))

View File

@ -16,7 +16,6 @@ SECRET_KEY = generate_or_read_from_file(os.path.join(TEST_DIR,
ROOT_URLCONF = 'openstack_dashboard.urls' ROOT_URLCONF = 'openstack_dashboard.urls'
TEMPLATE_DIRS = ( TEMPLATE_DIRS = (
os.path.join(TEST_DIR, 'templates'), os.path.join(TEST_DIR, 'templates'),
#os.path.join(ROOT_PATH, 'templates'),
) )
TEMPLATE_CONTEXT_PROCESSORS += ( TEMPLATE_CONTEXT_PROCESSORS += (

View File

@ -25,7 +25,7 @@ from .utils import TestDataContainer
def create_stubbed_exception(cls, status_code=500): def create_stubbed_exception(cls, status_code=500):
msg = "Expected failure." msg = "Expected failure."
def fake_init_exception(self, code, message): def fake_init_exception(self, code, message, **kwargs):
self.code = code self.code = code
self.message = message self.message = message