Add Developer Dashboard + Bootstrap Theme Preview

Themers need an easy way to see how customizations in
the new themeing mechanism will affect known bootstrap
elements.

While DEBUG is True, the developer dashboard will be present in the
Horizon nav sidebar. This currently displays the Bootstrap Theme Preview
page, and will also be used for Angular widget demos etc in the future.

Documentation on SCSS and the preview page are being provided in a
separate patch to coordinate with the theming work
(https://bugs.launchpad.net/horizon/+bug/1514869)

Note: The SECRET_KEY block in settings.py is moved before the enabled
files setup, so that importing settings in the enabled file doesn't
cause errors.

Co-Authored-By: Diana Whitten <hurgleburgler@gmail.com>

Implements: blueprint bootstrap-theme-preview
Change-Id: I44bc52d4dcfbbdc60a27879e638d78c4b508b2e9
This commit is contained in:
Rob Cresswell 2015-08-05 19:00:00 +01:00
parent 1b6807baf3
commit b285358a96
22 changed files with 1724 additions and 12 deletions

View File

@ -0,0 +1,26 @@
# Copyright 2015 Cisco Systems, 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 _
import horizon
class Developer(horizon.Dashboard):
name = _("Developer")
slug = "developer"
default_panel = "theme_preview"
horizon.register(Developer)

View File

@ -0,0 +1,45 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
/**
* @ngdoc module
* @ngname horizon.dashboard.developer
* @description
* Dashboard module to host developer panels.
*/
angular
.module('horizon.dashboard.developer', [
'horizon.dashboard.developer.theme-preview'
])
.config(config);
config.$inject = [
'$provide',
'$windowProvider'
];
/**
* @name horizon.dashboard.developer.basePath
* @description Base path for the developer dashboard
*/
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/developer/';
$provide.constant('horizon.dashboard.developer.basePath', path);
}
})();

View File

@ -0,0 +1,42 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
describe('horizon.dashboard.developer', function () {
it('should be defined', function () {
expect(angular.module('horizon.dashboard.developer')).toBeDefined();
});
});
describe('horizon.dashboard.developer.basePath constant', function () {
var developerBasePath, staticUrl;
beforeEach(module('horizon.dashboard.developer'));
beforeEach(inject(function ($injector) {
developerBasePath = $injector.get('horizon.dashboard.developer.basePath');
staticUrl = $injector.get('$window').STATIC_URL;
}));
it('should be defined', function () {
expect(developerBasePath).toBeDefined();
});
it('should equal to "/static/dashboard/developer/"', function () {
expect(developerBasePath).toEqual(staticUrl + 'dashboard/developer/');
});
});
})();

View File

@ -0,0 +1,2 @@
// Top level file for Developer dashboard SCSS
@import "theme-preview/theme-preview";

View File

@ -0,0 +1,101 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function() {
'use strict';
angular
.module('horizon.dashboard.developer.theme-preview')
.directive('themepreview', themePreview);
themePreview.$inject = ['horizon.dashboard.developer.basePath'];
/**
* @ngdoc directive
* @name themepreview
* @description
* Wraps the JS code for displaying the theme preview page. Shouldnt be used elsewhere.
*/
function themePreview(path) {
var directive = {
restrict: 'E',
templateUrl: path + 'theme-preview/theme-preview.html',
link: link
};
return directive;
}
function link(scope, element) {
//TODO(tqtran) Use angular, not jquery
$('a[href="#"]').click(function(e) {
e.preventDefault();
});
var $modal = $('#source-modal');
var $pre = $modal.find('pre');
var $button = $('<div id="source-button" class="btn btn-primary btn-xs"><span class="fa fa-code"></span></div>')
.click(function(){
var $fragment = stripAngular($(this).parent().clone());
var html = cleanSource($fragment.html());
$pre.text(html);
$modal.modal();
});
var $component = $('.bs-component');
$component.find('[data-toggle="popover"]').popover();
$component.find('[data-toggle="tooltip"]').tooltip();
$component.hover(function() {
$(this).append($button);
$button.show();
});
}
// Utility function to clean up the source code before displaying
function stripAngular($frag) {
var $translated = $frag.find('[translate]')
.removeAttr('translate');
$translated.html($translated.find('> span').html());
$frag.find('.ng-scope').removeClass('ng-scope');
$frag.find('.ng-pristine').removeClass('ng-pristine');
$frag.find('.ng-valid').removeClass('ng-valid');
$frag.find('input').removeAttr('style');
return $frag;
}
// Utility function to clean up the source code before displaying
function cleanSource(html) {
var lines = html.split(/\n/);
lines.shift();
lines.splice(-1, 1);
var indentSize = lines[0].length - lines[0].trim().length;
var re = new RegExp(' {' + indentSize + '}');
lines = lines.map(function(line) {
if (line.match(re)) {
line = line.substring(indentSize);
}
return line;
});
lines = lines.join('\n');
return lines;
}
})();

View File

@ -0,0 +1,28 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
/**
* @ngdoc module
* @ngname horizon.dashboard.developer.theme-preview
* @description
* Dashboard module for the bootstrap theme preview panel.
*/
angular
.module('horizon.dashboard.developer.theme-preview', [])
})();

View File

@ -0,0 +1,25 @@
/*
* (c) Copyright 2015 Cisco Systems, 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.
*/
(function () {
'use strict';
describe('horizon.dashboard.developer.theme-preview', function () {
it('should be defined', function () {
expect(angular.module('horizon.dashboard.developer.theme-preview')).toBeDefined();
});
});
})();

View File

@ -0,0 +1,53 @@
@import "/custom/variables";
themepreview {
#source-button {
font-weight: 700;
position: absolute;
right: 0;
top: 0;
z-index: 100;
}
.bs-component {
position: relative;
}
.bs-component .modal-dialog {
width: 90%;
}
.bs-component .modal {
bottom: auto;
display: block;
left: auto;
position: relative;
right: auto;
top: auto;
z-index: 1;
}
.bs-component .popover {
display: inline-block;
margin: 20px;
position: relative;
width: 220px;
}
.left .tooltip-inner {
float: right;
}
#nav-tabs .nav-tabs {
margin-bottom: 15px;
}
.nav-pills.nav-pills-stacked {
max-width: 300px;
}
.navbar {
margin-bottom: $padding-large-horizontal*2;
}
}

View File

@ -0,0 +1,27 @@
# Copyright 2015 Cisco Systems, 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 horizon
from horizon import base
from openstack_dashboard.test import helpers as test
class DeveloperTests(test.TestCase):
# Tests are run with DEBUG=False, so check that dashboard isn't registered
def test_registration_failure(self):
with self.assertRaises(base.NotRegistered):
horizon.get_dashboard("developer")

View File

@ -0,0 +1,22 @@
# Copyright 2015 Cisco Systems, 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 _
import horizon
class Preview(horizon.Panel):
name = _("Bootstrap Theme Preview")
slug = 'theme_preview'

View File

@ -0,0 +1,14 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}
{% trans "Theme Preview" %}
{% endblock %}
{% block page_header %}
<h1>{{ skin }} <small>{{ skin_desc }}</small></h1>
{% endblock %}
{% block main %}
<themepreview></themepreview>
{% endblock %}

View File

@ -0,0 +1,45 @@
# Copyright 2015 Cisco Systems, 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.conf import settings
from django.core import urlresolvers
from django.core.urlresolvers import reverse
from django.utils.importlib import import_module # noqa
from horizon import base
from openstack_dashboard.contrib.developer.dashboard import Developer
from openstack_dashboard.test import helpers as test
class ThemePreviewTests(test.TestCase):
# Manually register Developer Dashboard, as DEBUG=False in tests
def setUp(self):
super(ThemePreviewTests, self).setUp()
urlresolvers.clear_url_caches()
reload(import_module(settings.ROOT_URLCONF))
base.Horizon.register(Developer)
base.Horizon._urls()
def tearDown(self):
super(ThemePreviewTests, self).tearDown()
base.Horizon.unregister(Developer)
base.Horizon._urls()
def test_index(self):
index = reverse('horizon:developer:theme_preview:index')
res = self.client.get(index)
self.assertTemplateUsed(res, 'developer/theme_preview/index.html')

View File

@ -0,0 +1,24 @@
# Copyright 2015 Cisco Systems, 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.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.contrib.developer.theme_preview import views
urlpatterns = patterns(
'openstack_dashboard.contrib.developer.theme_preview.views',
url(r'^$', views.IndexView.as_view(), name='index'),
)

View File

@ -0,0 +1,34 @@
# Copyright 2015 Cisco Systems, 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 logging
from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from horizon import views
LOG = logging.getLogger(__name__)
class IndexView(views.HorizonTemplateView):
template_name = 'developer/theme_preview/index.html'
page_title = _("Bootstrap Theme Preview")
def get_context_data(self, **kwargs):
theme_path = settings.CUSTOM_THEME_PATH
context = super(IndexView, self).get_context_data(**kwargs)
context['skin'] = theme_path.split('/')[-1]
context['skin_desc'] = theme_path
return context

View File

@ -0,0 +1,36 @@
# Copyright 2015 Cisco Systems, 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.conf import settings
DASHBOARD = 'developer'
ADD_ANGULAR_MODULES = [
'horizon.dashboard.developer'
]
ADD_INSTALLED_APPS = [
'openstack_dashboard.contrib.developer'
]
ADD_SCSS_FILES = [
'dashboard/developer/developer.scss',
]
AUTO_DISCOVER_STATIC_FILES = True
DISABLED = True
if getattr(settings, 'DEBUG', False):
DISABLED = False

View File

@ -0,0 +1,21 @@
# Copyright 2015 Cisco Systems, 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.
PANEL = 'theme_preview'
PANEL_GROUP = 'default'
PANEL_DASHBOARD = 'developer'
ADD_PANEL = \
'openstack_dashboard.contrib.developer.theme_preview.panel.Preview'

View File

@ -327,6 +327,18 @@ if os.path.exists(os.path.join(CUSTOM_THEME, 'img')):
# specs files and external templates. # specs files and external templates.
find_static_files(HORIZON_CONFIG) find_static_files(HORIZON_CONFIG)
# Ensure that we always have a SECRET_KEY set, even when no local_settings.py
# file is present. See local_settings.py.example for full documentation on the
# horizon.utils.secret_key module and its use.
if not SECRET_KEY:
if not LOCAL_PATH:
LOCAL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'local')
from horizon.utils import secret_key
SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH,
'.secret_key_store'))
# Load the pluggable dashboard settings # Load the pluggable dashboard settings
import openstack_dashboard.enabled import openstack_dashboard.enabled
import openstack_dashboard.local.enabled import openstack_dashboard.local.enabled
@ -343,18 +355,6 @@ settings.update_dashboards(
) )
INSTALLED_APPS[0:0] = ADD_INSTALLED_APPS INSTALLED_APPS[0:0] = ADD_INSTALLED_APPS
# Ensure that we always have a SECRET_KEY set, even when no local_settings.py
# file is present. See local_settings.py.example for full documentation on the
# horizon.utils.secret_key module and its use.
if not SECRET_KEY:
if not LOCAL_PATH:
LOCAL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
'local')
from horizon.utils import secret_key
SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH,
'.secret_key_store'))
from openstack_auth import policy from openstack_auth import policy
POLICY_CHECK_FUNCTION = policy.check POLICY_CHECK_FUNCTION = policy.check

View File

@ -35,6 +35,8 @@ TEMPLATE_DIRS = (
os.path.join(TEST_DIR, 'templates'), os.path.join(TEST_DIR, 'templates'),
) )
CUSTOM_THEME_PATH = 'themes/default'
TEMPLATE_CONTEXT_PROCESSORS += ( TEMPLATE_CONTEXT_PROCESSORS += (
'openstack_dashboard.context_processors.openstack', 'openstack_dashboard.context_processors.openstack',
) )

View File

@ -0,0 +1,8 @@
---
features:
- Added the Developer dashboard plugin to contrib. This runs when
``DEBUG=True``, and adds tooling to the UI to aid in development.
- Added the Bootstrap Theme Preview panel to the Developer dashboard. This
panel contains a list of Bootstrap components with source code, so that
developers can see examples of how to structure this code and the
effects their theme will have upon it.