1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2012 Nebula, Inc.
5
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6
# not use this file except in compliance with the License. You may obtain
7
# a copy of the License at
9
# http://www.apache.org/licenses/LICENSE-2.0
11
# Unless required by applicable law or agreed to in writing, software
12
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14
# License for the specific language governing permissions and limitations
17
from django.template import TemplateSyntaxError
18
from django.template.loader import render_to_string
19
from django.utils.datastructures import SortedDict
21
from horizon.utils import html
24
CSS_TAB_GROUP_CLASSES = ["nav", "nav-tabs", "ajax-tabs"]
25
CSS_ACTIVE_TAB_CLASSES = ["active"]
26
CSS_DISABLED_TAB_CLASSES = ["disabled"]
29
class TabGroup(html.HTMLElement):
31
A container class which knows how to manage and render
32
:class:`~horizon.tabs.Tab` objects.
36
The URL slug and pseudo-unique identifier for this tab group.
38
.. attribute:: template_name
40
The name of the template which will be used to render this tab group.
41
Default: ``"horizon/common/_tab_group.html"``
43
.. attribute:: param_name
45
The name of the GET request parameter which will be used when
46
requesting specific tab data. Default: ``tab``.
48
.. attribute:: classes
50
A list of CSS classes which should be displayed on this tab group.
54
A dictionary of HTML attributes which should be rendered into the
55
markup for this tab group.
57
.. attribute:: selected
59
Read-only property which is set to the instance of the
60
currently-selected tab if there is one, otherwise ``None``.
64
Read-only property which is set to the value of the current active tab.
65
This may not be the same as the value of ``selected`` if no
66
specific tab was requested via the ``GET`` parameter.
69
template_name = "horizon/common/_tab_group.html"
82
def __init__(self, request, **kwargs):
83
super(TabGroup, self).__init__()
84
if not hasattr(self, "tabs"):
85
raise NotImplementedError('%s must declare a "tabs" attribute.'
87
self.request = request
91
tab_instances.append((tab.slug, tab(self, request)))
92
self._tabs = SortedDict(tab_instances)
93
if not self._set_active_tab():
94
self.tabs_not_available()
97
return "<%s: %s>" % (self.__class__.__name__, self.slug)
101
Returns the id for this tab group. Defaults to the value of the tab
102
group's :attr:`horizon.tabs.Tab.slug`.
106
def get_default_classes(self):
108
Returns a list of the default classes for the tab group. Defaults to
109
``["nav", "nav-tabs", "ajax-tabs"]``.
111
default_classes = super(TabGroup, self).get_default_classes()
112
default_classes.extend(CSS_TAB_GROUP_CLASSES)
113
return default_classes
115
def tabs_not_available(self):
117
In the event that no tabs are either allowed or enabled, this method
118
is the fallback handler. By default it's a no-op, but it exists
119
to make redirecting or raising exceptions possible for subclasses.
123
def _set_active_tab(self):
126
# See if we have a selected tab via the GET parameter.
127
tab = self.get_selected_tab()
133
# Iterate through to mark them all accordingly.
134
for tab in self._tabs.values():
135
if tab._allowed and tab._enabled and not marked_active:
139
elif tab == marked_active:
147
""" Renders the HTML output for this tab group. """
148
return render_to_string(self.template_name, {"tab_group": self})
151
""" Returns a list of the allowed tabs for this tab group. """
152
return filter(lambda tab: tab._allowed, self._tabs.values())
154
def get_tab(self, tab_name, allow_disabled=False):
155
""" Returns a specific tab from this tab group.
157
If the tab is not allowed or not enabled this method returns ``None``.
159
If the tab is disabled but you wish to return it anyway, you can pass
160
``True`` to the allow_disabled argument.
162
tab = self._tabs.get(tab_name, None)
163
if tab and tab._allowed and (tab._enabled or allow_disabled):
167
def get_selected_tab(self):
168
""" Returns the tab specific by the GET request parameter.
170
In the event that there is no GET request parameter, the value
171
of the query parameter is invalid, or the tab is not allowed/enabled,
172
the return value of this function is None.
174
selected = self.request.GET.get(self.param_name, None)
176
tab_group, tab_name = selected.split(SEPARATOR)
177
if tab_group == self.get_id():
178
self._selected = self.get_tab(tab_name)
179
return self._selected
182
class Tab(html.HTMLElement):
184
A reusable interface for constructing a tab within a
185
:class:`~horizon.tabs.TabGroup`.
189
The display name for the tab which will be rendered as the text for
190
the tab element in the HTML. Required.
194
The URL slug and id attribute for the tab. This should be unique for
195
a given tab group. Required.
197
.. attribute:: preload
199
Determines whether the contents of the tab should be rendered into
200
the page's HTML when the tab group is rendered, or whether it should
201
be loaded dynamically when the tab is selected. Default: ``True``.
203
.. attribute:: classes
205
A list of CSS classes which should be displayed on this tab.
209
A dictionary of HTML attributes which should be rendered into the
214
Read-only access to determine whether or not this tab's data should
215
be loaded immediately.
222
def __init__(self, tab_group, request):
223
super(Tab, self).__init__()
224
# Priority: constructor, class-defined, fallback
226
raise ValueError("%s must have a name." % self.__class__.__name__)
227
self.name = unicode(self.name) # Force unicode.
229
raise ValueError("%s must have a slug." % self.__class__.__name__)
230
self.request = request
231
self.tab_group = tab_group
232
self._allowed = self.allowed(request)
233
self._enabled = self.enabled(request)
236
return "<%s: %s>" % (self.__class__.__name__, self.slug)
239
""" Method to access whether or not this tab is the active tab. """
240
if self._active is None:
241
self.tab_group._set_active_tab()
246
load_preloaded = self.preload or self.is_active()
247
return load_preloaded and self._allowed and self._enabled
251
Renders the tab to HTML using the :meth:`~horizon.tabs.Tab.get_data`
252
method and the :meth:`~horizon.tabs.Tab.get_template_name` method.
254
If :attr:`~horizon.tabs.Tab.preload` is ``False`` and ``force_load``
256
either :meth:`~horizon.tabs.Tab.allowed` or
257
:meth:`~horizon.tabs.Tab.enabled` returns ``False`` this method will
258
return an empty string.
263
context = self.get_context_data(self.request)
264
except Exception as exc:
265
raise TemplateSyntaxError(exc)
266
return render_to_string(self.get_template_name(self.request), context)
270
Returns the id for this tab. Defaults to
271
``"{{ tab_group.slug }}__{{ tab.slug }}"``.
273
return SEPARATOR.join([self.tab_group.slug, self.slug])
275
def get_default_classes(self):
277
Returns a list of the default classes for the tab. Defaults to
278
and empty list (``[]``), however additional classes may be added
279
depending on the state of the tab as follows:
281
If the tab is the active tab for the tab group, in which
282
the class ``"active"`` will be added.
284
If the tab is not enabled, the classes the class ``"disabled"``
287
default_classes = super(Tab, self).get_default_classes()
289
default_classes.extend(CSS_ACTIVE_TAB_CLASSES)
290
if not self._enabled:
291
default_classes.extend(CSS_DISABLED_TAB_CLASSES)
292
return default_classes
294
def get_template_name(self, request):
296
Returns the name of the template to be used for rendering this tab.
298
By default it returns the value of the ``template_name`` attribute
299
on the ``Tab`` class.
301
if not hasattr(self, "template_name"):
302
raise AttributeError("%s must have a template_name attribute or "
303
"override the get_template_name method."
304
% self.__class__.__name__)
305
return self.template_name
307
def get_context_data(self, request):
309
This method should return a dictionary of context data used to render
312
raise NotImplementedError("%s needs to define a get_context_data "
313
"method." % self.__class__.__name__)
315
def enabled(self, request):
317
Determines whether or not the tab should be accessible
318
(e.g. be rendered into the HTML on load and respond to a click event).
320
If a tab returns ``False`` from ``enabled`` it will ignore the value
321
of ``preload`` and only render the HTML of the tab after being clicked.
323
The default behavior is to return ``True`` for all cases.
327
def allowed(self, request):
329
Determines whether or not the tab is displayed.
331
Tab instances can override this method to specify conditions under
332
which this tab should not be shown at all by returning ``False``.
334
The default behavior is to return ``True`` for all cases.