horizon/openstack_dashboard/static_settings.py
Tyr Johanson fca46ab60f Pre-populate the Angular template cache and allow template overrides
This patch populates the Angular template cache from Django.
This eliminates the need for Angular to do an http get for every HTML
fragment.

In addition, now that we are filling the template cache, this patch
introduces the logic needed to override any Angular template HTML from
the current theme.

How it works:
A new template tag is created called "template_cache_preloads". This
tag is used in _scripts.html to generate a list of text/javascript
script tags, each one containing an Angular "run" method that loads
a template contents into the Angular template cache. The first time
any Horizon page is loaded after server start, the template cache
preloads are computed for the current theme.

The output of this tag is cached for 30 days in Django using the
"cache" tag. Further, that cached result is wrapped in a "compress js"
tag to collapse the individual <script> tags into 1 block of
javascript, and compress like all other javascript Horizon serves to
the client.

Finally, when using offline compression, the compressor evaluates the
nodelist (HTML content) of _scripts.html, notices the compress tag
and builds the template cache preloads for each possible theme. Later,
at runtime, when the preloads are generated for the current theme, the
compressor gets the result from the Django cache, and hashes the
contents to determine which manifest file to serve to the client.
Since the preloads generated at run-time are identical to those
generated off-line, the compressor hash matches an existing manifest
which is served to the client.

Notice that even though the template cache pre-loads are generated
off-line...the template_cache_preloads tag will be executed once
every 30 days anyway. However, since the result matches the off-line
compression, the existing manifest continues to be served to the client.

Finally, this patch ALSO watches for 'post_compress' signals. If it
detects that the angular template preloads have been re-compressed, it
clears the old version from the Django cache.

To test the template caching:
- Run horizon
- View page source
- Notice the new <script type="text/javascript"> tags contained in
  the body (only visible if COMPRESS_ENABLED=False
- Open the javascript inspector
- Load launch instance
- Notice there are no longer http calls to load each HTML fragment
  used by the Angular launch instance

To test the override:
- Set the DEFAULT_THEME='material'
- Create /horizon/openstack_dashboard/themes/material/\
static/templates/framework/widgets/help-panel/help-panel.html
- Set the content to <h1>TEST</h1>
- Run Horizon and open launch instance.
- The help content should contain "TEST"

To test the new template tag:
- set a breakpoint or print in angular.py:template_cache_preloads
  and observe when it is called during off-line or run-time use

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

Implements: blueprint angular-template-overrides
Change-Id: I0e4e2623be58abbc68c6e02b2e9c5d7cdaba8e4d
2016-07-13 15:38:07 -07:00

217 lines
8.6 KiB
Python

# 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.
"""
This file contains configuration for the locations of all the static file
libraries, such as JavaScript and CSS libraries. Packagers for individual
distributions can edit or replace this file, in order to change the paths
to match their distribution's standards.
"""
import os
import xstatic.main
import xstatic.pkg.angular
import xstatic.pkg.angular_bootstrap
import xstatic.pkg.angular_fileupload
import xstatic.pkg.angular_gettext
import xstatic.pkg.angular_lrdragndrop
import xstatic.pkg.angular_smart_table
import xstatic.pkg.bootstrap_datepicker
import xstatic.pkg.bootstrap_scss
import xstatic.pkg.bootswatch
import xstatic.pkg.d3
import xstatic.pkg.font_awesome
import xstatic.pkg.hogan
import xstatic.pkg.jasmine
import xstatic.pkg.jquery
import xstatic.pkg.jquery_migrate
import xstatic.pkg.jquery_quicksearch
import xstatic.pkg.jquery_tablesorter
import xstatic.pkg.jquery_ui
import xstatic.pkg.jsencrypt
import xstatic.pkg.mdi
import xstatic.pkg.rickshaw
import xstatic.pkg.roboto_fontface
import xstatic.pkg.spin
import xstatic.pkg.termjs
from horizon.utils import file_discovery
from openstack_dashboard import theme_settings
def get_staticfiles_dirs(webroot='/'):
STATICFILES_DIRS = [
('horizon/lib/angular',
xstatic.main.XStatic(xstatic.pkg.angular,
root_url=webroot).base_dir),
('horizon/lib/angular',
xstatic.main.XStatic(xstatic.pkg.angular_bootstrap,
root_url=webroot).base_dir),
('horizon/lib/angular',
xstatic.main.XStatic(xstatic.pkg.angular_fileupload,
root_url=webroot).base_dir),
('horizon/lib/angular',
xstatic.main.XStatic(xstatic.pkg.angular_gettext,
root_url=webroot).base_dir),
('horizon/lib/angular',
xstatic.main.XStatic(xstatic.pkg.angular_lrdragndrop,
root_url=webroot).base_dir),
('horizon/lib/angular',
xstatic.main.XStatic(xstatic.pkg.angular_smart_table,
root_url=webroot).base_dir),
('horizon/lib/bootstrap_datepicker',
xstatic.main.XStatic(xstatic.pkg.bootstrap_datepicker,
root_url=webroot).base_dir),
('bootstrap',
xstatic.main.XStatic(xstatic.pkg.bootstrap_scss,
root_url=webroot).base_dir),
('horizon/lib/bootswatch',
xstatic.main.XStatic(xstatic.pkg.bootswatch,
root_url=webroot).base_dir),
('horizon/lib',
xstatic.main.XStatic(xstatic.pkg.d3,
root_url=webroot).base_dir),
('horizon/lib',
xstatic.main.XStatic(xstatic.pkg.hogan,
root_url=webroot).base_dir),
('horizon/lib/font-awesome',
xstatic.main.XStatic(xstatic.pkg.font_awesome,
root_url=webroot).base_dir),
('horizon/lib/jasmine',
xstatic.main.XStatic(xstatic.pkg.jasmine,
root_url=webroot).base_dir),
('horizon/lib/jquery',
xstatic.main.XStatic(xstatic.pkg.jquery,
root_url=webroot).base_dir),
('horizon/lib/jquery',
xstatic.main.XStatic(xstatic.pkg.jquery_migrate,
root_url=webroot).base_dir),
('horizon/lib/jquery',
xstatic.main.XStatic(xstatic.pkg.jquery_quicksearch,
root_url=webroot).base_dir),
('horizon/lib/jquery',
xstatic.main.XStatic(xstatic.pkg.jquery_tablesorter,
root_url=webroot).base_dir),
('horizon/lib/jsencrypt',
xstatic.main.XStatic(xstatic.pkg.jsencrypt,
root_url=webroot).base_dir),
('horizon/lib/mdi',
xstatic.main.XStatic(xstatic.pkg.mdi,
root_url=webroot).base_dir),
('horizon/lib',
xstatic.main.XStatic(xstatic.pkg.rickshaw,
root_url=webroot).base_dir),
('horizon/lib/roboto_fontface',
xstatic.main.XStatic(xstatic.pkg.roboto_fontface,
root_url=webroot).base_dir),
('horizon/lib',
xstatic.main.XStatic(xstatic.pkg.spin,
root_url=webroot).base_dir),
('horizon/lib',
xstatic.main.XStatic(xstatic.pkg.termjs,
root_url=webroot).base_dir),
]
if xstatic.main.XStatic(xstatic.pkg.jquery_ui,
root_url=webroot).version.startswith('1.10.'):
# The 1.10.x versions already contain the 'ui' directory.
STATICFILES_DIRS.append(
('horizon/lib/jquery-ui',
xstatic.main.XStatic(xstatic.pkg.jquery_ui,
root_url=webroot).base_dir))
else:
# Newer versions dropped the directory, add it to keep the path the
# same.
STATICFILES_DIRS.append(
('horizon/lib/jquery-ui/ui',
xstatic.main.XStatic(xstatic.pkg.jquery_ui,
root_url=webroot).base_dir))
return STATICFILES_DIRS
def find_static_files(
HORIZON_CONFIG,
AVAILABLE_THEMES,
THEME_COLLECTION_DIR,
ROOT_PATH):
import horizon
import openstack_dashboard
os_dashboard_home_dir = openstack_dashboard.__path__[0]
horizon_home_dir = horizon.__path__[0]
# note the path must end in a '/' or the resultant file paths will have a
# leading "/"
file_discovery.populate_horizon_config(
HORIZON_CONFIG,
os.path.join(horizon_home_dir, 'static/')
)
# filter out non-angular javascript code and lib
HORIZON_CONFIG['js_files'] = ([f for f in HORIZON_CONFIG['js_files']
if not f.startswith('horizon/')])
# note the path must end in a '/' or the resultant file paths will have a
# leading "/"
file_discovery.populate_horizon_config(
HORIZON_CONFIG,
os.path.join(os_dashboard_home_dir, 'static/'),
sub_path='app/'
)
# Discover theme static resources, and in particular any
# static HTML (client-side) that the theme overrides
theme_static_files = {}
theme_info = theme_settings.get_theme_static_dirs(
AVAILABLE_THEMES,
THEME_COLLECTION_DIR,
ROOT_PATH)
for url, path in theme_info:
discovered_files = {}
# discover static files provided by the theme
file_discovery.populate_horizon_config(
discovered_files,
path
)
# Get the theme name from the theme url
theme_name = url.split('/')[-1]
# build a dictionary of this theme's static HTML templates.
# For each overridden template, strip off the '/templates/' part of the
# theme filename then use that name as the key, and the location in the
# theme directory as the value. This allows the quick lookup of
# theme path for any file overridden by a theme template
template_overrides = {}
for theme_file in discovered_files['external_templates']:
# Example:
# external_templates_dict[
# 'framework/widgets/help-panel/help-panel.html'
# ] = 'themes/material/templates/framework/widgets/\
# help-panel/help-panel.html'
(templates_part, override_path) = theme_file.split('/templates/')
template_overrides[override_path] = 'themes/' +\
theme_name + theme_file
discovered_files['template_overrides'] = template_overrides
# Save all of the discovered file info for this theme in our
# 'theme_files' object using the theme name as the key
theme_static_files[theme_name] = discovered_files
# Add the theme file info to the horizon config for use by template tags
HORIZON_CONFIG['theme_static_files'] = theme_static_files