From: Michael Elsdoerfer Date: Sun, 29 Jun 2008 22:42:41 +0000 (+0200) Subject: added support for column default sort directions X-Git-Tag: 0.2~48 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=560516669c42fae3b72f2bfcd35915b7e48e01e0;p=django-tables2.git added support for column default sort directions --- diff --git a/README b/README index 71ae096..ecd8d07 100644 --- a/README +++ b/README @@ -307,6 +307,15 @@ If you don't want a column to be sortable by the user: pubdate = tables.Column(sortable=False) +Sorting is also affected by ``direction``, which can be used to change the +*default* sort direction to descending. Note that this option only indirectly +translates to the actual direction. Normal und reverse order, the terms +django-tables exposes, now simply mean different things. + + pubdate = tables.Column(direction='desc') + +Both variants are possible. + If you don't want to expose a column (but still require it to exist, for example because it should be sortable nonetheless): diff --git a/django_tables/columns.py b/django_tables/columns.py index b715f14..5c329cb 100644 --- a/django_tables/columns.py +++ b/django_tables/columns.py @@ -34,14 +34,21 @@ class Column(object): to the user, set ``inaccessible`` to True. Setting ``sortable`` to False will result in this column being unusable - in ordering. + in ordering. You can further change the *default* sort direction to + descending using ``direction``. Note that this option changes the actual + direction only indirectly. Normal und reverse order, the terms + django-tables exposes, now simply mean different things. """ + ASC = 1 + DESC = 2 + # Tracks each time a Column instance is created. Used to retain order. creation_counter = 0 def __init__(self, verbose_name=None, name=None, default=None, data=None, - visible=True, inaccessible=False, sortable=None): + visible=True, inaccessible=False, sortable=None, + direction=ASC): self.verbose_name = verbose_name self.name = name self.default = default @@ -49,10 +56,22 @@ class Column(object): self.visible = visible self.inaccessible = inaccessible self.sortable = sortable + self.direction = direction self.creation_counter = Column.creation_counter Column.creation_counter += 1 + def _set_direction(self, value): + if isinstance(value, basestring): + if value in ('asc', 'desc'): + self._direction = (value == 'asc') and Column.ASC or Column.DESC + else: + raise ValueError('Invalid direction value: %s' % value) + else: + self._direction = value + + direction = property(lambda s: s._direction, _set_direction) + class TextColumn(Column): pass diff --git a/django_tables/models.py b/django_tables/models.py index 3f4e794..5f07a44 100644 --- a/django_tables/models.py +++ b/django_tables/models.py @@ -1,7 +1,7 @@ from django.core.exceptions import FieldError from django.utils.datastructures import SortedDict from tables import BaseTable, DeclarativeColumnsMetaclass, \ - Column, BoundRow, Rows, TableOptions + Column, BoundRow, Rows, TableOptions, rmprefix, toggleprefix __all__ = ('BaseModelTable', 'ModelTable') @@ -122,7 +122,8 @@ class BaseModelTable(BaseTable): queryset = self.queryset if self.order_by: - queryset = queryset.order_by(*self._cols_to_fields(self.order_by)) + actual_order_by = self._resolve_sort_directions(self.order_by) + queryset = queryset.order_by(*self._cols_to_fields(actual_order_by)) self._snapshot = queryset def _get_rows(self): diff --git a/django_tables/tables.py b/django_tables/tables.py index 56a9e33..b7035c7 100644 --- a/django_tables/tables.py +++ b/django_tables/tables.py @@ -95,6 +95,10 @@ def rmprefix(s): """Normalize a column name by removing a potential sort prefix""" return (s[:1]=='-' and [s[1:]] or [s])[0] +def toggleprefix(s): + """Remove - prefix is existing, or add if missing.""" + return ((s[:1] == '-') and [s[1:]] or ["-"+s])[0] + class OrderByTuple(tuple, StrAndUnicode): """Stores 'order by' instructions; Used to render output in a format we understand as input (see __unicode__) - especially useful in @@ -169,7 +173,7 @@ class OrderByTuple(tuple, StrAndUnicode): or ((o[:1] == '-') and [o[1:]] or ["-"+o]) )[0] for o in self] - + # !!!: test for addition + + [name for name in names if not name in self] ) @@ -256,7 +260,8 @@ class BaseTable(object): row[name_in_source] = column.get_default(BoundRow(self, row)) if self.order_by: - sort_table(snapshot, self._cols_to_fields(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 def _get_data(self): @@ -265,6 +270,18 @@ class BaseTable(object): return self._snapshot data = property(lambda s: s._get_data()) + def _resolve_sort_directions(self, order_by): + """Given an ``order_by`` tuple, this will toggle the hyphen-prefixes + according to each column's ``direction`` option, e.g. it translates + between the ascending/descending and the straight/reverse terminology. + """ + result = [] + for inst in order_by: + if self.columns[rmprefix(inst)].column.direction == Column.DESC: + inst = toggleprefix(inst) + result.append(inst) + return result + def _cols_to_fields(self, names): """Utility function. Given a list of column names (as exposed to the user), converts column names to the names we have to use to diff --git a/tests/test_basic.py b/tests/test_basic.py index 48c67a3..f24ec58 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -154,7 +154,7 @@ def test_meta_sortable(): def test_sort(): class BookTable(tables.Table): - id = tables.Column() + id = tables.Column(direction='desc') name = tables.Column() pages = tables.Column(name='num_pages') # test rewritten names language = tables.Column(default='en') # default affects sorting @@ -195,6 +195,16 @@ def test_sort(): # sort by column with "data" option test_order('rating', [4,2,3,1]) + # test the column with a default ``direction`` set to descending + test_order('id', [4,3,2,1]) + test_order('-id', [1,2,3,4]) + # changing the direction afterwards is fine too + books.base_columns['id'].direction = 'asc' + test_order('id', [1,2,3,4]) + test_order('-id', [4,3,2,1]) + # a invalid direction string raises an exception + raises(ValueError, "books.base_columns['id'].direction = 'blub'") + # [bug] test alternative order formats if passed to constructor BookTable([], 'language,-num_pages') diff --git a/tests/test_models.py b/tests/test_models.py index cb7c3a6..a2aff04 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -162,6 +162,7 @@ def test_caches(): def test_sort(): class CountryTable(tables.ModelTable): tld = tables.Column(name="domain") + population = tables.Column() system = tables.Column(default="republic") custom1 = tables.Column() custom2 = tables.Column(sortable=True) @@ -169,9 +170,9 @@ def test_sort(): model = Country countries = CountryTable() - def test_order(order, result): - countries.order_by = order - assert [r['id'] for r in countries.rows] == result + def test_order(order, result, table=countries): + table.order_by = order + assert [r['id'] for r in table.rows] == result # test various orderings test_order(('population',), [1,4,3,2]) @@ -184,10 +185,19 @@ def test_sort(): # test multiple order instructions; note: one row is missing a "system" # value, but has a default set; however, that has no effect on sorting. test_order(('system', '-population'), [2,4,3,1]) - # using a simple string (for convinience as well as querystring passing + # using a simple string (for convinience as well as querystring passing) test_order('-population', [2,3,4,1]) test_order('system,-population', [2,4,3,1]) + # test column with a default ``direction`` set to descending + class CityTable(tables.ModelTable): + name = tables.Column(direction='desc') + class Meta: + model = City + cities = CityTable() + test_order('name', [1,2], table=cities) # Berlin to Amsterdam + test_order('-name', [2,1], table=cities) # Amsterdam to Berlin + # test invalid order instructions... countries.order_by = 'invalid_field,population' assert countries.order_by == ('population',)