Adds breadcrumb to resource browser.
Fixes bug #1037012 Change-Id: I09247ae2e30261989118c37de8ec33d90c8b4100
This commit is contained in:
parent
c00496e1d1
commit
156a368c63
@ -19,6 +19,7 @@ from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon.tables import DataTable
|
||||
from horizon.utils import html
|
||||
from .breadcrumb import Breadcrumb
|
||||
|
||||
|
||||
class ResourceBrowser(html.HTMLElement):
|
||||
@ -48,6 +49,18 @@ class ResourceBrowser(html.HTMLElement):
|
||||
This table class must set browser_table attribute in Meta to
|
||||
``"content"``.
|
||||
|
||||
.. attribute:: navigation_kwarg_name
|
||||
|
||||
This attribute represents the key of the navigatable items in the
|
||||
kwargs property of this browser's view.
|
||||
Defaults to ``"navigation_kwarg"``.
|
||||
|
||||
.. attribute:: content_kwarg_name
|
||||
|
||||
This attribute represents the key of the content items in the
|
||||
kwargs property of this browser's view.
|
||||
Defaults to ``"content_kwarg"``.
|
||||
|
||||
.. attribute:: template
|
||||
|
||||
String containing the template which should be used to render
|
||||
@ -57,20 +70,43 @@ class ResourceBrowser(html.HTMLElement):
|
||||
|
||||
The name of the context variable which will contain the browser when
|
||||
it is rendered. Defaults to ``"browser"``.
|
||||
|
||||
.. attribute:: has_breadcrumb
|
||||
|
||||
Indicates if the content table of the browser would have breadcrumb.
|
||||
Defaults to false.
|
||||
|
||||
.. attribute:: breadcrumb_template
|
||||
|
||||
This is a template used to render the breadcrumb.
|
||||
Defaults to ``"horizon/common/_breadcrumb.html"``.
|
||||
"""
|
||||
name = None
|
||||
verbose_name = None
|
||||
navigation_table_class = None
|
||||
content_table_class = None
|
||||
navigation_kwarg_name = "navigation_kwarg"
|
||||
content_kwarg_name = "content_kwarg"
|
||||
navigable_item_name = _("Navigation Item")
|
||||
template = "horizon/common/_resource_browser.html"
|
||||
context_var_name = "browser"
|
||||
has_breadcrumb = False
|
||||
breadcrumb_template = "horizon/common/_breadcrumb.html"
|
||||
breadcrumb_url = None
|
||||
|
||||
def __init__(self, request, tables_dict=None, attrs=None, **kwargs):
|
||||
super(ResourceBrowser, self).__init__()
|
||||
self.name = self.name or self.__class__.__name__
|
||||
self.verbose_name = self.verbose_name or self.name.title()
|
||||
self.request = request
|
||||
self.kwargs = kwargs
|
||||
self.has_breadcrumb = getattr(self, "has_breadcrumb")
|
||||
if self.has_breadcrumb:
|
||||
self.breadcrumb_template = getattr(self, "breadcrumb_template")
|
||||
self.breadcrumb_url = getattr(self, "breadcrumb_url")
|
||||
if not self.breadcrumb_url:
|
||||
raise ValueError("You must specify a breadcrumb_url "
|
||||
"if the has_breadcrumb is set to True.")
|
||||
self.attrs.update(attrs or {})
|
||||
self.check_table_class(self.content_table_class, "content_table_class")
|
||||
self.check_table_class(self.navigation_table_class,
|
||||
@ -91,6 +127,19 @@ class ResourceBrowser(html.HTMLElement):
|
||||
"""
|
||||
self.navigation_table = tables[self.navigation_table_class._meta.name]
|
||||
self.content_table = tables[self.content_table_class._meta.name]
|
||||
if self.has_breadcrumb:
|
||||
self.prepare_breadcrumb(tables)
|
||||
|
||||
def prepare_breadcrumb(self, tables):
|
||||
navigation_item = self.kwargs.get(self.navigation_kwarg_name)
|
||||
content_path = self.kwargs.get(self.content_kwarg_name)
|
||||
if self.has_breadcrumb and navigation_item and content_path:
|
||||
for table in tables.values():
|
||||
table.breadcrumb = Breadcrumb(self.request,
|
||||
self.breadcrumb_template,
|
||||
navigation_item,
|
||||
content_path,
|
||||
self.breadcrumb_url)
|
||||
|
||||
def render(self):
|
||||
browser_template = template.loader.get_template(self.template)
|
||||
|
48
horizon/browsers/breadcrumb.py
Normal file
48
horizon/browsers/breadcrumb.py
Normal file
@ -0,0 +1,48 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# 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 django import template
|
||||
|
||||
from horizon.utils import html
|
||||
|
||||
|
||||
class Breadcrumb(html.HTMLElement):
|
||||
def __init__(self, request, template, root,
|
||||
subfolder_path, url, attr=None):
|
||||
super(Breadcrumb, self).__init__()
|
||||
self.template = template
|
||||
self.request = request
|
||||
self.root = root
|
||||
self.subfolder_path = subfolder_path
|
||||
self.url = url
|
||||
self._subfolders = []
|
||||
|
||||
def get_subfolders(self):
|
||||
if self.subfolder_path and not self._subfolders:
|
||||
(parent, slash, folder) = self.subfolder_path.strip('/') \
|
||||
.rpartition('/')
|
||||
while folder:
|
||||
path = "%s%s%s/" % (parent, slash, folder)
|
||||
self._subfolders.insert(0, (folder, path))
|
||||
(parent, slash, folder) = parent.rpartition('/')
|
||||
return self._subfolders
|
||||
|
||||
def render(self):
|
||||
""" Renders the table using the template from the table options. """
|
||||
breadcrumb_template = template.loader.get_template(self.template)
|
||||
extra_context = {"breadcrumb": self}
|
||||
context = template.RequestContext(self.request, extra_context)
|
||||
return breadcrumb_template.render(context)
|
@ -14,8 +14,6 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from collections import defaultdict
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon.tables import MultiTableView
|
||||
@ -31,8 +29,8 @@ class ResourceBrowserView(MultiTableView):
|
||||
% self.__class__.__name__)
|
||||
self.table_classes = (self.browser_class.navigation_table_class,
|
||||
self.browser_class.content_table_class)
|
||||
super(ResourceBrowserView, self).__init__(*args, **kwargs)
|
||||
self.navigation_selection = False
|
||||
super(ResourceBrowserView, self).__init__(*args, **kwargs)
|
||||
|
||||
def get_browser(self):
|
||||
if not hasattr(self, "browser"):
|
||||
|
@ -31,3 +31,7 @@ class ContainerBrowser(browsers.ResourceBrowser):
|
||||
navigation_table_class = ContainersTable
|
||||
content_table_class = ObjectsTable
|
||||
navigable_item_name = _("Container")
|
||||
navigation_kwarg_name = "container_name"
|
||||
content_kwarg_name = "subfolder_path"
|
||||
has_breadcrumb = True
|
||||
breadcrumb_url = "horizon:nova:containers:index"
|
||||
|
@ -4,21 +4,7 @@
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
<h2>{% trans "Container" %}
|
||||
{% if subfolders %}
|
||||
: <a href="{% url horizon:nova:containers:index container_name|add:'/' %}">{{container_name}}</a>
|
||||
<small>/</small>
|
||||
{% elif container_name %}
|
||||
: {{container_name}}
|
||||
{% endif %}
|
||||
{% for subfolder, path in subfolders %}
|
||||
<small>
|
||||
{% if not forloop.last %}
|
||||
<a href="{% url horizon:nova:containers:index container_name|add:'/' path %}">
|
||||
{% endif %}{{ subfolder }}{% if not forloop.last %}</a> /{% endif %}
|
||||
</small>
|
||||
{% endfor %}
|
||||
</h2>
|
||||
<h2>{% trans "Containers" %}
|
||||
</div>
|
||||
{% endblock page_header %}
|
||||
|
||||
|
@ -361,7 +361,7 @@ class FilterAction(BaseAction):
|
||||
|
||||
def data_type_filter(self, table, data, filter_string):
|
||||
filtered_data = []
|
||||
for data_type in table.data_types:
|
||||
for data_type in table._meta.data_types:
|
||||
func_name = "filter_%s_data" % data_type
|
||||
filter_func = getattr(self, func_name, None)
|
||||
if not filter_func and not callable(filter_func):
|
||||
|
@ -881,6 +881,7 @@ class DataTable(object):
|
||||
self.kwargs = kwargs
|
||||
self._needs_form_wrapper = needs_form_wrapper
|
||||
self._no_data_message = self._meta.no_data_message
|
||||
self.breadcrumb = None
|
||||
|
||||
# Create a new set
|
||||
columns = []
|
||||
|
20
horizon/templates/horizon/common/_breadcrumb.html
Normal file
20
horizon/templates/horizon/common/_breadcrumb.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% load url from future %}
|
||||
{% load i18n %}
|
||||
{% with subfolders=breadcrumb.get_subfolders %}
|
||||
<ul class="breadcrumb">
|
||||
<li>
|
||||
Folder Path: <a href="{% url breadcrumb.url breadcrumb.root|add:'/' %}">{{ breadcrumb.root }}</a> <span class="divider">/</span>
|
||||
</li>
|
||||
{% for subfolder, path in subfolders %}
|
||||
<li>
|
||||
{% if not forloop.last %}
|
||||
<a href="{% url breadcrumb.url breadcrumb.root|add:'/' path %}">
|
||||
{% endif %}
|
||||
{{ subfolder }}
|
||||
{% if not forloop.last %}
|
||||
</a> <span class="divider">/</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endwith %}
|
@ -11,6 +11,13 @@
|
||||
{{ table.render_table_actions }}
|
||||
</th>
|
||||
</tr>
|
||||
{% if table.breadcrumb %}
|
||||
<tr>
|
||||
<td class="breadcrumb_td" colspan="{{ table.get_columns|length }}">
|
||||
{{ table.breadcrumb.render }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% if not table.is_browser_table %}
|
||||
<tr>
|
||||
{% for column in columns %}
|
||||
|
@ -1410,6 +1410,7 @@ label.log-length {
|
||||
/* ResourceBrowser style */
|
||||
#browser_wrapper {
|
||||
width: @browserWrapperWidth;
|
||||
min-width: 1000px;
|
||||
background-color: @grayLighter;
|
||||
border: @dataTableBorderWidth solid @dataTableBorderColor;
|
||||
.border-radius(4px);
|
||||
@ -1435,21 +1436,36 @@ label.log-length {
|
||||
float: left;
|
||||
}
|
||||
div.navigation_wrapper {
|
||||
z-index: 10;
|
||||
width: @navigationTableWidth;
|
||||
div.table_wrapper,
|
||||
thead th.table_header {
|
||||
border-right: 0 none;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
td.normal_column{
|
||||
td {
|
||||
&:first-child {
|
||||
border-left: 0 none;
|
||||
}
|
||||
&.breadcrumb_td {
|
||||
padding-right: 0px;
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
tfoot td {
|
||||
border-right: 0 none;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
ul.breadcrumb {
|
||||
padding-right: 0px;
|
||||
border-top-right-radius: 0px;
|
||||
border-bottom-right-radius: 0px;
|
||||
border-right: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
tbody td {
|
||||
background-color: @white;
|
||||
}
|
||||
}
|
||||
div.content_wrapper {
|
||||
width: @contentTableWidth;
|
||||
@ -1462,13 +1478,31 @@ label.log-length {
|
||||
&:last-child {
|
||||
border-right: 0 none;
|
||||
}
|
||||
&.breadcrumb_td {
|
||||
padding-left: 0px;
|
||||
}
|
||||
}
|
||||
tfoot td {
|
||||
border-left: 0 none;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
/* FIXME(Ke Wu): for now there are two breadcrumb tr in both table
|
||||
* and this one in the content table is hidden. This hack is made to
|
||||
* fix the alignment of two table, needs a better solution in the
|
||||
* future.
|
||||
*/
|
||||
ul.breadcrumb {
|
||||
padding-left: 0px;
|
||||
border-top-left-radius: 0px;
|
||||
border-bottom-left-radius: 0px;
|
||||
border-left: none;
|
||||
li {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
thead {
|
||||
tr th {
|
||||
border-bottom: none;
|
||||
@ -1484,15 +1518,14 @@ label.log-length {
|
||||
height: @tdHeight;
|
||||
padding: @actionsColumnPadding;
|
||||
}
|
||||
}
|
||||
&.table-striped {
|
||||
tbody {
|
||||
tr:nth-child(even) td,
|
||||
tr:nth-child(even) th {
|
||||
background-color: @white;
|
||||
td.actions_column {
|
||||
position: static;
|
||||
}
|
||||
}
|
||||
}
|
||||
.breadcrumb{
|
||||
padding: 6px;
|
||||
margin: 0 0 1px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user