\r
pubdate = tables.Column(sortable=False)\r
\r
+Sorting is also affected by ``direction``, which can be used to change the\r
+*default* sort direction to descending. Note that this option only indirectly\r
+translates to the actual direction. Normal und reverse order, the terms\r
+django-tables exposes, now simply mean different things.\r
+\r
+ pubdate = tables.Column(direction='desc')\r
+\r
+Both variants are possible.\r
+\r
If you don't want to expose a column (but still require it to exist, for\r
example because it should be sortable nonetheless):\r
\r
to the user, set ``inaccessible`` to True.\r
\r
Setting ``sortable`` to False will result in this column being unusable\r
- in ordering.\r
+ in ordering. You can further change the *default* sort direction to\r
+ descending using ``direction``. Note that this option changes the actual\r
+ direction only indirectly. Normal und reverse order, the terms\r
+ django-tables exposes, now simply mean different things.\r
"""\r
\r
+ ASC = 1\r
+ DESC = 2\r
+\r
# Tracks each time a Column instance is created. Used to retain order.\r
creation_counter = 0\r
\r
def __init__(self, verbose_name=None, name=None, default=None, data=None,\r
- visible=True, inaccessible=False, sortable=None):\r
+ visible=True, inaccessible=False, sortable=None,\r
+ direction=ASC):\r
self.verbose_name = verbose_name\r
self.name = name\r
self.default = default\r
self.visible = visible\r
self.inaccessible = inaccessible\r
self.sortable = sortable\r
+ self.direction = direction\r
\r
self.creation_counter = Column.creation_counter\r
Column.creation_counter += 1\r
\r
+ def _set_direction(self, value):\r
+ if isinstance(value, basestring):\r
+ if value in ('asc', 'desc'):\r
+ self._direction = (value == 'asc') and Column.ASC or Column.DESC\r
+ else:\r
+ raise ValueError('Invalid direction value: %s' % value)\r
+ else:\r
+ self._direction = value\r
+\r
+ direction = property(lambda s: s._direction, _set_direction)\r
+\r
class TextColumn(Column):\r
pass\r
\r
from django.core.exceptions import FieldError\r
from django.utils.datastructures import SortedDict\r
from tables import BaseTable, DeclarativeColumnsMetaclass, \\r
- Column, BoundRow, Rows, TableOptions\r
+ Column, BoundRow, Rows, TableOptions, rmprefix, toggleprefix\r
\r
__all__ = ('BaseModelTable', 'ModelTable')\r
\r
\r
queryset = self.queryset\r
if self.order_by:\r
- queryset = queryset.order_by(*self._cols_to_fields(self.order_by))\r
+ actual_order_by = self._resolve_sort_directions(self.order_by)\r
+ queryset = queryset.order_by(*self._cols_to_fields(actual_order_by))\r
self._snapshot = queryset\r
\r
def _get_rows(self):\r
"""Normalize a column name by removing a potential sort prefix"""\r
return (s[:1]=='-' and [s[1:]] or [s])[0]\r
\r
+def toggleprefix(s):\r
+ """Remove - prefix is existing, or add if missing."""\r
+ return ((s[:1] == '-') and [s[1:]] or ["-"+s])[0]\r
+\r
class OrderByTuple(tuple, StrAndUnicode):\r
"""Stores 'order by' instructions; Used to render output in a format\r
we understand as input (see __unicode__) - especially useful in\r
or ((o[:1] == '-') and [o[1:]] or ["-"+o])\r
)[0]\r
for o in self]\r
- + # !!!: test for addition\r
+ +\r
[name for name in names if not name in self]\r
)\r
\r
row[name_in_source] = column.get_default(BoundRow(self, row))\r
\r
if self.order_by:\r
- sort_table(snapshot, self._cols_to_fields(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
\r
def _get_data(self):\r
return self._snapshot\r
data = property(lambda s: s._get_data())\r
\r
+ def _resolve_sort_directions(self, order_by):\r
+ """Given an ``order_by`` tuple, this will toggle the hyphen-prefixes\r
+ according to each column's ``direction`` option, e.g. it translates\r
+ between the ascending/descending and the straight/reverse terminology.\r
+ """\r
+ result = []\r
+ for inst in order_by:\r
+ if self.columns[rmprefix(inst)].column.direction == Column.DESC:\r
+ inst = toggleprefix(inst)\r
+ result.append(inst)\r
+ return result\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
\r
def test_sort():\r
class BookTable(tables.Table):\r
- id = tables.Column()\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
# 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
+ raises(ValueError, "books.base_columns['id'].direction = 'blub'")\r
+\r
# [bug] test alternative order formats if passed to constructor\r
BookTable([], 'language,-num_pages')\r
\r
def test_sort():\r
class CountryTable(tables.ModelTable):\r
tld = tables.Column(name="domain")\r
+ population = tables.Column()\r
system = tables.Column(default="republic")\r
custom1 = tables.Column()\r
custom2 = tables.Column(sortable=True)\r
model = Country\r
countries = CountryTable()\r
\r
- def test_order(order, result):\r
- countries.order_by = order\r
- assert [r['id'] for r in countries.rows] == result\r
+ def test_order(order, result, table=countries):\r
+ table.order_by = order\r
+ assert [r['id'] for r in table.rows] == result\r
\r
# test various orderings\r
test_order(('population',), [1,4,3,2])\r
# test multiple order instructions; note: one row is missing a "system"\r
# value, but has a default set; however, that has no effect on sorting.\r
test_order(('system', '-population'), [2,4,3,1])\r
- # using a simple string (for convinience as well as querystring passing\r
+ # using a simple string (for convinience as well as querystring passing)\r
test_order('-population', [2,3,4,1])\r
test_order('system,-population', [2,4,3,1])\r
\r
+ # test column with a default ``direction`` set to descending\r
+ class CityTable(tables.ModelTable):\r
+ name = tables.Column(direction='desc')\r
+ class Meta:\r
+ model = City\r
+ cities = CityTable()\r
+ test_order('name', [1,2], table=cities) # Berlin to Amsterdam\r
+ test_order('-name', [2,1], table=cities) # Amsterdam to Berlin\r
+\r
# test invalid order instructions...\r
countries.order_by = 'invalid_field,population'\r
assert countries.order_by == ('population',)\r