deb-horizon/horizon/tabs/views.py
Gabriel Hurley 33a0a2e401 Ensures tab data is preloaded. With tests! Fixes bug 964317.
Change-Id: I55ad1e0b9b836793ee2816cec18160b223f80462
2012-03-24 20:53:28 -07:00

142 lines
5.6 KiB
Python

from django import http
from django.views import generic
from horizon import exceptions
from horizon import tables
from .base import TableTab
class TabView(generic.TemplateView):
"""
A generic class-based view for displaying a :class:`horizon.tabs.TabGroup`.
This view handles selecting specific tabs and deals with AJAX requests
gracefully.
.. attribute:: tab_group_class
The only required attribute for ``TabView``. It should be a class which
inherits from :class:`horizon.tabs.TabGroup`.
"""
tab_group_class = None
_tab_group = None
def __init__(self):
if not self.tab_group_class:
raise AttributeError("You must set the tab_group_class attribute "
"on %s." % self.__class__.__name__)
def get_tabs(self, request, **kwargs):
""" Returns the initialized tab group for this view. """
if self._tab_group is None:
self._tab_group = self.tab_group_class(request, **kwargs)
return self._tab_group
def get_context_data(self, **kwargs):
""" Adds the ``tab_group`` variable to the context data. """
context = super(TabView, self).get_context_data(**kwargs)
try:
tab_group = self.get_tabs(self.request, **kwargs)
context["tab_group"] = tab_group
# Make sure our data is pre-loaded to capture errors.
context["tab_group"].load_tab_data()
except:
exceptions.handle(self.request)
return context
def handle_tabbed_response(self, tab_group, context):
"""
Sends back an AJAX-appropriate response for the tab group if
required, otherwise renders the response as normal.
"""
if self.request.is_ajax():
if tab_group.selected:
return http.HttpResponse(tab_group.selected.render())
else:
return http.HttpResponse(tab_group.render())
return self.render_to_response(context)
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.handle_tabbed_response(context["tab_group"], context)
def render_to_response(self, *args, **kwargs):
response = super(TabView, self).render_to_response(*args, **kwargs)
# Because Django's TemplateView uses the TemplateResponse class
# to provide deferred rendering (which is usually helpful), if
# a tab group raises an Http302 redirect (from exceptions.handle for
# example) the exception is actually raised *after* the final pass
# of the exception-handling middleware.
response.render()
return response
class TabbedTableView(tables.MultiTableMixin, TabView):
def __init__(self, *args, **kwargs):
super(TabbedTableView, self).__init__(*args, **kwargs)
self.table_classes = []
self._table_dict = {}
def load_tabs(self):
"""
Loads the tab group, and compiles the table instances for each
table attached to any :class:`horizon.tabs.TableTab` instances on
the tab group. This step is necessary before processing any
tab or table actions.
"""
tab_group = self.get_tabs(self.request, **self.kwargs)
tabs = tab_group.get_tabs()
for tab in [t for t in tabs if issubclass(t.__class__, TableTab)]:
self.table_classes.extend(tab.table_classes)
for table in tab._tables.values():
self._table_dict[table._meta.name] = {'table': table,
'tab': tab}
def get_tables(self):
""" A no-op on this class. Tables are handled at the tab level. """
# Override the base class implementation so that the MultiTableMixin
# doesn't freak out. We do the processing at the TableTab level.
return {}
def handle_table(self, table_dict):
"""
For the given dict containing a ``DataTable`` and a ``TableTab``
instance, it loads the table data for that tab and calls the
table's :meth:`~horizon.tables.DataTable.maybe_handle` method. The
return value will be the result of ``maybe_handle``.
"""
table = table_dict['table']
tab = table_dict['tab']
tab.load_table_data()
table_name = table._meta.name
tab._tables[table_name]._meta.has_more_data = self.has_more_data(table)
handled = tab._tables[table_name].maybe_handle()
return handled
def get(self, request, *args, **kwargs):
self.load_tabs()
# Gather our table instances. It's important that they're the
# actual instances and not the classes!
table_instances = [t['table'] for t in self._table_dict.values()]
# Early out before any tab or table data is loaded
for table in table_instances:
preempted = table.maybe_preempt()
if preempted:
return preempted
# If we have an action, determine if it belongs to one of our tables.
# We don't iterate through all of the tables' maybes_handle
# methods; just jump to the one that's got the matching name.
table_name, action, obj_id = tables.DataTable.check_handler(request)
if table_name in self._table_dict:
handled = self.handle_table(self._table_dict[table_name])
if handled:
return handled
context = self.get_context_data(**kwargs)
return self.handle_tabbed_response(context["tab_group"], context)
def post(self, request, *args, **kwargs):
# GET and POST handling are the same
return self.get(request, *args, **kwargs)