Fix bug where WEBROOT is not respected

The WEB_ROOT setting needs to be respected rather than assuming / as
the root of the application. Simiarly, we can not assume that STATIC_URL
is a sub url of the WEB_ROOT. They can be configured as two indepedent url.

Angular templates are loaded dynamically with Ajax, which requires an
absolute base path. We define those base paths as angular constants for
template use and future routing.

TO TEST, follow directions at:
  http://docs.openstack.org/developer/horizon/topics/settings.html#webroot

Co-Authored-By: Matt Borland <matt.borland@hp.com>
Co-Authored-By: Shaoquan Chen <sean.chen2@hp.com>
Co-Authored-By: Thai Tran <tqtran@us.ibm.com>

Change-Id: Ifcd459633682edc94b270019ce77e17d64bea22d
Closes-Bug: #1451681
This commit is contained in:
Thai Tran 2015-07-21 10:45:40 -07:00 committed by Shaoquan Chen
parent ede7402604
commit 35e47358f0
19 changed files with 117 additions and 77 deletions
horizon
static
templates/horizon/jasmine
openstack_dashboard
context_processors.py
dashboards
identity/static/dashboard/identity
project
images/images
instances
network_topology/templates/network_topology
settings.py
static/dashboard
templates/horizon
test-shim.js

@ -7,16 +7,20 @@
'horizon.framework.util',
'horizon.framework.widgets'
])
.constant('horizon.framework.basePath', '/static/framework/')
.config(frameworkConfiguration);
.config(config);
frameworkConfiguration.$inject = [
config.$inject = [
'$provide',
'$interpolateProvider',
'$httpProvider',
'$windowProvider'
];
function frameworkConfiguration($interpolateProvider, $httpProvider, $windowProvider) {
function config($provide, $interpolateProvider, $httpProvider, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'framework/';
$provide.constant('horizon.framework.basePath', path);
// Replacing the default angular symbol
// allow us to mix angular with django templates
$interpolateProvider.startSymbol('{$');

@ -21,11 +21,17 @@ limitations under the License.
.module('horizon.framework.util.http', [])
.service('horizon.framework.util.http.service', ApiService);
ApiService.$inject = ['$http'];
ApiService.$inject = ['$http', '$window'];
function ApiService($http) {
function ApiService($http, $window) {
var httpCall = function (method, url, data, config) {
/* eslint-disable angular/ng_window_service */
url = $window.WEBROOT + url;
/* eslint-enable angular/ng_window_service */
url = url.replace(/\/+/g, '/');
if (angular.isUndefined(config)) {
config = {};
}

@ -1,7 +1,8 @@
(function () {
'use strict';
angular.module('horizon.framework.util', [
angular
.module('horizon.framework.util', [
'horizon.framework.util.bind-scope',
'horizon.framework.util.filters',
'horizon.framework.util.http',
@ -10,5 +11,11 @@
'horizon.framework.util.workflow',
'horizon.framework.util.validators'
])
.constant('horizon.framework.util.basePath', '/static/framework/util/');
.config(config);
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'framework/util/';
$provide.constant('horizon.framework.util.basePath', path);
}
})();

@ -79,11 +79,14 @@
}
];
var markup = '<magic-search ' +
'template="/static/framework/widgets/magic-search/magic-search.html" ' +
/* eslint-disable angular/ng_window_service */
var markup =
'<magic-search ' +
'template="' + window.STATIC_URL + 'framework/widgets/magic-search/magic-search.html" ' +
'strings="filterStrings" ' +
'facets="{{ filterFacets }}">' +
'</magic-search>';
/* eslint-enable angular/ng_window_service */
$element = $compile(angular.element(markup))($scope);

@ -1,7 +1,8 @@
(function () {
'use strict';
angular.module('horizon.framework.widgets', [
angular
.module('horizon.framework.widgets', [
'horizon.framework.widgets.help-panel',
'horizon.framework.widgets.wizard',
'horizon.framework.widgets.table',
@ -14,6 +15,11 @@
'horizon.framework.widgets.metadata-display',
'horizon.framework.widgets.toast'
])
.constant('horizon.framework.widgets.basePath', '/static/framework/widgets/');
.config(config);
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'framework/widgets/';
$provide.constant('horizon.framework.widgets.basePath', path);
}
})();

@ -1,4 +1,3 @@
/* global STATIC_URL, console */
/* Namespace for core functionality related to DataTables. */
horizon.datatables = {
update: function () {
@ -70,7 +69,8 @@ horizon.datatables = {
var imagePath = $new_row.find('.btn-action-required').length > 0 ?
"dashboard/img/action_required.png":
"dashboard/img/loading.gif";
imagePath = STATIC_URL + imagePath;
imagePath = window.STATIC_URL + imagePath;
spinner_elm.prepend(
$("<div>")
.addClass("loading_gif")

@ -5,6 +5,10 @@
<title>Jasmine Spec Runner</title>
<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}horizon/lib/jasmine/jasmine.css">
<script>
window.STATIC_URL = '/static/';
window.WEBROOT = '/';
</script>
<script src="{% url 'horizon:jsi18n' 'horizon' %}"></script>
<script src="{{ STATIC_URL }}horizon/lib/jasmine/jasmine.js"></script>
<script src="{{ STATIC_URL }}horizon/lib/jasmine/jasmine-html.js"></script>

@ -53,4 +53,7 @@ def openstack(request):
region in available_regions]}
context['regions'] = regions
# Adding webroot access
context['WEBROOT'] = getattr(settings, "WEBROOT", "/")
return context

@ -29,15 +29,11 @@
'hz.dashboard.identity.users',
'hz.dashboard.identity.projects'
])
.config(config);
/**
* @name hz.dashboard.identity.basePath
* @description Base path for the identity dashboard
*/
.constant('hz.dashboard.identity.basePath', getBasePath());
function getBasePath() {
return (window.WEBROOT || '') + '/static/dashboard/identity/';
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/identity/';
$provide.constant('hz.dashboard.identity.basePath', path);
}
})();

@ -24,10 +24,11 @@
*/
angular
.module('hz.dashboard.identity.projects', [])
.constant('hz.dashboard.identity.projects.basePath', basePath());
.config(config);
function basePath() {
return (window.WEBROOT || '') + '/static/dashboard/identity/projects/';
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/identity/projects/';
$provide.constant('hz.dashboard.identity.projects.basePath', path);
}
})();

@ -44,6 +44,7 @@
var toastService;
var policyAPI;
var keystoneAPI;
var staticUrl;
///////////////////////
@ -61,6 +62,7 @@
policyAPI = $injector.get('horizon.openstack-service-api.policy');
keystoneAPI = $injector.get('horizon.openstack-service-api.keystone');
controller = $injector.get('$controller');
staticUrl = $injector.get('$window').STATIC_URL;
spyOn(toastService, 'add').and.callFake(fakeToast);
spyOn(policyAPI, 'check').and.callFake(fakePolicy);
@ -77,7 +79,7 @@
}
it('should set path properly', function() {
var path = '/static/dashboard/identity/users/table/';
var path = staticUrl + 'dashboard/identity/users/table/';
expect(createController().path).toEqual(path);
});

@ -60,20 +60,20 @@ class LaunchImage(tables.LinkAction):
class LaunchImageNG(LaunchImage):
name = "launch_image_ng"
verbose_name = _("Launch")
url = "horizon:project:images:index"
classes = ("btn-launch")
ajax = False
def __init__(self, attrs=None, **kwargs):
if attrs is None:
attrs = {"ng-controller": "LaunchInstanceModalController"}
kwargs['preempt'] = True
super(LaunchImage, self).__init__(attrs, **kwargs)
def get_link_url(self, datum):
imageId = self.table.get_object_id(datum)
clickValue = "openLaunchInstanceWizard({successUrl: " +\
"'/project/images/', imageId: '%s'})" % (imageId)
self.attrs['ng-click'] = clickValue
url = reverse(self.url)
ngclick = "openLaunchInstanceWizard({successUrl: '%s', imageId: '%s'})"
self.attrs.update({"ng-controller": "LaunchInstanceModalController",
"ng-click": ngclick % (url, imageId)})
return "javascript:void(0);"

@ -349,18 +349,16 @@ class LaunchLink(tables.LinkAction):
class LaunchLinkNG(LaunchLink):
name = "launch-ng"
url = "horizon:project:instances:index"
ajax = False
classes = ("btn-launch")
def __init__(self,
attrs={
"ng-controller": "LaunchInstanceModalController",
"ng-click": "openLaunchInstanceWizard(" +
"{successUrl: '/project/instances/'})"
},
**kwargs):
kwargs['preempt'] = True
super(LaunchLink, self).__init__(attrs, **kwargs)
def get_default_attrs(self):
url = urlresolvers.reverse(self.url)
ngclick = "openLaunchInstanceWizard({ successUrl: '%s' })" % url
self.attrs.update({'ng-controller': 'LaunchInstanceModalController',
'ng-click': ngclick})
return super(LaunchLinkNG, self).get_default_attrs()
def get_link_url(self, datum=None):
return "javascript:void(0);"

@ -29,7 +29,8 @@
<div class="launchButtons">
{% if launch_instance_allowed %}
{% if show_ng_launch %}
<a ng-controller="LaunchInstanceModalController" ng-click="openLaunchInstanceWizard({successUrl: '/project/network_topology/', dismissUrl: '/project/network_topology/'})" id="instances__action_launch" class="btn btn-default btn-sm btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
{% url 'horizon:project:network_topology:index' as networkUrl %}
<a ng-controller="LaunchInstanceModalController" ng-click="openLaunchInstanceWizard({successUrl: '{{networkUrl}}', dismissUrl: '{{networkUrl}}'})" id="instances__action_launch" class="btn btn-default btn-sm btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
{% endif %}
<a href="{% url 'horizon:project:network_topology:launchinstance' %}" id="instances__action_launch" class="btn btn-default btn-sm btn-launch ajax-modal {% if instance_quota_exceeded %}disabled{% endif %}"><span class="fa fa-cloud-upload"></span> {% if instance_quota_exceeded %}{% trans "Launch Instance (Quota exceeded)"%}{% else %}{% trans "Launch Instance"%}{% endif %}</a>
{% endif %}

@ -364,6 +364,7 @@ POLICY_CHECK_FUNCTION = policy_backend.check
# Add HORIZON_CONFIG to the context information for offline compression
COMPRESS_OFFLINE_CONTEXT = {
'WEBROOT': WEBROOT,
'STATIC_URL': STATIC_URL,
'HORIZON_CONFIG': HORIZON_CONFIG,
}

@ -1,11 +1,16 @@
(function () {
'use strict';
angular.module('hz.dashboard', [
angular
.module('hz.dashboard', [
'hz.dashboard.launch-instance',
'hz.dashboard.tech-debt'
])
.config(config);
.constant('dashboardBasePath', '/static/dashboard/');
function config($provide, $windowProvider) {
var path = $windowProvider.$get().STATIC_URL + 'dashboard/';
$provide.constant('dashboardBasePath', path);
}
})();

@ -24,18 +24,20 @@
describe('hz.dashboard:constant:dashboardBasePath', function () {
var dashboardBasePath;
var staticUrl;
beforeEach(module('hz.dashboard'));
beforeEach(inject(function ($injector) {
dashboardBasePath = $injector.get('dashboardBasePath');
staticUrl = $injector.get('$window').STATIC_URL;
}));
it('should be defined', function () {
expect(dashboardBasePath).toBeDefined();
});
it('should equal to "/static/dashboard/"', function () {
expect(dashboardBasePath).toEqual('/static/dashboard/');
it('should get set correctly', function () {
expect(dashboardBasePath).toEqual(staticUrl + 'dashboard/');
});
});

@ -8,7 +8,10 @@
{% comment %} Compress jQuery, Angular, Plugins, Bootstrap, Hogan.js and Horizon-specific JS. {% endcomment %}
{% compress js %}
<script type="text/javascript">var STATIC_URL = "{{ STATIC_URL }}";</script>
<script type="text/javascript">
var STATIC_URL = "{{ STATIC_URL }}";
var WEBROOT = "{{ WEBROOT }}";
</script>
<script src='{{ STATIC_URL }}horizon/lib/angular/angular-cookies.js'></script>
<script src='{{ STATIC_URL }}horizon/lib/angular/angular-sanitize.js'></script>
<script src="{{ STATIC_URL }}horizon/lib/angular/lrdragndrop.js"></script>

@ -90,9 +90,7 @@ var horizonPlugInModules = [];
globals.npgettext = django.npgettext;
globals.interpolate = django.interpolate;
globals.get_format = django.get_format;
globals.STATIC_URL = '/static/';
globals.WEBROOT = '/';
}(this));