diff --git a/doc/source/configuration/settings.rst b/doc/source/configuration/settings.rst index a8acc5a0aa..a6da05aad3 100644 --- a/doc/source/configuration/settings.rst +++ b/doc/source/configuration/settings.rst @@ -174,8 +174,11 @@ Default: ``"default"`` This setting tells Horizon which theme to use if the user has not yet selected a theme through the theme picker and therefore set the cookie value. This value represents the ``theme_name`` key that is -used from ``AVAILABLE_THEMES``. To use this setting, the theme must -also be configured inside of ``AVAILABLE_THEMES``. +used from `AVAILABLE_THEMES`_. To use this setting, the theme must +also be configured inside of ``AVAILABLE_THEMES``. Your default theme +must be configured as part of `SELECTABLE_THEMES`_. If it is not, then +your ``DEFAULT_THEME`` will default to the first theme in +``SELECTABLE_THEMES``. DEFAULT_THEME_PATH ------------------ @@ -815,6 +818,19 @@ This setting allows you to expose configuration values over Horizons internal REST API, so that the AngularJS panels can access them. Please be cautious about which values are listed here (and thus exposed on the frontend) +SELECTABLE_THEMES +--------------------- + +.. versionadded:: 12.0.0(Pike) + +Default: ``AVAILABLE_THEMES`` + +This setting tells Horizon which themes to expose to the user as selectable +in the theme picker widget. This value defaults to all themes configured +in `AVAILABLE_THEMES`_, but a brander may wish to simply inherit from an +existing theme and not allow that parent theme to be selected by the user. +``SELECTABLE_THEMES`` takes the exact same format as ``AVAILABLE_THEMES``. + SESSION_TIMEOUT --------------- diff --git a/doc/source/configuration/themes.rst b/doc/source/configuration/themes.rst index 05f897ce71..96ac38fca5 100644 --- a/doc/source/configuration/themes.rst +++ b/doc/source/configuration/themes.rst @@ -81,6 +81,13 @@ directory. The following is an example of inheriting from the material theme:: @import "/themes/material/variables"; @import "/themes/material/styles"; +All themes will need to be configured in ``AVAILABLE_THEMES`` to allow +inheritance. If you wish to inherit from a theme, but not show that theme +as a selectable option in the theme picker widget, then simply configure the +``SELECTABLE_THEMES`` to exclude the parent theme. ``SELECTABLE_THEMES`` must +be of the same format as ``AVAILABLE_THEMES``. It defaults to +``AVAILABLE_THEMES`` if it is not set explicitly. + Bootswatch ~~~~~~~~~~ diff --git a/horizon/themes.py b/horizon/themes.py index e03e917911..ba8ad07f89 100644 --- a/horizon/themes.py +++ b/horizon/themes.py @@ -38,6 +38,11 @@ else: _local = threading.local() +# Get the themes from settings +def get_selectable_themes(): + return getattr(settings, 'SELECTABLE_THEMES', []) + + # Get the themes from settings def get_themes(): return getattr(settings, 'AVAILABLE_THEMES', diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index 65cfe4c788..eae39e4bf7 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -55,6 +55,7 @@ MEDIA_ROOT = None MEDIA_URL = None STATIC_ROOT = None STATIC_URL = None +SELECTABLE_THEMES = None INTEGRATION_TESTS_SUPPORT = False NG_TEMPLATE_CACHE_AGE = 2592000 @@ -376,11 +377,14 @@ if STATIC_ROOT is None: if STATIC_URL is None: STATIC_URL = WEBROOT + 'static/' -AVAILABLE_THEMES, DEFAULT_THEME = theme_settings.get_available_themes( - AVAILABLE_THEMES, - CUSTOM_THEME_PATH, - DEFAULT_THEME_PATH, - DEFAULT_THEME +AVAILABLE_THEMES, SELECTABLE_THEMES, DEFAULT_THEME = ( + theme_settings.get_available_themes( + AVAILABLE_THEMES, + CUSTOM_THEME_PATH, + DEFAULT_THEME_PATH, + DEFAULT_THEME, + SELECTABLE_THEMES + ) ) if CUSTOM_THEME_PATH is not None: diff --git a/openstack_dashboard/templatetags/themes.py b/openstack_dashboard/templatetags/themes.py index c87555fe55..c76b71ea04 100644 --- a/openstack_dashboard/templatetags/themes.py +++ b/openstack_dashboard/templatetags/themes.py @@ -69,7 +69,7 @@ def find_asset(theme, asset): @register.assignment_tag() def themes(): - return hz_themes.get_themes() + return hz_themes.get_selectable_themes() @register.assignment_tag() diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py index 28409a0271..8b36dfda95 100644 --- a/openstack_dashboard/test/settings.py +++ b/openstack_dashboard/test/settings.py @@ -63,6 +63,14 @@ AVAILABLE_THEMES = [ ), ] +SELECTABLE_THEMES = [ + ( + 'default', + pgettext_lazy('Default style theme', 'Default'), + 'themes/default' + ), +] + # Theme Static Directory THEME_COLLECTION_DIR = 'themes' diff --git a/openstack_dashboard/test/themes.py b/openstack_dashboard/test/themes.py new file mode 100644 index 0000000000..06f29ae0a6 --- /dev/null +++ b/openstack_dashboard/test/themes.py @@ -0,0 +1,29 @@ +# 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 openstack_dashboard import settings +from openstack_dashboard.templatetags import themes +from openstack_dashboard.test import helpers as test + + +class SelectableThemeTest(test.TestCase): + def test_selectable_theme_defaults(self): + selectable = settings.SELECTABLE_THEMES + available = settings.AVAILABLE_THEMES + # NOTE(e0ne): veryfy that by default 'selectable' are the same as + # 'available' list + self.assertEqual(selectable, available) + + def test_selectable_override(self): + selectable = themes.themes() + available = themes.settings.AVAILABLE_THEMES + self.assertNotEqual(selectable, available) diff --git a/openstack_dashboard/theme_settings.py b/openstack_dashboard/theme_settings.py index ff7cdec9e8..5b5090b394 100644 --- a/openstack_dashboard/theme_settings.py +++ b/openstack_dashboard/theme_settings.py @@ -41,7 +41,7 @@ def get_theme_static_dirs(available_themes, collection_dir, root): def get_available_themes(available_themes, custom_path, default_path, - default_theme): + default_theme, selectable_themes): new_theme_list = [] # We can only support one path at a time, because of static file # collection. @@ -100,4 +100,13 @@ def get_available_themes(available_themes, custom_path, default_path, if default_theme_ndx == -1 and custom_ndx == -1: default_theme = available_themes[0][0] - return new_theme_list, default_theme + if selectable_themes is None: + selectable_themes = new_theme_list + + if default_theme not in [x[0] for x in selectable_themes]: + default_theme = selectable_themes[0][0] + logging.warning("Your DEFAULT_THEME is not configured in your " + "selectable themes, therefore using %s as your " + "default theme." % default_theme) + + return new_theme_list, selectable_themes, default_theme diff --git a/releasenotes/notes/Selectable-Themes-Setting-863c4686f71c70d8.yaml b/releasenotes/notes/Selectable-Themes-Setting-863c4686f71c70d8.yaml new file mode 100644 index 0000000000..347ccb24ac --- /dev/null +++ b/releasenotes/notes/Selectable-Themes-Setting-863c4686f71c70d8.yaml @@ -0,0 +1,11 @@ +--- +prelude: > + AVAILABLE_THEMES was used to determine whether a + theme was selectable via the user facing widget, + however it was noted that sometimes a parent theme + is desired for inheritance, and needs to be hidden + from the widget entirely. SELECTABLE_THEMES was + added as a setting that can be used to collect + a theme for inheritance, but hide it from the + user's view. See the settings documentation for + usage specifics.