~ubuntu-branches/ubuntu/precise/horizon/precise-updates

« back to all changes in this revision

Viewing changes to horizon/tabs/base.py

  • Committer: Package Import Robot
  • Author(s): Chuck Short
  • Date: 2012-03-02 12:11:59 UTC
  • mfrom: (1.1.8)
  • Revision ID: package-import@ubuntu.com-20120302121159-65b88lcl4slve26i
Tags: 2012.1~e4-0ubuntu1
* New upstream version.
* debian/rules: Update due to upstream build changes.
* debian/control: Update standards-version.
* debian/patches/openstack-config-settings.patch: Dropped
* debian/patches/fix-dashboard-django-wsgi.patch: Refreshed
* debian/patches/fix-dashboard-manage.patch: Refreshed
* debian/openstack-dashboard.install: Update due to upstream build changes.
* debian/dashboard: Update to upstream build changes.
* debian/pydist-overrides: Dont try to install python-django-nose-selenium.
* debian/openstack-dashboard.install: Add missing config files.
* debian/rules: Fix broken settings.py
* debian/patches/pkg-setup.patch: Copy missing templates, shameously
  taken from debian
* debian/patches/fix-broken-tarbll.patch: Add missing files.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
 
2
 
 
3
# Copyright 2012 Nebula, Inc.
 
4
#
 
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
 
8
#
 
9
#         http://www.apache.org/licenses/LICENSE-2.0
 
10
#
 
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
 
15
#    under the License.
 
16
 
 
17
from django.template import TemplateSyntaxError
 
18
from django.template.loader import render_to_string
 
19
from django.utils.datastructures import SortedDict
 
20
 
 
21
from horizon.utils import html
 
22
 
 
23
SEPARATOR = "__"
 
24
CSS_TAB_GROUP_CLASSES = ["nav", "nav-tabs", "ajax-tabs"]
 
25
CSS_ACTIVE_TAB_CLASSES = ["active"]
 
26
CSS_DISABLED_TAB_CLASSES = ["disabled"]
 
27
 
 
28
 
 
29
class TabGroup(html.HTMLElement):
 
30
    """
 
31
    A container class which knows how to manage and render
 
32
    :class:`~horizon.tabs.Tab` objects.
 
33
 
 
34
    .. attribute:: slug
 
35
 
 
36
        The URL slug and pseudo-unique identifier for this tab group.
 
37
 
 
38
    .. attribute:: template_name
 
39
 
 
40
        The name of the template which will be used to render this tab group.
 
41
        Default: ``"horizon/common/_tab_group.html"``
 
42
 
 
43
    .. attribute:: param_name
 
44
 
 
45
        The name of the GET request parameter which will be used when
 
46
        requesting specific tab data. Default: ``tab``.
 
47
 
 
48
    .. attribute:: classes
 
49
 
 
50
        A list of CSS classes which should be displayed on this tab group.
 
51
 
 
52
    .. attribute:: attrs
 
53
 
 
54
        A dictionary of HTML attributes which should be rendered into the
 
55
        markup for this tab group.
 
56
 
 
57
    .. attribute:: selected
 
58
 
 
59
        Read-only property which is set to the instance of the
 
60
        currently-selected tab if there is one, otherwise ``None``.
 
61
 
 
62
    .. attribute:: active
 
63
 
 
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.
 
67
    """
 
68
    slug = None
 
69
    template_name = "horizon/common/_tab_group.html"
 
70
    param_name = 'tab'
 
71
    _selected = None
 
72
    _active = None
 
73
 
 
74
    @property
 
75
    def selected(self):
 
76
        return self._selected
 
77
 
 
78
    @property
 
79
    def active(self):
 
80
        return self._active
 
81
 
 
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.'
 
86
                                      % self.__class__)
 
87
        self.request = request
 
88
        self.kwargs = kwargs
 
89
        tab_instances = []
 
90
        for tab in self.tabs:
 
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()
 
95
 
 
96
    def __repr__(self):
 
97
        return "<%s: %s>" % (self.__class__.__name__, self.slug)
 
98
 
 
99
    def get_id(self):
 
100
        """
 
101
        Returns the id for this tab group. Defaults to the value of the tab
 
102
        group's :attr:`horizon.tabs.Tab.slug`.
 
103
        """
 
104
        return self.slug
 
105
 
 
106
    def get_default_classes(self):
 
107
        """
 
108
        Returns a list of the default classes for the tab group. Defaults to
 
109
        ``["nav", "nav-tabs", "ajax-tabs"]``.
 
110
        """
 
111
        default_classes = super(TabGroup, self).get_default_classes()
 
112
        default_classes.extend(CSS_TAB_GROUP_CLASSES)
 
113
        return default_classes
 
114
 
 
115
    def tabs_not_available(self):
 
116
        """
 
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.
 
120
        """
 
121
        pass
 
122
 
 
123
    def _set_active_tab(self):
 
124
        marked_active = None
 
125
 
 
126
        # See if we have a selected tab via the GET parameter.
 
127
        tab = self.get_selected_tab()
 
128
        if tab:
 
129
            tab._active = True
 
130
            self._active = tab
 
131
            marked_active = tab
 
132
 
 
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:
 
136
                tab._active = True
 
137
                self._active = tab
 
138
                marked_active = True
 
139
            elif tab == marked_active:
 
140
                continue
 
141
            else:
 
142
                tab._active = False
 
143
 
 
144
        return marked_active
 
145
 
 
146
    def render(self):
 
147
        """ Renders the HTML output for this tab group. """
 
148
        return render_to_string(self.template_name, {"tab_group": self})
 
149
 
 
150
    def get_tabs(self):
 
151
        """ Returns a list of the allowed tabs for this tab group. """
 
152
        return filter(lambda tab: tab._allowed, self._tabs.values())
 
153
 
 
154
    def get_tab(self, tab_name, allow_disabled=False):
 
155
        """ Returns a specific tab from this tab group.
 
156
 
 
157
        If the tab is not allowed or not enabled this method returns ``None``.
 
158
 
 
159
        If the tab is disabled but you wish to return it anyway, you can pass
 
160
        ``True`` to the allow_disabled argument.
 
161
        """
 
162
        tab = self._tabs.get(tab_name, None)
 
163
        if tab and tab._allowed and (tab._enabled or allow_disabled):
 
164
            return tab
 
165
        return None
 
166
 
 
167
    def get_selected_tab(self):
 
168
        """ Returns the tab specific by the GET request parameter.
 
169
 
 
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.
 
173
        """
 
174
        selected = self.request.GET.get(self.param_name, None)
 
175
        if selected:
 
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
 
180
 
 
181
 
 
182
class Tab(html.HTMLElement):
 
183
    """
 
184
    A reusable interface for constructing a tab within a
 
185
    :class:`~horizon.tabs.TabGroup`.
 
186
 
 
187
    .. attribute:: name
 
188
 
 
189
        The display name for the tab which will be rendered as the text for
 
190
        the tab element in the HTML. Required.
 
191
 
 
192
    .. attribute:: slug
 
193
 
 
194
        The URL slug and id attribute for the tab. This should be unique for
 
195
        a given tab group. Required.
 
196
 
 
197
    .. attribute:: preload
 
198
 
 
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``.
 
202
 
 
203
    .. attribute:: classes
 
204
 
 
205
        A list of CSS classes which should be displayed on this tab.
 
206
 
 
207
    .. attribute:: attrs
 
208
 
 
209
        A dictionary of HTML attributes which should be rendered into the
 
210
        markup for this tab.
 
211
 
 
212
    .. attribute:: load
 
213
 
 
214
        Read-only access to determine whether or not this tab's data should
 
215
        be loaded immediately.
 
216
    """
 
217
    name = None
 
218
    slug = None
 
219
    preload = True
 
220
    _active = None
 
221
 
 
222
    def __init__(self, tab_group, request):
 
223
        super(Tab, self).__init__()
 
224
        # Priority: constructor, class-defined, fallback
 
225
        if not self.name:
 
226
            raise ValueError("%s must have a name." % self.__class__.__name__)
 
227
        self.name = unicode(self.name)  # Force unicode.
 
228
        if not self.slug:
 
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)
 
234
 
 
235
    def __repr__(self):
 
236
        return "<%s: %s>" % (self.__class__.__name__, self.slug)
 
237
 
 
238
    def is_active(self):
 
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()
 
242
        return self._active
 
243
 
 
244
    @property
 
245
    def load(self):
 
246
        load_preloaded = self.preload or self.is_active()
 
247
        return load_preloaded and self._allowed and self._enabled
 
248
 
 
249
    def render(self):
 
250
        """
 
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.
 
253
 
 
254
        If :attr:`~horizon.tabs.Tab.preload` is ``False`` and ``force_load``
 
255
        is not ``True``, or
 
256
        either :meth:`~horizon.tabs.Tab.allowed` or
 
257
        :meth:`~horizon.tabs.Tab.enabled` returns ``False`` this method will
 
258
        return an empty string.
 
259
        """
 
260
        if not self.load:
 
261
            return ''
 
262
        try:
 
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)
 
267
 
 
268
    def get_id(self):
 
269
        """
 
270
        Returns the id for this tab. Defaults to
 
271
        ``"{{ tab_group.slug }}__{{ tab.slug }}"``.
 
272
        """
 
273
        return SEPARATOR.join([self.tab_group.slug, self.slug])
 
274
 
 
275
    def get_default_classes(self):
 
276
        """
 
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:
 
280
 
 
281
        If the tab is the active tab for the tab group, in which
 
282
        the class ``"active"`` will be added.
 
283
 
 
284
        If the tab is not enabled, the classes the class ``"disabled"``
 
285
        will be added.
 
286
        """
 
287
        default_classes = super(Tab, self).get_default_classes()
 
288
        if self.is_active():
 
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
 
293
 
 
294
    def get_template_name(self, request):
 
295
        """
 
296
        Returns the name of the template to be used for rendering this tab.
 
297
 
 
298
        By default it returns the value of the ``template_name`` attribute
 
299
        on the ``Tab`` class.
 
300
        """
 
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
 
306
 
 
307
    def get_context_data(self, request):
 
308
        """
 
309
        This method should return a dictionary of context data used to render
 
310
        the tab. Required.
 
311
        """
 
312
        raise NotImplementedError("%s needs to define a get_context_data "
 
313
                                  "method." % self.__class__.__name__)
 
314
 
 
315
    def enabled(self, request):
 
316
        """
 
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).
 
319
 
 
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.
 
322
 
 
323
        The default behavior is to return ``True`` for all cases.
 
324
        """
 
325
        return True
 
326
 
 
327
    def allowed(self, request):
 
328
        """
 
329
        Determines whether or not the tab is displayed.
 
330
 
 
331
        Tab instances can override this method to specify conditions under
 
332
        which this tab should not be shown at all by returning ``False``.
 
333
 
 
334
        The default behavior is to return ``True`` for all cases.
 
335
        """
 
336
        return True