Minor refactoring. The BaseTable implementation is now split into an actual "BaseTabl...
authorMichael Elsdörfer <michael@elsdoerfer.info>
Wed, 24 Mar 2010 16:30:36 +0000 (17:30 +0100)
committerMichael Elsdörfer <michael@elsdoerfer.info>
Wed, 24 Mar 2010 16:30:36 +0000 (17:30 +0100)
README
django_tables/__init__.py
django_tables/base.py [moved from django_tables/tables.py with 85% similarity]
django_tables/memory.py [new file with mode: 0644]
django_tables/models.py
tests/test_basic.py
tests/test_memory.py [new file with mode: 0644]
tests/test_templates.py

diff --git a/README b/README
index 622785b68f56fc8856d01144fbf202e6023508a7..db1930f897584f3ca1b4696d4c22263ab827744a 100644 (file)
--- a/README
+++ b/README
@@ -22,7 +22,7 @@ Working with Tables
 A table class looks very much like a form:\r
 \r
     import django_tables as tables\r
-    class CountryTable(tables.Table):\r
+    class CountryTable(tables.MemoryTable):\r
         name = tables.Column(verbose_name="Country Name")\r
         population = tables.Column(sortable=False, visible=False)\r
         time_zone = tables.Column(name="tz", default="UTC+1")\r
@@ -131,7 +131,7 @@ Table Options
 Table-specific options are implemented using the same inner ``Meta`` class\r
 concept as known from forms and models in Django:\r
 \r
-    class MyTable(tables.Table):\r
+    class MyTable(tables.MemoryTable):\r
         class Meta:\r
             sortable = True\r
 \r
@@ -269,7 +269,7 @@ the base column class will continue to work by itself.
 \r
 There are no required arguments. The following is fine:\r
 \r
-    class MyTable(tables.Table):\r
+    class MyTable(tables.MemoryTable):\r
         c = tables.Column()\r
 \r
 It will result in a column named "c" in the table. You can specify the\r
@@ -613,4 +613,4 @@ have subclasses for each model class, even if it just redirects to use the
 correct filter class; \r
 \r
 If not using django-filter, we wouldn't have different filter types; filters \r
-would just hold the data, and each column would know how to apply it.
\ No newline at end of file
+would just hold the data, and each column would know how to apply it.\r
index 399df8d52b2a30f1ffc16299485489ad237e0770..6a3916a7f0925de1827022bdc032dfb5df58b2a4 100644 (file)
@@ -1,3 +1,4 @@
-from tables import *\r
+from memory import *\r
 from models import *\r
-from columns import *
\ No newline at end of file
+from columns import *\r
+from base import *
\ No newline at end of file
similarity index 85%
rename from django_tables/tables.py
rename to django_tables/base.py
index 94274e8c79c56752c05eedf367549e3a6b727a35..8ab77be7bba9d2195f790ef4793e2fa4c01ecda3 100644 (file)
@@ -6,35 +6,16 @@ from django.utils.encoding import StrAndUnicode
 from django.utils.text import capfirst\r
 from columns import Column\r
 \r
-__all__ = ('BaseTable', 'Table', 'options')\r
 \r
-def sort_table(data, order_by):\r
-    """Sort a list of dicts according to the fieldnames in the\r
-    ``order_by`` iterable. Prefix with hypen for reverse.\r
+__all__ = ('BaseTable', 'options')\r
 \r
-    Dict values can be callables.\r
-    """\r
-    def _cmp(x, y):\r
-        for name, reverse in instructions:\r
-            lhs, rhs = x.get(name), y.get(name)\r
-            res = cmp((callable(lhs) and [lhs(x)] or [lhs])[0],\r
-                      (callable(rhs) and [rhs(y)] or [rhs])[0])\r
-            if res != 0:\r
-                return reverse and -res or res\r
-        return 0\r
-    instructions = []\r
-    for o in order_by:\r
-        if o.startswith('-'):\r
-            instructions.append((o[1:], True,))\r
-        else:\r
-            instructions.append((o, False,))\r
-    data.sort(cmp=_cmp)\r
 \r
 class TableOptions(object):\r
     def __init__(self, options=None):\r
         super(TableOptions, self).__init__()\r
         self.sortable = getattr(options, 'sortable', None)\r
 \r
+\r
 class DeclarativeColumnsMetaclass(type):\r
     """\r
     Metaclass that converts Column attributes to a dictionary called\r
@@ -189,7 +170,13 @@ class DefaultOptions(object):
     IGNORE_INVALID_OPTIONS = True\r
 options = DefaultOptions()\r
 \r
+\r
 class BaseTable(object):\r
+    """A collection of columns, plus their associated data rows.\r
+    """\r
+\r
+    __metaclass__ = DeclarativeColumnsMetaclass\r
+\r
     def __init__(self, data, order_by=None):\r
         """Create a new table instance with the iterable ``data``.\r
 \r
@@ -219,52 +206,15 @@ class BaseTable(object):
         self.base_columns = copy.deepcopy(type(self).base_columns)\r
 \r
     def _build_snapshot(self):\r
-        """Rebuilds the table whenever it's options change.\r
+        """Rebuild the table for the current set of options.\r
 \r
         Whenver the table options change, e.g. say a new sort order,\r
         this method will be asked to regenerate the actual table from\r
         the linked data source.\r
 \r
-        In the case of this base table implementation, a copy of the\r
-        source data is created, and then modified appropriately.\r
-\r
-        # TODO: currently this is called whenever data changes; it is\r
-        # probably much better to do this on-demand instead, when the\r
-        # data is *needed* for the first time.\r
+        Subclasses should override this.\r
         """\r
-\r
-        # reset caches\r
-        self._columns._reset()\r
-        self._rows._reset()\r
-\r
-        snapshot = copy.copy(self._data)\r
-        for row in snapshot:\r
-            # add data that is missing from the source. we do this now so\r
-            # that the colunn ``default`` and ``data`` values can affect\r
-            # sorting (even when callables are used)!\r
-            # This is a design decision - the alternative would be to\r
-            # resolve the values when they are accessed, and either do not\r
-            # support sorting them at all, or run the callables during\r
-            # sorting.\r
-            for column in self.columns.all():\r
-                name_in_source = column.declared_name\r
-                if column.column.data:\r
-                    if callable(column.column.data):\r
-                        # if data is a callable, use it's return value\r
-                        row[name_in_source] = column.column.data(BoundRow(self, row))\r
-                    else:\r
-                        name_in_source = column.column.data\r
-\r
-                # the following will be True if:\r
-                #  * the source does not provide that column or provides None\r
-                #  * the column did provide a data callable that returned None\r
-                if row.get(name_in_source, None) is None:\r
-                    row[name_in_source] = column.get_default(BoundRow(self, row))\r
-\r
-        if self.order_by:\r
-            actual_order_by = self._resolve_sort_directions(self.order_by)\r
-            sort_table(snapshot, self._cols_to_fields(actual_order_by))\r
-        self._snapshot = snapshot\r
+        self._snapshot = copy.copy(self._data)\r
 \r
     def _get_data(self):\r
         if self._snapshot is None:\r
@@ -384,13 +334,6 @@ class BaseTable(object):
             raise Http404(str(e))\r
 \r
 \r
-class Table(BaseTable):\r
-    "A collection of columns, plus their associated data rows."\r
-    # This is a separate class from BaseTable in order to abstract the way\r
-    # self.columns is specified.\r
-    __metaclass__ = DeclarativeColumnsMetaclass\r
-\r
-\r
 class Columns(object):\r
     """Container for spawning BoundColumns.\r
 \r
diff --git a/django_tables/memory.py b/django_tables/memory.py
new file mode 100644 (file)
index 0000000..cd2118b
--- /dev/null
@@ -0,0 +1,90 @@
+import copy
+from base import BaseTable, BoundRow
+
+
+__all__ = ('MemoryTable', 'Table',)
+
+
+def sort_table(data, order_by):
+    """Sort a list of dicts according to the fieldnames in the
+    ``order_by`` iterable. Prefix with hypen for reverse.
+
+    Dict values can be callables.
+    """
+    def _cmp(x, y):
+        for name, reverse in instructions:
+            lhs, rhs = x.get(name), y.get(name)
+            res = cmp((callable(lhs) and [lhs(x)] or [lhs])[0],
+                      (callable(rhs) and [rhs(y)] or [rhs])[0])
+            if res != 0:
+                return reverse and -res or res
+        return 0
+    instructions = []
+    for o in order_by:
+        if o.startswith('-'):
+            instructions.append((o[1:], True,))
+        else:
+            instructions.append((o, False,))
+    data.sort(cmp=_cmp)
+
+
+class MemoryTable(BaseTable):
+
+    # This is a separate class from BaseTable in order to abstract the way
+    # self.columns is specified.
+
+    def _build_snapshot(self):
+        """Rebuilds the table whenever it's options change.
+
+        Whenver the table options change, e.g. say a new sort order,
+        this method will be asked to regenerate the actual table from
+        the linked data source.
+
+        In the case of this base table implementation, a copy of the
+        source data is created, and then modified appropriately.
+
+        # TODO: currently this is called whenever data changes; it is
+        # probably much better to do this on-demand instead, when the
+        # data is *needed* for the first time.
+        """
+
+        # reset caches
+        self._columns._reset()
+        self._rows._reset()
+
+        snapshot = copy.copy(self._data)
+        for row in snapshot:
+            # add data that is missing from the source. we do this now so
+            # that the colunn ``default`` and ``data`` values can affect
+            # sorting (even when callables are used)!
+            # This is a design decision - the alternative would be to
+            # resolve the values when they are accessed, and either do not
+            # support sorting them at all, or run the callables during
+            # sorting.
+            for column in self.columns.all():
+                name_in_source = column.declared_name
+                if column.column.data:
+                    if callable(column.column.data):
+                        # if data is a callable, use it's return value
+                        row[name_in_source] = column.column.data(BoundRow(self, row))
+                    else:
+                        name_in_source = column.column.data
+
+                # the following will be True if:
+                #  * the source does not provide that column or provides None
+                #  * the column did provide a data callable that returned None
+                if row.get(name_in_source, None) is None:
+                    row[name_in_source] = column.get_default(BoundRow(self, row))
+
+        if self.order_by:
+            actual_order_by = self._resolve_sort_directions(self.order_by)
+            sort_table(snapshot, self._cols_to_fields(actual_order_by))
+        self._snapshot = snapshot
+
+
+class Table(MemoryTable):
+    def __new__(cls, *a, **kw):
+        from warnings import warn
+        warn('"Table" has been renamed to "MemoryTable". Please use the '+
+             'new name.', DeprecationWarning)
+        return MemoryTable.__new__(cls)
index 3650edd05653964140ab3762999056ff3d2b058d..a7cba827cdf7a00a5d40cff0e69f2e7785ccaa80 100644 (file)
@@ -1,9 +1,11 @@
 from django.core.exceptions import FieldError\r
 from django.utils.datastructures import SortedDict\r
-from tables import BaseTable, DeclarativeColumnsMetaclass, \\r
+from base import BaseTable, DeclarativeColumnsMetaclass, \\r
     Column, BoundRow, Rows, TableOptions, rmprefix, toggleprefix\r
 \r
-__all__ = ('BaseModelTable', 'ModelTable')\r
+\r
+__all__ = ('ModelTable',)\r
+\r
 \r
 class ModelTableOptions(TableOptions):\r
     def __init__(self, options=None):\r
@@ -12,6 +14,7 @@ class ModelTableOptions(TableOptions):
         self.columns = getattr(options, 'columns', None)\r
         self.exclude = getattr(options, 'exclude', None)\r
 \r
+\r
 def columns_for_model(model, columns=None, exclude=None):\r
     """\r
     Returns a ``SortedDict`` containing form columns for the given model.\r
@@ -54,7 +57,8 @@ class ModelTableMetaclass(DeclarativeColumnsMetaclass):
             self.base_columns = columns\r
         return self\r
 \r
-class BaseModelTable(BaseTable):\r
+\r
+class ModelTable(BaseTable):\r
     """Table that is based on a model.\r
 \r
     Similar to ModelForm, a column will automatically be created for all\r
@@ -72,6 +76,9 @@ class BaseModelTable(BaseTable):
     just don't any data at all, the model the table is based on will\r
     provide it.\r
     """\r
+\r
+    __metaclass__ = ModelTableMetaclass\r
+\r
     def __init__(self, data=None, *args, **kwargs):\r
         if data == None:\r
             if self._meta.model is None:\r
@@ -83,7 +90,7 @@ class BaseModelTable(BaseTable):
         else:\r
             self.queryset = data\r
 \r
-        super(BaseModelTable, self).__init__(self.queryset, *args, **kwargs)\r
+        super(ModelTable, self).__init__(self.queryset, *args, **kwargs)\r
         self._rows = ModelRows(self)\r
 \r
     def _validate_column_name(self, name, purpose):\r
@@ -91,7 +98,7 @@ class BaseModelTable(BaseTable):
         spanning relationships to be sorted."""\r
 \r
         # let the base class sort out the easy ones\r
-        result = super(BaseModelTable, self)._validate_column_name(name, purpose)\r
+        result = super(ModelTable, self)._validate_column_name(name, purpose)\r
         if not result:\r
             return False\r
 \r
@@ -121,7 +128,7 @@ class BaseModelTable(BaseTable):
                     continue\r
                 try:\r
                     # Let Django validate the lookup by asking it to build\r
-                    # the final query; the way to do this has changed in \r
+                    # the final query; the way to do this has changed in\r
                     # Django 1.2, and we try to support both versions.\r
                     _temp = self.queryset.order_by(lookup).query\r
                     if hasattr(_temp, 'as_sql'):\r
@@ -157,8 +164,6 @@ class BaseModelTable(BaseTable):
         for row in self.data:\r
             yield BoundModelRow(self, row)\r
 \r
-class ModelTable(BaseModelTable):\r
-    __metaclass__ = ModelTableMetaclass\r
 \r
 class ModelRows(Rows):\r
     def __init__(self, *args, **kwargs):\r
index 372532ad55da62c243216a39b098da0ae3107078..d171bd0a315015b3483550a6e70dfd4322174f3c 100644 (file)
-"""Test the base table functionality.\r
-\r
-This includes the core, as well as static data, non-model tables.\r
-"""\r
-\r
-from math import sqrt\r
-from nose.tools import assert_raises\r
-from django.core.paginator import Paginator\r
-from django.http import Http404\r
-import django_tables as tables\r
-\r
-def test_declaration():\r
-    """\r
-    Test defining tables by declaration.\r
-    """\r
-\r
-    class GeoAreaTable(tables.Table):\r
-        name = tables.Column()\r
-        population = tables.Column()\r
-\r
-    assert len(GeoAreaTable.base_columns) == 2\r
-    assert 'name' in GeoAreaTable.base_columns\r
-    assert not hasattr(GeoAreaTable, 'name')\r
-\r
-    class CountryTable(GeoAreaTable):\r
-        capital = tables.Column()\r
-\r
-    assert len(CountryTable.base_columns) == 3\r
-    assert 'capital' in CountryTable.base_columns\r
-\r
-    # multiple inheritance\r
-    class AddedMixin(tables.Table):\r
-        added = tables.Column()\r
-    class CityTable(GeoAreaTable, AddedMixin):\r
-        mayer = tables.Column()\r
-\r
-    assert len(CityTable.base_columns) == 4\r
-    assert 'added' in CityTable.base_columns\r
-\r
-    # modelforms: support switching from a non-model table hierarchy to a\r
-    # modeltable hierarchy (both base class orders)\r
-    class StateTable1(tables.ModelTable, GeoAreaTable):\r
-        motto = tables.Column()\r
-    class StateTable2(GeoAreaTable, tables.ModelTable):\r
-        motto = tables.Column()\r
-\r
-    assert len(StateTable1.base_columns) == len(StateTable2.base_columns) == 3\r
-    assert 'motto' in StateTable1.base_columns\r
-    assert 'motto' in StateTable2.base_columns\r
-\r
-def test_basic():\r
-    class StuffTable(tables.Table):\r
-        name = tables.Column()\r
-        answer = tables.Column(default=42)\r
-        c = tables.Column(name="count", default=1)\r
-        email = tables.Column(data="@")\r
-    stuff = StuffTable([\r
-        {'id': 1, 'name': 'Foo Bar', '@': 'foo@bar.org'},\r
-    ])\r
-\r
-    # access without order_by works\r
-    stuff.data\r
-    stuff.rows\r
-\r
-    # make sure BoundColumnn.name always gives us the right thing, whether\r
-    # the column explicitely defines a name or not.\r
-    stuff.columns['count'].name == 'count'\r
-    stuff.columns['answer'].name == 'answer'\r
-\r
-    for r in stuff.rows:\r
-        # unknown fields are removed/not-accessible\r
-        assert 'name' in r\r
-        assert not 'id' in r\r
-        # missing data is available as default\r
-        assert 'answer' in r\r
-        assert r['answer'] == 42   # note: different from prev. line!\r
-\r
-        # all that still works when name overrides are used\r
-        assert not 'c' in r\r
-        assert 'count' in r\r
-        assert r['count'] == 1\r
-\r
-        # columns with data= option work fine\r
-        assert r['email'] == 'foo@bar.org'\r
-\r
-    # try to splice rows by index\r
-    assert 'name' in stuff.rows[0]\r
-    assert isinstance(stuff.rows[0:], list)\r
-\r
-    # [bug] splicing the table gives us valid, working rows\r
-    assert list(stuff[0]) == list(stuff.rows[0])\r
-    assert stuff[0]['name'] == 'Foo Bar'\r
-\r
-    # changing an instance's base_columns does not change the class\r
-    assert id(stuff.base_columns) != id(StuffTable.base_columns)\r
-    stuff.base_columns['test'] = tables.Column()\r
-    assert not 'test' in StuffTable.base_columns\r
-\r
-    # optionally, exceptions can be raised when input is invalid\r
-    tables.options.IGNORE_INVALID_OPTIONS = False\r
-    try:\r
-        assert_raises(ValueError, setattr, stuff, 'order_by', '-name,made-up-column')\r
-        assert_raises(ValueError, setattr, stuff, 'order_by', ('made-up-column',))\r
-        # when a column name is overwritten, the original won't work anymore\r
-        assert_raises(ValueError, setattr, stuff, 'order_by', 'c')\r
-        # reset for future tests\r
-    finally:\r
-        tables.options.IGNORE_INVALID_OPTIONS = True\r
-\r
-def test_caches():\r
-    """Ensure the various caches are effective.\r
-    """\r
-\r
-    class BookTable(tables.Table):\r
-        name = tables.Column()\r
-        answer = tables.Column(default=42)\r
-    books = BookTable([\r
-        {'name': 'Foo: Bar'},\r
-    ])\r
-\r
-    assert id(list(books.columns)[0]) == id(list(books.columns)[0])\r
-    # TODO: row cache currently not used\r
-    #assert id(list(books.rows)[0]) == id(list(books.rows)[0])\r
-\r
-    # test that caches are reset after an update()\r
-    old_column_cache = id(list(books.columns)[0])\r
-    old_row_cache = id(list(books.rows)[0])\r
-    books.update()\r
-    assert id(list(books.columns)[0]) != old_column_cache\r
-    assert id(list(books.rows)[0]) != old_row_cache\r
-\r
-def test_meta_sortable():\r
-    """Specific tests for sortable table meta option."""\r
-\r
-    def mktable(default_sortable):\r
-        class BookTable(tables.Table):\r
-            id = tables.Column(sortable=True)\r
-            name = tables.Column(sortable=False)\r
-            author = tables.Column()\r
-            class Meta:\r
-                sortable = default_sortable\r
-        return BookTable([])\r
-\r
-    global_table = mktable(None)\r
-    for default_sortable, results in (\r
-        (None,      (True, False, True)),    # last bool is global default\r
-        (True,      (True, False, True)),    # last bool is table default\r
-        (False,     (True, False, False)),   # last bool is table default\r
-    ):\r
-        books = mktable(default_sortable)\r
-        assert [c.sortable for c in books.columns] == list(results)\r
-\r
-        # it also works if the meta option is manually changed after\r
-        # class and instance creation\r
-        global_table._meta.sortable = default_sortable\r
-        assert [c.sortable for c in global_table.columns] == list(results)\r
-\r
-def test_sort():\r
-    class BookTable(tables.Table):\r
-        id = tables.Column(direction='desc')\r
-        name = tables.Column()\r
-        pages = tables.Column(name='num_pages')  # test rewritten names\r
-        language = tables.Column(default='en')   # default affects sorting\r
-        rating = tables.Column(data='*')         # test data field option\r
-\r
-    books = BookTable([\r
-        {'id': 1, 'pages':  60, 'name': 'Z: The Book', '*': 5},    # language: en\r
-        {'id': 2, 'pages': 100, 'language': 'de', 'name': 'A: The Book', '*': 2},\r
-        {'id': 3, 'pages':  80, 'language': 'de', 'name': 'A: The Book, Vol. 2', '*': 4},\r
-        {'id': 4, 'pages': 110, 'language': 'fr', 'name': 'A: The Book, French Edition'},   # rating (with data option) is missing\r
-    ])\r
-\r
-    # None is normalized to an empty order by tuple, ensuring iterability;\r
-    # it also supports all the cool methods that we offer for order_by.\r
-    # This is true for the default case...\r
-    assert books.order_by == ()\r
-    iter(books.order_by)\r
-    assert hasattr(books.order_by, 'toggle')\r
-    # ...as well as when explicitly set to None.\r
-    books.order_by = None\r
-    assert books.order_by == ()\r
-    iter(books.order_by)\r
-    assert hasattr(books.order_by, 'toggle')\r
-\r
-    # test various orderings\r
-    def test_order(order, result):\r
-        books.order_by = order\r
-        assert [b['id'] for b in books.rows] == result\r
-    test_order(('num_pages',), [1,3,2,4])\r
-    test_order(('-num_pages',), [4,2,3,1])\r
-    test_order(('name',), [2,4,3,1])\r
-    test_order(('language', 'num_pages'), [3,2,1,4])\r
-    # using a simple string (for convinience as well as querystring passing\r
-    test_order('-num_pages', [4,2,3,1])\r
-    test_order('language,num_pages', [3,2,1,4])\r
-    # if overwritten, the declared fieldname has no effect\r
-    test_order('pages,name', [2,4,3,1])   # == ('name',)\r
-    # sort by column with "data" option\r
-    test_order('rating', [4,2,3,1])\r
-\r
-    # test the column with a default ``direction`` set to descending\r
-    test_order('id', [4,3,2,1])\r
-    test_order('-id', [1,2,3,4])\r
-    # changing the direction afterwards is fine too\r
-    books.base_columns['id'].direction = 'asc'\r
-    test_order('id', [1,2,3,4])\r
-    test_order('-id', [4,3,2,1])\r
-    # a invalid direction string raises an exception\r
-    assert_raises(ValueError, setattr, books.base_columns['id'], 'direction', 'blub')\r
-\r
-    # [bug] test alternative order formats if passed to constructor\r
-    BookTable([], 'language,-num_pages')\r
-\r
-    # test invalid order instructions\r
-    books.order_by = 'xyz'\r
-    assert not books.order_by\r
-    books.base_columns['language'].sortable = False\r
-    books.order_by = 'language'\r
-    assert not books.order_by\r
-    test_order(('language', 'num_pages'), [1,3,2,4])  # as if: 'num_pages'\r
-\r
-    # [bug] order_by did not run through setter when passed to init\r
-    books = BookTable([], order_by='name')\r
-    assert books.order_by == ('name',)\r
-\r
-    # test table.order_by extensions\r
-    books.order_by = ''\r
-    assert books.order_by.polarize(False) == ()\r
-    assert books.order_by.polarize(True) == ()\r
-    assert books.order_by.toggle() == ()\r
-    assert books.order_by.polarize(False, ['id']) == ('id',)\r
-    assert books.order_by.polarize(True, ['id']) == ('-id',)\r
-    assert books.order_by.toggle(['id']) == ('id',)\r
-    books.order_by = 'id,-name'\r
-    assert books.order_by.polarize(False, ['name']) == ('id', 'name')\r
-    assert books.order_by.polarize(True, ['name']) == ('id', '-name')\r
-    assert books.order_by.toggle(['name']) == ('id', 'name')\r
-    # ``in`` operator works\r
-    books.order_by = 'name'\r
-    assert 'name' in books.order_by\r
-    books.order_by = '-name'\r
-    assert 'name' in books.order_by\r
-    assert not 'language' in books.order_by\r
-\r
-def test_callable():\r
-    """Data fields, ``default`` and ``data`` options can be callables.\r
-    """\r
-\r
-    class MathTable(tables.Table):\r
-        lhs = tables.Column()\r
-        rhs = tables.Column()\r
-        op = tables.Column(default='+')\r
-        sum = tables.Column(default=lambda d: calc(d['op'], d['lhs'], d['rhs']))\r
-        sqrt = tables.Column(data=lambda d: int(sqrt(d['sum'])))\r
-\r
-    math = MathTable([\r
-        {'lhs': 1, 'rhs': lambda x: x['lhs']*3},              # 1+3\r
-        {'lhs': 9, 'rhs': lambda x: x['lhs'], 'op': '/'},     # 9/9\r
-        {'lhs': lambda x: x['rhs']+3, 'rhs': 4, 'op': '-'},   # 7-4\r
-    ])\r
-\r
-    # function is called when queried\r
-    def calc(op, lhs, rhs):\r
-        if op == '+': return lhs+rhs\r
-        elif op == '/': return lhs/rhs\r
-        elif op == '-': return lhs-rhs\r
-    assert [calc(row['op'], row['lhs'], row['rhs']) for row in math] == [4,1,3]\r
-\r
-    # field function is called while sorting\r
-    math.order_by = ('-rhs',)\r
-    assert [row['rhs'] for row in math] == [9,4,3]\r
-\r
-    # default function is called while sorting\r
-    math.order_by = ('sum',)\r
-    assert [row['sum'] for row in math] == [1,3,4]\r
-\r
-    # data function is called while sorting\r
-    math.order_by = ('sqrt',)\r
-    assert [row['sqrt'] for row in math] == [1,1,2]\r
-\r
-def test_pagination():\r
-    class BookTable(tables.Table):\r
-        name = tables.Column()\r
-\r
-    # create some sample data\r
-    data = []\r
-    for i in range(1,101):\r
-        data.append({'name': 'Book Nr. %d'%i})\r
-    books = BookTable(data)\r
-\r
-    # external paginator\r
-    paginator = Paginator(books.rows, 10)\r
-    assert paginator.num_pages == 10\r
-    page = paginator.page(1)\r
-    assert len(page.object_list) == 10\r
-    assert page.has_previous() == False\r
-    assert page.has_next() == True\r
-\r
-    # integrated paginator\r
-    books.paginate(Paginator, 10, page=1)\r
-    # rows is now paginated\r
-    assert len(list(books.rows.page())) == 10\r
-    assert len(list(books.rows.all())) == 100\r
-    # new attributes\r
-    assert books.paginator.num_pages == 10\r
-    assert books.page.has_previous() == False\r
-    assert books.page.has_next() == True\r
-    # exceptions are converted into 404s\r
-    assert_raises(Http404, books.paginate, Paginator, 10, page=9999)\r
-    assert_raises(Http404, books.paginate, Paginator, 10, page="abc")\r
-\r
-\r
-# TODO: all the column stuff might warrant it's own test file\r
-def test_columns():\r
-    """Test Table.columns container functionality.\r
-    """\r
-\r
-    class BookTable(tables.Table):\r
-        id = tables.Column(sortable=False, visible=False)\r
-        name = tables.Column(sortable=True)\r
-        pages = tables.Column(sortable=True)\r
-        language = tables.Column(sortable=False)\r
-    books = BookTable([])\r
-\r
-    assert list(books.columns.sortable()) == [c for c in books.columns if c.sortable]\r
-\r
-    # .columns iterator only yields visible columns\r
-    assert len(list(books.columns)) == 3\r
-    # visiblity of columns can be changed at instance-time\r
-    books.columns['id'].visible = True\r
-    assert len(list(books.columns)) == 4\r
-\r
-\r
-def test_column_order():\r
-    """Test the order functionality of bound columns.\r
-    """\r
-\r
-    class BookTable(tables.Table):\r
-        id = tables.Column()\r
-        name = tables.Column()\r
-        pages = tables.Column()\r
-        language = tables.Column()\r
-    books = BookTable([])\r
-\r
-    # the basic name property is a no-brainer\r
-    books.order_by = ''\r
-    assert [c.name for c in books.columns] == ['id','name','pages','language']\r
-\r
-    # name_reversed will always reverse, no matter what\r
-    for test in ['', 'name', '-name']:\r
-        books.order_by = test\r
-        assert [c.name_reversed for c in books.columns] == ['-id','-name','-pages','-language']\r
-\r
-    # name_toggled will always toggle\r
-    books.order_by = ''\r
-    assert [c.name_toggled for c in books.columns] == ['id','name','pages','language']\r
-    books.order_by = 'id'\r
-    assert [c.name_toggled for c in books.columns] == ['-id','name','pages','language']\r
-    books.order_by = '-name'\r
-    assert [c.name_toggled for c in books.columns] == ['id','name','pages','language']\r
-    # other columns in an order_by will be dismissed\r
-    books.order_by = '-id,name'\r
-    assert [c.name_toggled for c in books.columns] == ['id','-name','pages','language']\r
-\r
-    # with multi-column order, this is slightly more complex\r
-    books.order_by =  ''\r
-    assert [str(c.order_by) for c in books.columns] == ['id','name','pages','language']\r
-    assert [str(c.order_by_reversed) for c in books.columns] == ['-id','-name','-pages','-language']\r
-    assert [str(c.order_by_toggled) for c in books.columns] == ['id','name','pages','language']\r
-    books.order_by =  'id'\r
-    assert [str(c.order_by) for c in books.columns] == ['id','id,name','id,pages','id,language']\r
-    assert [str(c.order_by_reversed) for c in books.columns] == ['-id','id,-name','id,-pages','id,-language']\r
-    assert [str(c.order_by_toggled) for c in books.columns] == ['-id','id,name','id,pages','id,language']\r
-    books.order_by =  '-pages,id'\r
-    assert [str(c.order_by) for c in books.columns] == ['-pages,id','-pages,id,name','pages,id','-pages,id,language']\r
-    assert [str(c.order_by_reversed) for c in books.columns] == ['-pages,-id','-pages,id,-name','-pages,id','-pages,id,-language']\r
-    assert [str(c.order_by_toggled) for c in books.columns] == ['-pages,-id','-pages,id,name','pages,id','-pages,id,language']\r
-\r
-    # querying whether a column is ordered is possible\r
-    books.order_by = ''\r
-    assert [c.is_ordered for c in books.columns] == [False, False, False, False]\r
-    books.order_by = 'name'\r
-    assert [c.is_ordered for c in books.columns] == [False, True, False, False]\r
-    assert [c.is_ordered_reverse for c in books.columns] == [False, False, False, False]\r
-    assert [c.is_ordered_straight for c in books.columns] == [False, True, False, False]\r
-    books.order_by = '-pages'\r
-    assert [c.is_ordered for c in books.columns] == [False, False, True, False]\r
-    assert [c.is_ordered_reverse for c in books.columns] == [False, False, True, False]\r
-    assert [c.is_ordered_straight for c in books.columns] == [False, False, False, False]\r
-    # and even works with multi-column ordering\r
-    books.order_by = 'id,-pages'\r
-    assert [c.is_ordered for c in books.columns] == [True, False, True, False]\r
-    assert [c.is_ordered_reverse for c in books.columns] == [False, False, True, False]\r
-    assert [c.is_ordered_straight for c in books.columns] == [True, False, False, False]
\ No newline at end of file
+"""Test the core table functionality.
+"""
+
+
+from nose.tools import assert_raises
+from django.http import Http404
+from django.core.paginator import Paginator
+import django_tables as tables
+from django_tables.base import BaseTable
+
+
+class TestTable(BaseTable):
+    pass
+
+
+def test_declaration():
+    """
+    Test defining tables by declaration.
+    """
+
+    class GeoAreaTable(TestTable):
+        name = tables.Column()
+        population = tables.Column()
+
+    assert len(GeoAreaTable.base_columns) == 2
+    assert 'name' in GeoAreaTable.base_columns
+    assert not hasattr(GeoAreaTable, 'name')
+
+    class CountryTable(GeoAreaTable):
+        capital = tables.Column()
+
+    assert len(CountryTable.base_columns) == 3
+    assert 'capital' in CountryTable.base_columns
+
+    # multiple inheritance
+    class AddedMixin(TestTable):
+        added = tables.Column()
+    class CityTable(GeoAreaTable, AddedMixin):
+        mayer = tables.Column()
+
+    assert len(CityTable.base_columns) == 4
+    assert 'added' in CityTable.base_columns
+
+    # modelforms: support switching from a non-model table hierarchy to a
+    # modeltable hierarchy (both base class orders)
+    class StateTable1(tables.ModelTable, GeoAreaTable):
+        motto = tables.Column()
+    class StateTable2(GeoAreaTable, tables.ModelTable):
+        motto = tables.Column()
+
+    assert len(StateTable1.base_columns) == len(StateTable2.base_columns) == 3
+    assert 'motto' in StateTable1.base_columns
+    assert 'motto' in StateTable2.base_columns
+
+
+def test_pagination():
+    class BookTable(TestTable):
+        name = tables.Column()
+
+    # create some sample data
+    data = []
+    for i in range(1,101):
+        data.append({'name': 'Book Nr. %d'%i})
+    books = BookTable(data)
+
+    # external paginator
+    paginator = Paginator(books.rows, 10)
+    assert paginator.num_pages == 10
+    page = paginator.page(1)
+    assert len(page.object_list) == 10
+    assert page.has_previous() == False
+    assert page.has_next() == True
+
+    # integrated paginator
+    books.paginate(Paginator, 10, page=1)
+    # rows is now paginated
+    assert len(list(books.rows.page())) == 10
+    assert len(list(books.rows.all())) == 100
+    # new attributes
+    assert books.paginator.num_pages == 10
+    assert books.page.has_previous() == False
+    assert books.page.has_next() == True
+    # exceptions are converted into 404s
+    assert_raises(Http404, books.paginate, Paginator, 10, page=9999)
+    assert_raises(Http404, books.paginate, Paginator, 10, page="abc")
\ No newline at end of file
diff --git a/tests/test_memory.py b/tests/test_memory.py
new file mode 100644 (file)
index 0000000..5c9cfdd
--- /dev/null
@@ -0,0 +1,325 @@
+"""Test the memory table functionality.\r
+\r
+TODO: A bunch of those tests probably fit better into test_basic, since\r
+they aren't really MemoryTable specific.\r
+"""\r
+\r
+from math import sqrt\r
+from nose.tools import assert_raises\r
+from django.core.paginator import Paginator\r
+import django_tables as tables\r
+\r
+\r
+def test_basic():\r
+    class StuffTable(tables.MemoryTable):\r
+        name = tables.Column()\r
+        answer = tables.Column(default=42)\r
+        c = tables.Column(name="count", default=1)\r
+        email = tables.Column(data="@")\r
+    stuff = StuffTable([\r
+        {'id': 1, 'name': 'Foo Bar', '@': 'foo@bar.org'},\r
+    ])\r
+\r
+    # access without order_by works\r
+    stuff.data\r
+    stuff.rows\r
+\r
+    # make sure BoundColumnn.name always gives us the right thing, whether\r
+    # the column explicitely defines a name or not.\r
+    stuff.columns['count'].name == 'count'\r
+    stuff.columns['answer'].name == 'answer'\r
+\r
+    for r in stuff.rows:\r
+        # unknown fields are removed/not-accessible\r
+        assert 'name' in r\r
+        assert not 'id' in r\r
+        # missing data is available as default\r
+        assert 'answer' in r\r
+        assert r['answer'] == 42   # note: different from prev. line!\r
+\r
+        # all that still works when name overrides are used\r
+        assert not 'c' in r\r
+        assert 'count' in r\r
+        assert r['count'] == 1\r
+\r
+        # columns with data= option work fine\r
+        assert r['email'] == 'foo@bar.org'\r
+\r
+    # try to splice rows by index\r
+    assert 'name' in stuff.rows[0]\r
+    assert isinstance(stuff.rows[0:], list)\r
+\r
+    # [bug] splicing the table gives us valid, working rows\r
+    assert list(stuff[0]) == list(stuff.rows[0])\r
+    assert stuff[0]['name'] == 'Foo Bar'\r
+\r
+    # changing an instance's base_columns does not change the class\r
+    assert id(stuff.base_columns) != id(StuffTable.base_columns)\r
+    stuff.base_columns['test'] = tables.Column()\r
+    assert not 'test' in StuffTable.base_columns\r
+\r
+    # optionally, exceptions can be raised when input is invalid\r
+    tables.options.IGNORE_INVALID_OPTIONS = False\r
+    try:\r
+        assert_raises(ValueError, setattr, stuff, 'order_by', '-name,made-up-column')\r
+        assert_raises(ValueError, setattr, stuff, 'order_by', ('made-up-column',))\r
+        # when a column name is overwritten, the original won't work anymore\r
+        assert_raises(ValueError, setattr, stuff, 'order_by', 'c')\r
+        # reset for future tests\r
+    finally:\r
+        tables.options.IGNORE_INVALID_OPTIONS = True\r
+\r
+def test_caches():\r
+    """Ensure the various caches are effective.\r
+    """\r
+\r
+    class BookTable(tables.MemoryTable):\r
+        name = tables.Column()\r
+        answer = tables.Column(default=42)\r
+    books = BookTable([\r
+        {'name': 'Foo: Bar'},\r
+    ])\r
+\r
+    assert id(list(books.columns)[0]) == id(list(books.columns)[0])\r
+    # TODO: row cache currently not used\r
+    #assert id(list(books.rows)[0]) == id(list(books.rows)[0])\r
+\r
+    # test that caches are reset after an update()\r
+    old_column_cache = id(list(books.columns)[0])\r
+    old_row_cache = id(list(books.rows)[0])\r
+    books.update()\r
+    assert id(list(books.columns)[0]) != old_column_cache\r
+    assert id(list(books.rows)[0]) != old_row_cache\r
+\r
+def test_meta_sortable():\r
+    """Specific tests for sortable table meta option."""\r
+\r
+    def mktable(default_sortable):\r
+        class BookTable(tables.MemoryTable):\r
+            id = tables.Column(sortable=True)\r
+            name = tables.Column(sortable=False)\r
+            author = tables.Column()\r
+            class Meta:\r
+                sortable = default_sortable\r
+        return BookTable([])\r
+\r
+    global_table = mktable(None)\r
+    for default_sortable, results in (\r
+        (None,      (True, False, True)),    # last bool is global default\r
+        (True,      (True, False, True)),    # last bool is table default\r
+        (False,     (True, False, False)),   # last bool is table default\r
+    ):\r
+        books = mktable(default_sortable)\r
+        assert [c.sortable for c in books.columns] == list(results)\r
+\r
+        # it also works if the meta option is manually changed after\r
+        # class and instance creation\r
+        global_table._meta.sortable = default_sortable\r
+        assert [c.sortable for c in global_table.columns] == list(results)\r
+\r
+def test_sort():\r
+    class BookTable(tables.MemoryTable):\r
+        id = tables.Column(direction='desc')\r
+        name = tables.Column()\r
+        pages = tables.Column(name='num_pages')  # test rewritten names\r
+        language = tables.Column(default='en')   # default affects sorting\r
+        rating = tables.Column(data='*')         # test data field option\r
+\r
+    books = BookTable([\r
+        {'id': 1, 'pages':  60, 'name': 'Z: The Book', '*': 5},    # language: en\r
+        {'id': 2, 'pages': 100, 'language': 'de', 'name': 'A: The Book', '*': 2},\r
+        {'id': 3, 'pages':  80, 'language': 'de', 'name': 'A: The Book, Vol. 2', '*': 4},\r
+        {'id': 4, 'pages': 110, 'language': 'fr', 'name': 'A: The Book, French Edition'},   # rating (with data option) is missing\r
+    ])\r
+\r
+    # None is normalized to an empty order by tuple, ensuring iterability;\r
+    # it also supports all the cool methods that we offer for order_by.\r
+    # This is true for the default case...\r
+    assert books.order_by == ()\r
+    iter(books.order_by)\r
+    assert hasattr(books.order_by, 'toggle')\r
+    # ...as well as when explicitly set to None.\r
+    books.order_by = None\r
+    assert books.order_by == ()\r
+    iter(books.order_by)\r
+    assert hasattr(books.order_by, 'toggle')\r
+\r
+    # test various orderings\r
+    def test_order(order, result):\r
+        books.order_by = order\r
+        assert [b['id'] for b in books.rows] == result\r
+    test_order(('num_pages',), [1,3,2,4])\r
+    test_order(('-num_pages',), [4,2,3,1])\r
+    test_order(('name',), [2,4,3,1])\r
+    test_order(('language', 'num_pages'), [3,2,1,4])\r
+    # using a simple string (for convinience as well as querystring passing\r
+    test_order('-num_pages', [4,2,3,1])\r
+    test_order('language,num_pages', [3,2,1,4])\r
+    # if overwritten, the declared fieldname has no effect\r
+    test_order('pages,name', [2,4,3,1])   # == ('name',)\r
+    # sort by column with "data" option\r
+    test_order('rating', [4,2,3,1])\r
+\r
+    # test the column with a default ``direction`` set to descending\r
+    test_order('id', [4,3,2,1])\r
+    test_order('-id', [1,2,3,4])\r
+    # changing the direction afterwards is fine too\r
+    books.base_columns['id'].direction = 'asc'\r
+    test_order('id', [1,2,3,4])\r
+    test_order('-id', [4,3,2,1])\r
+    # a invalid direction string raises an exception\r
+    assert_raises(ValueError, setattr, books.base_columns['id'], 'direction', 'blub')\r
+\r
+    # [bug] test alternative order formats if passed to constructor\r
+    BookTable([], 'language,-num_pages')\r
+\r
+    # test invalid order instructions\r
+    books.order_by = 'xyz'\r
+    assert not books.order_by\r
+    books.base_columns['language'].sortable = False\r
+    books.order_by = 'language'\r
+    assert not books.order_by\r
+    test_order(('language', 'num_pages'), [1,3,2,4])  # as if: 'num_pages'\r
+\r
+    # [bug] order_by did not run through setter when passed to init\r
+    books = BookTable([], order_by='name')\r
+    assert books.order_by == ('name',)\r
+\r
+    # test table.order_by extensions\r
+    books.order_by = ''\r
+    assert books.order_by.polarize(False) == ()\r
+    assert books.order_by.polarize(True) == ()\r
+    assert books.order_by.toggle() == ()\r
+    assert books.order_by.polarize(False, ['id']) == ('id',)\r
+    assert books.order_by.polarize(True, ['id']) == ('-id',)\r
+    assert books.order_by.toggle(['id']) == ('id',)\r
+    books.order_by = 'id,-name'\r
+    assert books.order_by.polarize(False, ['name']) == ('id', 'name')\r
+    assert books.order_by.polarize(True, ['name']) == ('id', '-name')\r
+    assert books.order_by.toggle(['name']) == ('id', 'name')\r
+    # ``in`` operator works\r
+    books.order_by = 'name'\r
+    assert 'name' in books.order_by\r
+    books.order_by = '-name'\r
+    assert 'name' in books.order_by\r
+    assert not 'language' in books.order_by\r
+\r
+def test_callable():\r
+    """Data fields, ``default`` and ``data`` options can be callables.\r
+    """\r
+\r
+    class MathTable(tables.MemoryTable):\r
+        lhs = tables.Column()\r
+        rhs = tables.Column()\r
+        op = tables.Column(default='+')\r
+        sum = tables.Column(default=lambda d: calc(d['op'], d['lhs'], d['rhs']))\r
+        sqrt = tables.Column(data=lambda d: int(sqrt(d['sum'])))\r
+\r
+    math = MathTable([\r
+        {'lhs': 1, 'rhs': lambda x: x['lhs']*3},              # 1+3\r
+        {'lhs': 9, 'rhs': lambda x: x['lhs'], 'op': '/'},     # 9/9\r
+        {'lhs': lambda x: x['rhs']+3, 'rhs': 4, 'op': '-'},   # 7-4\r
+    ])\r
+\r
+    # function is called when queried\r
+    def calc(op, lhs, rhs):\r
+        if op == '+': return lhs+rhs\r
+        elif op == '/': return lhs/rhs\r
+        elif op == '-': return lhs-rhs\r
+    assert [calc(row['op'], row['lhs'], row['rhs']) for row in math] == [4,1,3]\r
+\r
+    # field function is called while sorting\r
+    math.order_by = ('-rhs',)\r
+    assert [row['rhs'] for row in math] == [9,4,3]\r
+\r
+    # default function is called while sorting\r
+    math.order_by = ('sum',)\r
+    assert [row['sum'] for row in math] == [1,3,4]\r
+\r
+    # data function is called while sorting\r
+    math.order_by = ('sqrt',)\r
+    assert [row['sqrt'] for row in math] == [1,1,2]\r
+\r
+\r
+# TODO: all the column stuff might warrant it's own test file\r
+def test_columns():\r
+    """Test Table.columns container functionality.\r
+    """\r
+\r
+    class BookTable(tables.MemoryTable):\r
+        id = tables.Column(sortable=False, visible=False)\r
+        name = tables.Column(sortable=True)\r
+        pages = tables.Column(sortable=True)\r
+        language = tables.Column(sortable=False)\r
+    books = BookTable([])\r
+\r
+    assert list(books.columns.sortable()) == [c for c in books.columns if c.sortable]\r
+\r
+    # .columns iterator only yields visible columns\r
+    assert len(list(books.columns)) == 3\r
+    # visiblity of columns can be changed at instance-time\r
+    books.columns['id'].visible = True\r
+    assert len(list(books.columns)) == 4\r
+\r
+\r
+def test_column_order():\r
+    """Test the order functionality of bound columns.\r
+    """\r
+\r
+    class BookTable(tables.MemoryTable):\r
+        id = tables.Column()\r
+        name = tables.Column()\r
+        pages = tables.Column()\r
+        language = tables.Column()\r
+    books = BookTable([])\r
+\r
+    # the basic name property is a no-brainer\r
+    books.order_by = ''\r
+    assert [c.name for c in books.columns] == ['id','name','pages','language']\r
+\r
+    # name_reversed will always reverse, no matter what\r
+    for test in ['', 'name', '-name']:\r
+        books.order_by = test\r
+        assert [c.name_reversed for c in books.columns] == ['-id','-name','-pages','-language']\r
+\r
+    # name_toggled will always toggle\r
+    books.order_by = ''\r
+    assert [c.name_toggled for c in books.columns] == ['id','name','pages','language']\r
+    books.order_by = 'id'\r
+    assert [c.name_toggled for c in books.columns] == ['-id','name','pages','language']\r
+    books.order_by = '-name'\r
+    assert [c.name_toggled for c in books.columns] == ['id','name','pages','language']\r
+    # other columns in an order_by will be dismissed\r
+    books.order_by = '-id,name'\r
+    assert [c.name_toggled for c in books.columns] == ['id','-name','pages','language']\r
+\r
+    # with multi-column order, this is slightly more complex\r
+    books.order_by =  ''\r
+    assert [str(c.order_by) for c in books.columns] == ['id','name','pages','language']\r
+    assert [str(c.order_by_reversed) for c in books.columns] == ['-id','-name','-pages','-language']\r
+    assert [str(c.order_by_toggled) for c in books.columns] == ['id','name','pages','language']\r
+    books.order_by =  'id'\r
+    assert [str(c.order_by) for c in books.columns] == ['id','id,name','id,pages','id,language']\r
+    assert [str(c.order_by_reversed) for c in books.columns] == ['-id','id,-name','id,-pages','id,-language']\r
+    assert [str(c.order_by_toggled) for c in books.columns] == ['-id','id,name','id,pages','id,language']\r
+    books.order_by =  '-pages,id'\r
+    assert [str(c.order_by) for c in books.columns] == ['-pages,id','-pages,id,name','pages,id','-pages,id,language']\r
+    assert [str(c.order_by_reversed) for c in books.columns] == ['-pages,-id','-pages,id,-name','-pages,id','-pages,id,-language']\r
+    assert [str(c.order_by_toggled) for c in books.columns] == ['-pages,-id','-pages,id,name','pages,id','-pages,id,language']\r
+\r
+    # querying whether a column is ordered is possible\r
+    books.order_by = ''\r
+    assert [c.is_ordered for c in books.columns] == [False, False, False, False]\r
+    books.order_by = 'name'\r
+    assert [c.is_ordered for c in books.columns] == [False, True, False, False]\r
+    assert [c.is_ordered_reverse for c in books.columns] == [False, False, False, False]\r
+    assert [c.is_ordered_straight for c in books.columns] == [False, True, False, False]\r
+    books.order_by = '-pages'\r
+    assert [c.is_ordered for c in books.columns] == [False, False, True, False]\r
+    assert [c.is_ordered_reverse for c in books.columns] == [False, False, True, False]\r
+    assert [c.is_ordered_straight for c in books.columns] == [False, False, False, False]\r
+    # and even works with multi-column ordering\r
+    books.order_by = 'id,-pages'\r
+    assert [c.is_ordered for c in books.columns] == [True, False, True, False]\r
+    assert [c.is_ordered_reverse for c in books.columns] == [False, False, True, False]\r
+    assert [c.is_ordered_straight for c in books.columns] == [True, False, False, False]
\ No newline at end of file
index 8ac15b7fc1454f6232b6a1466c723e4958d0cdde..21b8a0ba3b373f18b98ff620b0cd45d9f26e2ebb 100644 (file)
@@ -11,7 +11,7 @@ from django.http import HttpRequest
 import django_tables as tables\r
 \r
 def test_order_by():\r
-    class BookTable(tables.Table):\r
+    class BookTable(tables.MemoryTable):\r
         id = tables.Column()\r
         name = tables.Column()\r
     books = BookTable([\r
@@ -25,7 +25,7 @@ def test_order_by():
     assert str(books.order_by) == 'name,-id'\r
 \r
 def test_columns_and_rows():\r
-    class CountryTable(tables.Table):\r
+    class CountryTable(tables.MemoryTable):\r
         name = tables.TextColumn()\r
         capital = tables.TextColumn(sortable=False)\r
         population = tables.NumberColumn(verbose_name="Population Size")\r
@@ -71,7 +71,7 @@ def test_columns_and_rows():
 def test_render():\r
     """For good measure, render some actual templates."""\r
 \r
-    class CountryTable(tables.Table):\r
+    class CountryTable(tables.MemoryTable):\r
         name = tables.TextColumn()\r
         capital = tables.TextColumn()\r
         population = tables.NumberColumn(verbose_name="Population Size")\r
@@ -101,7 +101,7 @@ def test_templatetags():
     add_to_builtins('django_tables.app.templatetags.tables')\r
 \r
     # [bug] set url param tag handles an order_by tuple with multiple columns\r
-    class MyTable(tables.Table):\r
+    class MyTable(tables.MemoryTable):\r
         f1 = tables.Column()\r
         f2 = tables.Column()\r
     t = Template('{% set_url_param x=table.order_by %}')\r