static tables were implemented incorrectly, were using column aliases to read from...
authorMichael Elsdörfer <michael@elsdoerfer.info>
Wed, 18 Jun 2008 12:43:57 +0000 (12:43 +0000)
committerMichael Elsdörfer <michael@elsdoerfer.info>
Wed, 18 Jun 2008 12:43:57 +0000 (12:43 +0000)
django_tables/models.py
django_tables/tables.py
tests/test_basic.py
tests/test_models.py
tests/test_templates.py

index ab40038909acf53cfc19b53869772b86d7da9c7f..1747442ee845358a98bbef43afebe5c304bc2fb5 100644 (file)
@@ -79,24 +79,6 @@ class BaseModelTable(BaseTable):
 \r
         super(BaseModelTable, self).__init__(self.queryset, *args, **kwargs)\r
 \r
-    def _cols_to_fields(self, names):\r
-        """Utility function. Given a list of column names (as exposed to the\r
-        user), converts overwritten column names to their corresponding model\r
-        field name.\r
-\r
-        Supports prefixed field names as used e.g. in order_by ("-field").\r
-        """\r
-        result = []\r
-        for ident in names:\r
-            if ident[:1] == '-':\r
-                name = ident[1:]\r
-                prefix = '-'\r
-            else:\r
-                name = ident\r
-                prefix = ''\r
-            result.append(prefix + self.columns[name].declared_name)\r
-        return result\r
-\r
     def _validate_column_name(self, name, purpose):\r
         """Overridden. Only allow model-based fields to be sorted."""\r
         if purpose == 'order_by':\r
index 556fff10eb74ca2ce1afc46a8e26f56cefccacee..eeecde011ca69003d8f879cf2c8acfef3d77ee02 100644 (file)
@@ -117,18 +117,34 @@ 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
+\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
         snapshot = copy.copy(self._data)\r
         for row in snapshot:\r
-            # delete unknown columns and add missing ones; note that\r
-            # self.columns already accounts for column name overrides.\r
-            for column in row.keys():\r
-                if not column in self.columns:\r
-                    del row[column]\r
-            for colname, colobj in self.columns.items():\r
-                if not colname in row:\r
-                    row[colname] = colobj.column.default\r
+            # delete unknown columns that are in the source data, but that\r
+            # we don't know about.\r
+            # TODO: does this even make sense? we might in some cases save\r
+            # memory, but at what performance cost?\r
+            decl_colnames = [c.declared_name for c in self.columns.all()]\r
+            for key in row.keys():\r
+                if not key in decl_colnames:\r
+                    del row[key]\r
+            # add data for defined columns that is missing from the source.\r
+            # we do this so that colunn default values can affect sorting,\r
+            # which is the current design decision.\r
+            for column in self.columns.all():\r
+                if not column.declared_name in row:\r
+                    row[column.declared_name] = column.column.default\r
+\r
         if self.order_by:\r
-            sort_table(snapshot, self.order_by)\r
+            sort_table(snapshot, self._cols_to_fields(self.order_by))\r
         self._snapshot = snapshot\r
 \r
     def _get_data(self):\r
@@ -137,6 +153,28 @@ class BaseTable(object):
         return self._snapshot\r
     data = property(lambda s: s._get_data())\r
 \r
+    def _cols_to_fields(self, names):\r
+        """Utility function. Given a list of column names (as exposed to\r
+        the user), converts column names to the names we have to use to\r
+        retrieve a column's data from the source.\r
+\r
+        Right now, the name used in the table declaration is used for\r
+        access, but a column can define it's own alias-like name that will\r
+        be used to refer to the column from outside.\r
+\r
+        Supports prefixed column names as used e.g. in order_by ("-field").\r
+        """\r
+        result = []\r
+        for ident in names:\r
+            if ident[:1] == '-':\r
+                name = ident[1:]\r
+                prefix = '-'\r
+            else:\r
+                name = ident\r
+                prefix = ''\r
+            result.append(prefix + self.columns[name].declared_name)\r
+        return result\r
+\r
     def _validate_column_name(self, name, purpose):\r
         """Return True/False, depending on whether the column ``name`` is\r
         valid for ``purpose``. Used to validate things like ``order_by``\r
@@ -160,7 +198,6 @@ class BaseTable(object):
         # validate, remove all invalid order instructions\r
         self._order_by = OrderByTuple([o for o in self._order_by\r
             if self._validate_column_name((o[:1]=='-' and [o[1:]] or [o])[0], "order_by")])\r
-        # TODO: optionally, throw an exception\r
     order_by = property(lambda s: s._order_by, _set_order_by)\r
 \r
     def __unicode__(self):\r
@@ -225,6 +262,11 @@ class Columns(object):
         self._columns = new_columns\r
 \r
     def all(self):\r
+        """Iterate through all columns, regardless of visiblity (as\r
+        opposed to ``__iter__``.\r
+\r
+        This is used internally a lot.\r
+        """\r
         self._spawn_columns()\r
         for column in self._columns.values():\r
             yield column\r
@@ -234,7 +276,7 @@ class Columns(object):
         for r in self._columns.items():\r
             yield r\r
 \r
-    def keys(self):\r
+    def names(self):\r
         self._spawn_columns()\r
         for r in self._columns.keys():\r
             yield r\r
@@ -244,6 +286,10 @@ class Columns(object):
         return self._columns.keyOrder.index(name)\r
 \r
     def __iter__(self):\r
+        """Iterate through all *visible* bound columns.\r
+\r
+        This is primarily geared towards table rendering.\r
+        """\r
         for column in self.all():\r
             if column.column.visible:\r
                 yield column\r
@@ -252,7 +298,7 @@ class Columns(object):
         """Check by both column object and column name."""\r
         self._spawn_columns()\r
         if isinstance(item, basestring):\r
-            return item in self.keys()\r
+            return item in self.names()\r
         else:\r
             return item in self.all()\r
 \r
@@ -266,7 +312,11 @@ class BoundColumn(StrAndUnicode):
     """'Runtime' version of ``Column`` that is bound to a table instance,\r
     and thus knows about the table's data.\r
 \r
-    Note name... TODO\r
+    Note that the name that is passed in tells us how this field is\r
+    delared in the bound table. The column itself can overwrite this name.\r
+    While the overwritten name will be hat mostly counts, we need to\r
+    remember the one used for declaration as well, or we won't know how\r
+    to read a column's value from the source.\r
     """\r
     def __init__(self, table, column, name):\r
         self.table = table\r
@@ -306,7 +356,7 @@ class BoundRow(object):
     def __getitem__(self, name):\r
         """Returns this row's value for a column. All other access methods,\r
         e.g. __iter__, lead ultimately to this."""\r
-        return self.data[name]\r
+        return self.data[self.table.columns[name].declared_name]\r
 \r
     def __contains__(self, item):\r
         """Check by both row object and column name."""\r
index dee5d6049b015bd1a1962aa69e49e32a4349d09e..7b5cfab70b408abd311dd6019fe748cb97e633dd 100644 (file)
@@ -87,10 +87,10 @@ def test_sort():
         language = tables.Column(default='en')  # default affects sorting\r
 \r
     books = BookTable([\r
-        {'id': 1, 'num_pages':  60, 'name': 'Z: The Book'},    # language: en\r
-        {'id': 2, 'num_pages': 100, 'language': 'de', 'name': 'A: The Book'},\r
-        {'id': 3, 'num_pages':  80, 'language': 'de', 'name': 'A: The Book, Vol. 2'},\r
-        {'id': 4, 'num_pages': 110, 'language': 'fr', 'name': 'A: The Book, French Edition'},\r
+        {'id': 1, 'pages':  60, 'name': 'Z: The Book'},    # language: en\r
+        {'id': 2, 'pages': 100, 'language': 'de', 'name': 'A: The Book'},\r
+        {'id': 3, 'pages':  80, 'language': 'de', 'name': 'A: The Book, Vol. 2'},\r
+        {'id': 4, 'pages': 110, 'language': 'fr', 'name': 'A: The Book, French Edition'},\r
     ])\r
 \r
     def test_order(order, result):\r
index eb32509bf787847b726cfe371b187a2365f4c1e2..9d9f3e437da59237e1ee611cb5e821d56edfad11 100644 (file)
@@ -144,11 +144,11 @@ def test_sort():
     test_order('-population', [2,3,4,1])\r
     test_order('system,-population', [2,4,3,1])\r
 \r
-    # test invalid order instructions\r
+    # test invalid order instructions...\r
     countries.order_by = 'invalid_field,population'\r
     assert countries.order_by == ('population',)\r
-    # ...for modeltables, this primarily means that only model-based colunns\r
-    # are currently sortable at all.\r
+    # ...in case of ModelTables, this primarily means that only\r
+    # model-based colunns are currently sortable at all.\r
     countries.order_by = ('custom1', 'custom2')\r
     assert countries.order_by == ()\r
 \r
@@ -161,4 +161,4 @@ def test_pagination():
 # TODO: manual base columns change -> update() call (add as example in docstr here) -> rebuild snapshot: is row cache, column cache etc. reset?\r
 # TODO: test that boundcolumn.name works with name overrides and without\r
 # TODO: more beautiful auto column names\r
-# TODO: normal tables should handle name overrides differently; the backend data should still use declared_name
\ No newline at end of file
+# TODO: throw an exception on invalid order_by
\ No newline at end of file
index 9c0852f64b13c42351c38784c72b2d890beab296..e49c16863ee2fa1e54dcfba1b0d83b064fc6e6a3 100644 (file)
@@ -67,7 +67,6 @@ def test_columns_and_rows():
     assert countries.columns['name'].visible == True\r
     assert countries.columns['tld'].visible == False\r
 \r
-\r
 def test_render():\r
     """For good measure, render some actual templates."""\r
 \r
@@ -80,10 +79,10 @@ def test_render():
         calling_code = tables.NumberColumn(name="cc", verbose_name="Phone Ext.")\r
 \r
     countries = CountryTable(\r
-        [{'name': 'Germany', 'capital': 'Berlin', 'population': 83, 'currency': 'Euro (€)', 'tld': 'de', 'cc': 49},\r
-         {'name': 'France', 'population': 64, 'currency': 'Euro (€)', 'tld': 'fr', 'cc': 33},\r
-         {'name': 'Netherlands', 'capital': 'Amsterdam', 'cc': '31'},\r
-         {'name': 'Austria', 'cc': 43, 'currency': 'Euro (€)', 'population': 8}])\r
+        [{'name': 'Germany', 'capital': 'Berlin', 'population': 83, 'currency': 'Euro (€)', 'tld': 'de', 'calling_code': 49},\r
+         {'name': 'France', 'population': 64, 'currency': 'Euro (€)', 'tld': 'fr', 'calling_code': 33},\r
+         {'name': 'Netherlands', 'capital': 'Amsterdam', 'calling_code': '31'},\r
+         {'name': 'Austria', 'calling_code': 43, 'currency': 'Euro (€)', 'population': 8}])\r
 \r
     from django.template import Template, Context\r
 \r