=== modified file 'lava/utils/data_tables/tables.py'
--- lava/utils/data_tables/tables.py	2012-04-20 19:58:36 +0000
+++ lava/utils/data_tables/tables.py	2012-05-29 01:33:39 +0000
@@ -18,9 +18,13 @@
 
 """Tables designed to be used with the DataTables jQuery plug in.
 
-There are just three steps to using this:
-
-1) Define the table::
+It is expected that most tables will be backed onto database queries, but it
+is possible to create tables backed onto regular Python instances.
+
+There are just three steps to creating a database-backed table:
+
+1) Define the table, which means the columns and the queryset that supplies
+   them with data::
 
     class BookTable(DataTablesTable):
         author = Column()
@@ -33,6 +37,10 @@
     def book_table_json(request):
         return BookTable.json(request)
 
+   Don't forget urls.py:
+
+    url(r'^book_table_json$', 'book_table_json'),
+
 3) Include the table in the view for the page you are building::
 
     def book_list(request):
@@ -85,9 +93,15 @@
 in the json view need to be consistent, many of the options that you can pass
 to Table's __init__ are not available for DataTablesTable.  In practice this
 means that you need a DataTablesTable subclass for each different table.
+
+If you want to create a DataTablesTable based table, just pass a sequence to
+the data= argument of the constructor (and do not pass anything for the source
+argument) and supply the table instance to {% render_table %} (no need for a
+get_queryset method or a json view in this case).  The 'params' argument in
+this case, if passed, is stored on the instance where it can be accessed if
+needed when rendering a column.
 """
 
-from abc import ABCMeta, abstractmethod
 import simplejson
 
 from django.template import RequestContext
@@ -99,48 +113,54 @@
 from lava.utils.data_tables.backends import TableBackend
 
 
-class MetaTable(ABCMeta, Table.__metaclass__):
-    pass
-
-
 class DataTablesTable(Table):
     """A table designed to be used with the DataTables jQuery plug in.
     """
 
-    __metaclass__ = MetaTable
-
     def __init__(self, id, source=None, params=(), sortable=None,
-                 empty_text=None, attrs=None, template=None):
+                 empty_text=None, attrs=None, template=None, data=None):
         """Initialize the table.
 
         Options that Table supports that affect the data presented are not
         supported in this subclass.  Extra paramters are:
 
         :param id: The id of the table in the resulting HTML.  You just need
-            to provide somethign that will be unique in the generated page.
+            to provide something that will be unique in the generated page.
         :param source: The URL to get json data from.
         :param params: A tuple of arguments to pass to the get_queryset()
             method.
+        :param data: The data to base the table on, if any.
         """
+        if data is not None:
+            if source is not None or self.source is not None:
+                raise AssertionError(
+                    "Do not specify both data and source when building a "
+                    "DataTablesTable")
+            self.params = params
+            data_backed_table = True
+        else:
+            data_backed_table = False
+            data = []
+            if source is not None:
+                self.source = source
         if template is None:
             template = 'ajax_table.html'
-        # The reason we pass data=[] here and patch the queryset in below is
-        # because of a bootstrapping issue.  We want to sort the initial
-        # queryset, and this is much cleaner if the table has has its .columns
-        # set up which is only done in Table.__init__...
+        # Even if this is an ajax backed table, we pass data here and patch
+        # the queryset in below because of a bootstrapping issue: we want to
+        # sort the initial queryset, and this is much cleaner if the table has
+        # has its .columns set up which is only done in Table.__init__...
         super(DataTablesTable, self).__init__(
-            data=[], sortable=sortable, empty_text=empty_text, attrs=attrs,
+            data=data, sortable=sortable, empty_text=empty_text, attrs=attrs,
             template=template)
-        self.full_queryset = self.get_queryset(*params)
-        self.full_length = self.full_queryset.count()
-        ordering = self.datatable_opts.get('aaSorting', [[0, 'asc']])
-        sorted_queryset = TableBackend(self).apply_sorting_columns(
-            self.full_queryset, ordering)
-        display_length = self.datatable_opts.get('iDisplayLength', 10)
-        del self.data.list
-        self.data.queryset = sorted_queryset[:display_length]
-        if source is not None:
-            self.source = source
+        if not data_backed_table:
+            self.full_queryset = self.get_queryset(*params)
+            self.full_length = self.full_queryset.count()
+            ordering = self.datatable_opts.get('aaSorting', [[0, 'asc']])
+            sorted_queryset = TableBackend(self).apply_sorting_columns(
+                self.full_queryset, ordering)
+            display_length = self.datatable_opts.get('iDisplayLength', 10)
+            del self.data.list
+            self.data.queryset = sorted_queryset[:display_length]
         # We are careful about modifying the attrs here -- if it comes from
         # class Meta:-type options, we don't want to modify the original
         # value!
@@ -182,13 +202,17 @@
         """The DataTable options for this table, serialized as JSON."""
         opts = {
             'bJQueryUI': True,
-            'bServerSide': True,
             'bProcessing': True,
-            'sAjaxSource': self.source,
-            'bFilter': bool(self.searchable_columns),
-            'iDeferLoading': self.full_length,
+            'bFilter': True,
             }
         opts.update(self.datatable_opts)
+        if self.source is not None:
+            opts.update({
+                'bServerSide': True,
+                'sAjaxSource': self.source,
+                'bFilter': bool(self.searchable_columns),
+                'iDeferLoading': self.full_length,
+                })
         aoColumnDefs = opts['aoColumnDefs'] = []
         for col in self.columns:
             aoColumnDefs.append({
@@ -198,28 +222,30 @@
                 })
         return simplejson.dumps(opts)
 
-    # Subclasses must override get_queryset() and may want to provide values
-    # for source, datatable_opts and searchable_columns.
-
-    @abstractmethod
+    # Any subclass might want to provide values for datatable_opts.
+
+    # Extra DataTable options.  Values you might want to override here include
+    # 'iDisplayLength' (how big to make the table's pages by default) and
+    # 'aaSorting' (the initial sort of the table).  See
+    # http://datatables.net/usage/options for more.
+    datatable_opts = {}
+
+    # Subclasses that use dynamic data *must* override get_queryset() and may
+    # want to provide values for source, and searchable_columns.
+
     def get_queryset(self, *args):
         """The data the table displays.
 
         The return data will be sorted, filtered and sliced depending on how
         the table is manipulated by the user.
         """
+        raise NotImplementedError(self.get_queryset)
 
     # The URL to get data from (i.e. the sAjaxSource of the table).  Often
     # it's more convenient to pass this to the table __init__ to allow the
     # code to be laid out in a more logical order.
     source = None
 
-    # Extra DataTable options.  Values you might want to override here include
-    # 'iDisplayLength' (how big to make the table's pages by default) and
-    # 'aaSorting' (the initial sort of the table).  See
-    # http://datatables.net/usage/options for more.
-    datatable_opts = {}
-
     # Perform searches by looking in these columns.  Searching will not be
     # enabled unless you set this.  Searching is only supported in textual
     # columns for now (supporting an IntegerField with Choices seems possible

