f5f371655e
Barcharts were using a complex D3 mechanism to draw very very simple markup that can be achieved using a simple Bootstrap Stacked Progress Bar. A templatetag was added to aid in markup reuse. Also, a 'minifyspace' template helper was also added that enables us to make templates that have excessive attributes to be more readable, while not affecting the rendered HTML on the page. By moving the logic to simple markup, the rendering of the barcharts are now immediate on the page, instead of requiring Horizon Init functions to run and draw. This immediate render removes the first animation on the Quota bars, but it retains animations driven by user interaction with the forms. It was hoped that horizon.d3barchart.js could be completely removed, but it is currently being used by horizon.d3linechart.js. This will be addressed soon, but was outside of the scope of this patch. Much of the markup on the Quota pages was cleaned up and made simpler to use existing style as well as replacing improper usage of <strong> Summary of Improvements: * Immediate Render time for Bar Charts * Bar Charts now Inherit from theme, and use progress bar styles * 'minifyspace' was added to increase readability to templates * bs_progress_bar templatetag was added to facilitate code reuse * Markup for Quota Bar pages refactored Partially-Implements: blueprint horizon-theme-css-reorg Partially-Implements: blueprint bootstrap-html-standards Change-Id: I16a1e3955d3a4fded0f2ea6a87f5e7c2f630185d
233 lines
7.7 KiB
Python
233 lines
7.7 KiB
Python
# Copyright 2012 Nebula, 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 __future__ import absolute_import
|
|
|
|
from collections import OrderedDict
|
|
from horizon.contrib import bootstrap_datepicker
|
|
|
|
from django.conf import settings
|
|
from django import template
|
|
from django.template import Node
|
|
from django.utils.encoding import force_text
|
|
from django.utils import translation
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from horizon.base import Horizon # noqa
|
|
from horizon import conf
|
|
|
|
|
|
register = template.Library()
|
|
|
|
|
|
class MinifiedNode(Node):
|
|
def __init__(self, nodelist):
|
|
self.nodelist = nodelist
|
|
|
|
def render(self, context):
|
|
return ' '.join(
|
|
force_text(self.nodelist.render(context).strip()).split()
|
|
)
|
|
|
|
|
|
@register.filter
|
|
def has_permissions(user, component):
|
|
"""Checks if the given user meets the permissions requirements for
|
|
the component.
|
|
"""
|
|
return user.has_perms(getattr(component, 'permissions', set()))
|
|
|
|
|
|
@register.filter
|
|
def has_permissions_on_list(components, user):
|
|
return [component for component
|
|
in components if has_permissions(user, component)]
|
|
|
|
|
|
@register.inclusion_tag('horizon/_sidebar.html', takes_context=True)
|
|
def horizon_nav(context):
|
|
if 'request' not in context:
|
|
return {}
|
|
current_dashboard = context['request'].horizon.get('dashboard', None)
|
|
current_panel_group = None
|
|
current_panel = context['request'].horizon.get('panel', None)
|
|
dashboards = []
|
|
for dash in Horizon.get_dashboards():
|
|
panel_groups = dash.get_panel_groups()
|
|
non_empty_groups = []
|
|
for group in panel_groups.values():
|
|
allowed_panels = []
|
|
for panel in group:
|
|
if (callable(panel.nav) and panel.nav(context) and
|
|
panel.can_access(context)):
|
|
allowed_panels.append(panel)
|
|
elif (not callable(panel.nav) and panel.nav and
|
|
panel.can_access(context)):
|
|
allowed_panels.append(panel)
|
|
if panel == current_panel:
|
|
current_panel_group = group.slug
|
|
if allowed_panels:
|
|
non_empty_groups.append((group, allowed_panels))
|
|
if (callable(dash.nav) and dash.nav(context) and
|
|
dash.can_access(context)):
|
|
dashboards.append((dash, OrderedDict(non_empty_groups)))
|
|
elif (not callable(dash.nav) and dash.nav and
|
|
dash.can_access(context)):
|
|
dashboards.append((dash, OrderedDict(non_empty_groups)))
|
|
return {'components': dashboards,
|
|
'user': context['request'].user,
|
|
'current': current_dashboard,
|
|
'current_panel_group': current_panel_group,
|
|
'current_panel': current_panel.slug if current_panel else '',
|
|
'request': context['request']}
|
|
|
|
|
|
@register.inclusion_tag('horizon/_nav_list.html', takes_context=True)
|
|
def horizon_main_nav(context):
|
|
"""Generates top-level dashboard navigation entries."""
|
|
if 'request' not in context:
|
|
return {}
|
|
current_dashboard = context['request'].horizon.get('dashboard', None)
|
|
dashboards = []
|
|
for dash in Horizon.get_dashboards():
|
|
if dash.can_access(context):
|
|
if callable(dash.nav) and dash.nav(context):
|
|
dashboards.append(dash)
|
|
elif dash.nav:
|
|
dashboards.append(dash)
|
|
return {'components': dashboards,
|
|
'user': context['request'].user,
|
|
'current': current_dashboard,
|
|
'request': context['request']}
|
|
|
|
|
|
@register.inclusion_tag('horizon/_subnav_list.html', takes_context=True)
|
|
def horizon_dashboard_nav(context):
|
|
"""Generates sub-navigation entries for the current dashboard."""
|
|
if 'request' not in context:
|
|
return {}
|
|
dashboard = context['request'].horizon['dashboard']
|
|
panel_groups = dashboard.get_panel_groups()
|
|
non_empty_groups = []
|
|
|
|
for group in panel_groups.values():
|
|
allowed_panels = []
|
|
for panel in group:
|
|
if (callable(panel.nav) and panel.nav(context) and
|
|
panel.can_access(context)):
|
|
allowed_panels.append(panel)
|
|
elif (not callable(panel.nav) and panel.nav and
|
|
panel.can_access(context)):
|
|
allowed_panels.append(panel)
|
|
if allowed_panels:
|
|
if group.name is None:
|
|
non_empty_groups.append((dashboard.name, allowed_panels))
|
|
else:
|
|
non_empty_groups.append((group.name, allowed_panels))
|
|
|
|
return {'components': OrderedDict(non_empty_groups),
|
|
'user': context['request'].user,
|
|
'current': context['request'].horizon['panel'].slug,
|
|
'request': context['request']}
|
|
|
|
|
|
@register.filter
|
|
def quota(val, units=None):
|
|
if val == float("inf"):
|
|
return _("(No Limit)")
|
|
elif units is not None:
|
|
return "%s %s %s" % (val, force_text(units),
|
|
force_text(_("Available")))
|
|
else:
|
|
return "%s %s" % (val, force_text(_("Available")))
|
|
|
|
|
|
@register.filter
|
|
def quotainf(val, units=None):
|
|
if val == float("inf"):
|
|
return '-1'
|
|
elif units is not None:
|
|
return "%s %s" % (val, units)
|
|
else:
|
|
return val
|
|
|
|
|
|
@register.simple_tag
|
|
def quotapercent(used, limit):
|
|
if used >= limit or limit == 0:
|
|
return 100
|
|
elif limit == float("inf"):
|
|
return '[%s, true]' % used
|
|
else:
|
|
return round((float(used) / float(limit)) * 100)
|
|
|
|
|
|
class JSTemplateNode(template.Node):
|
|
"""Helper node for the ``jstemplate`` template tag."""
|
|
def __init__(self, nodelist):
|
|
self.nodelist = nodelist
|
|
|
|
def render(self, context,):
|
|
output = self.nodelist.render(context)
|
|
output = output.replace('[[[', '{{{').replace(']]]', '}}}')
|
|
output = output.replace('[[', '{{').replace(']]', '}}')
|
|
output = output.replace('[%', '{%').replace('%]', '%}')
|
|
return output
|
|
|
|
|
|
@register.tag
|
|
def jstemplate(parser, token):
|
|
"""Replaces ``[[[`` and ``]]]`` with ``{{{`` and ``}}}``,
|
|
``[[`` and ``]]`` with ``{{`` and ``}}`` and
|
|
``[%`` and ``%]`` with ``{%`` and ``%}`` to avoid conflicts
|
|
with Django's template engine when using any of the Mustache-based
|
|
templating libraries.
|
|
"""
|
|
nodelist = parser.parse(('endjstemplate',))
|
|
parser.delete_first_token()
|
|
return JSTemplateNode(nodelist)
|
|
|
|
|
|
@register.assignment_tag
|
|
def load_config():
|
|
return conf
|
|
|
|
|
|
@register.assignment_tag
|
|
def datepicker_locale():
|
|
locale_mapping = getattr(settings, 'DATEPICKER_LOCALES',
|
|
bootstrap_datepicker.LOCALE_MAPPING)
|
|
return locale_mapping.get(translation.get_language(), 'en')
|
|
|
|
|
|
@register.tag
|
|
def minifyspace(parser, token):
|
|
"""Removes whitespace including tab and newline characters. Do not use this
|
|
if you are using a <pre> tag
|
|
Example usage::
|
|
{% minifyspace %}
|
|
<p>
|
|
<a title="foo"
|
|
href="foo/">
|
|
Foo
|
|
</a>
|
|
</p>
|
|
{% endminifyspace %}
|
|
This example would return this HTML::
|
|
<p><a title="foo" href="foo/">Foo</a></p>
|
|
"""
|
|
nodelist = parser.parse(('endminifyspace',))
|
|
parser.delete_first_token()
|
|
return MinifiedNode(nodelist)
|