fca46ab60f
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
121 lines
4.5 KiB
Python
121 lines
4.5 KiB
Python
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
|
# All Rights Reserved.
|
|
#
|
|
# 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 compressor.signals import post_compress
|
|
from django.contrib.staticfiles import finders
|
|
from django.core.cache import caches
|
|
from django.core.cache.utils import make_template_fragment_key
|
|
from django.dispatch import receiver
|
|
from django import template
|
|
|
|
register = template.Library()
|
|
|
|
|
|
@receiver(post_compress)
|
|
def update_angular_template_hash(sender, **kwargs):
|
|
"""Listen for compress events. If the angular templates
|
|
have been re-compressed, also clear them from the Django
|
|
cache backend. This is important to allow deployers to
|
|
change a template file, re-compress, and not accidentally
|
|
serve the old Django cached version of that content to
|
|
clients.
|
|
"""
|
|
context = kwargs['context'] # context the compressor is working with
|
|
theme = context['THEME'] # current theme being compressed
|
|
compressed = context['compressed'] # the compressed content
|
|
compressed_name = compressed['name'] # name of the compressed content
|
|
if compressed_name == 'angular_template_cache_preloads':
|
|
# The compressor has modified the angular template cache preloads
|
|
# which are cached in the 'default' Django cache. Fetch that cache.
|
|
cache = caches['default']
|
|
|
|
# generate the same key as used in _scripts.html when caching the
|
|
# preloads
|
|
key = make_template_fragment_key(
|
|
"angular",
|
|
['template_cache_preloads', theme]
|
|
)
|
|
|
|
# if template preloads have been cached, clear them
|
|
if cache.get(key):
|
|
cache.delete(key)
|
|
|
|
|
|
@register.filter(name='angular_escapes')
|
|
def angular_escapes(value):
|
|
"""Djangos 'escapejs' is too aggressive and inserts unicode. Provide
|
|
a basic filter to allow angular template content to be used within
|
|
javascript strings.
|
|
|
|
Args:
|
|
value: a string
|
|
|
|
Returns:
|
|
string with escaped values
|
|
"""
|
|
return value \
|
|
.replace('"', '\\"') \
|
|
.replace("'", "\\'") \
|
|
.replace("\n", "\\n") \
|
|
.replace("\r", "\\r")
|
|
|
|
|
|
@register.inclusion_tag('angular/angular_templates.html', takes_context=True)
|
|
def angular_templates(context):
|
|
"""For all static HTML templates, generate a dictionary of template
|
|
contents. If the template has been overridden by a theme, load the
|
|
override contents instead of the original HTML file. One use for
|
|
this is to pre-populate the angular template cache.
|
|
|
|
Args:
|
|
context: the context of the current Django template
|
|
|
|
Returns: an object containing
|
|
angular_templates: dictionary of angular template contents
|
|
- key is the template's static path,
|
|
- value is a string of HTML template contents
|
|
"""
|
|
template_paths = context['HORIZON_CONFIG']['external_templates']
|
|
all_theme_static_files = context['HORIZON_CONFIG']['theme_static_files']
|
|
this_theme_static_files = all_theme_static_files[context['THEME']]
|
|
template_overrides = this_theme_static_files['template_overrides']
|
|
angular_templates = {}
|
|
|
|
for relative_path in template_paths:
|
|
|
|
template_static_path = context['STATIC_URL'] + relative_path
|
|
|
|
# If the current theme overrides this template, use the theme
|
|
# content instead of the original file content
|
|
if relative_path in template_overrides:
|
|
relative_path = template_overrides[relative_path]
|
|
|
|
result = []
|
|
for finder in finders.get_finders():
|
|
result.extend(finder.find(relative_path, True))
|
|
path = result[-1]
|
|
try:
|
|
with open(path) as template_file:
|
|
angular_templates[template_static_path] = template_file.read()
|
|
except (OSError, IOError):
|
|
# Failed to read template, leave the template dictionary blank
|
|
# If the caller is using this dictionary to pre-populate a cache
|
|
# there will simply be no pre-loaded version for this template.
|
|
pass
|
|
|
|
return {
|
|
'angular_templates': angular_templates
|
|
}
|