Skip to content

Commit

Permalink
Rendered tables now include empty_text
Browse files Browse the repository at this point in the history
  • Loading branch information
bradleyayers committed Apr 10, 2011
1 parent 13a9014 commit d20176a
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 63 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -1,5 +1,6 @@
*.pyc
/*.komodoproject
/*.tmproj
/*.egg-info/
/*.egg
/MANIFEST
Expand Down
77 changes: 32 additions & 45 deletions django_tables/columns.py
Expand Up @@ -297,32 +297,30 @@ def render(self, record, table, **kwargs):


class BoundColumn(object):
"""A *runtime* version of :class:`Column`. The difference between
:class:`BoundColumn` and :class:`Column`, is that :class:`BoundColumn`
objects are of the relationship between a :class:`Column` and a
:class:`Table`. This means that it knows the *name* given to the
:class:`Column`.
"""A *runtime* version of :class:`.Column`. The difference between
``BoundColumn`` and ``Column``, is that ``BoundColumn`` objects are of
the relationship between a ``Column`` and a :class:`.Table`. This
means that it knows the *name* given to the ``Column``.
For convenience, all :class:`Column` properties are available from this
For convenience, all :class:`.Column` properties are available from this
class.
:type table: :class:`Table` object
:type table: :class:`.Table` object
:param table: the table in which this column exists
:type column: :class:`Column` object
:type column: :class:`.Column` object
:param column: the type of column
:type name: :class:`basestring` object
:type name: ``basestring`` object
:param name: the variable name of the column used to when defining the
:class:`Table`. Example:
:class:`.Table`. Example:
.. code-block:: python
class SimpleTable(tables.Table):
age = tables.Column()
`age` is the name.
``age`` is the name.
"""
def __init__(self, table, column, name):
Expand All @@ -333,11 +331,6 @@ def __init__(self, table, column, name):
def __unicode__(self):
return self.verbose_name

@property
def column(self):
"""Returns the :class:`Column` object for this column."""
return self._column

@property
def accessor(self):
"""
Expand All @@ -347,6 +340,11 @@ def accessor(self):
"""
return self.column.accessor or A(self.name)

@property
def column(self):
"""Returns the :class:`.Column` object for this column."""
return self._column

@property
def default(self):
"""Returns the default value for this column."""
Expand All @@ -355,8 +353,7 @@ def default(self):
@property
def header(self):
"""
Return the value that should be used in the header cell for this
column.
The value that should be used in the header cell for this column.
"""
return self.column.header or self.verbose_name
Expand All @@ -370,7 +367,7 @@ def name(self):
def order_by(self):
"""
If this column is sorted, return the associated :class:`.OrderBy`
instance, otherwise :const:`None`.
instance, otherwise ``None``.
"""
try:
Expand All @@ -380,17 +377,10 @@ def order_by(self):

@property
def sortable(self):
"""
Return a :class:`bool` depending on whether this column is
sortable.
"""
"""Return a ``bool`` depending on whether this column is sortable."""
if self.column.sortable is not None:
return self.column.sortable
elif self.table._meta.sortable is not None:
return self.table._meta.sortable
else:
return True # the default value
return self.table.sortable

@property
def table(self):
Expand Down Expand Up @@ -425,11 +415,11 @@ class BoundColumns(object):
item-based, filtered and unfiltered etc), stuff that would not be possible
with a simple iterator in the table class.
A :class:`BoundColumns` object is a container for holding
:class:`BoundColumn` objects. It provides methods that make accessing
columns easier than if they were stored in a :class:`list` or
:class:`dict`. :class:`Columns` has a similar API to a :class:`dict` (it
actually uses a :class:`SortedDict` interally).
A :class:`.BoundColumns` object is a container for holding
:class:`.BoundColumn` objects. It provides methods that make accessing
columns easier than if they were stored in a ``list`` or
:class:`dict`. :class:`Columns` has a similar API to a ``dict`` (it
actually uses a ``SortedDict`` interally).
At the moment you'll only come across this class when you access a
:attr:`.Table.columns` property.
Expand Down Expand Up @@ -480,16 +470,10 @@ def items(self):
for r in self._columns.items():
yield r

def names(self):
"""Return an iterator of column names."""
self._spawn_columns()
for r in self._columns.keys():
yield r

def sortable(self):
"""
Same as :meth:`.BoundColumns.all` but only returns sortable :class:`BoundColumn`
objects.
Same as :meth:`.BoundColumns.all` but only returns sortable
:class:`.BoundColumn` objects.
This is useful in templates, where iterating over the full
set and checking ``{% if column.sortable %}`` can be problematic in
Expand Down Expand Up @@ -518,15 +502,18 @@ def __iter__(self):
return self.visible()

def __contains__(self, item):
"""Check if a column is contained within a :class:`Columns` object.
"""Check if a column is contained within a :class:`.Columns` object.
*item* can either be a :class:`BoundColumn` object, or the name of a
*item* can either be a :class:`.BoundColumn` object, or the name of a
column.
"""
self._spawn_columns()
if isinstance(item, basestring):
return item in self.names()
for key in self._columns.keys():
if item == key:
return True
return False
else:
return item in self.all()

Expand Down
22 changes: 11 additions & 11 deletions django_tables/tables.py
Expand Up @@ -83,6 +83,7 @@ def __iter__(self):
def __getitem__(self, index):
"""Forwards indexing accesses to underlying data"""
return (self.list if hasattr(self, 'list') else self.queryset)[index]



class DeclarativeColumnsMetaclass(type):
Expand Down Expand Up @@ -138,7 +139,7 @@ def __init__(self, options=None):
"""
super(TableOptions, self).__init__()
self.sortable = getattr(options, 'sortable', None)
self.sortable = getattr(options, 'sortable', True)
order_by = getattr(options, 'order_by', ())
if isinstance(order_by, basestring):
order_by = (order_by, )
Expand Down Expand Up @@ -184,19 +185,16 @@ def __init__(self, data, order_by=None, sortable=None, empty_text=None):
self._rows = BoundRows(self) # bound rows
self._columns = BoundColumns(self) # bound columns
self._data = self.TableDataClass(data=data, table=self)

self.empty_text = empty_text
self.sortable = sortable
if order_by is None:
self.order_by = self._meta.order_by
else:
self.order_by = order_by

self.sortable = sortable
self.empty_text = empty_text

# Make a copy so that modifying this will not touch the class
# definition. Note that this is different from forms, where the
# copy is made available in a ``fields`` attribute. See the
# ``Table`` class docstring for more information.
# copy is made available in a ``fields`` attribute.
self.base_columns = copy.deepcopy(type(self).base_columns)

def __unicode__(self):
Expand All @@ -217,15 +215,17 @@ def order_by(self, value):
of column names.
"""
# accept both string and tuple instructions
# accept string
order_by = value.split(',') if isinstance(value, basestring) else value
# accept None
order_by = () if order_by is None else order_by
new = []
# validate, raise exception on failure
# everything's been converted to a iterable, accept iterable!
for o in order_by:
name = OrderBy(o).bare
ob = OrderBy(o)
name = ob.bare
if name in self.columns and self.columns[name].sortable:
new.append(o)
new.append(ob)
order_by = OrderByTuple(new)
self._order_by = order_by
self._data.order_by(order_by)
Expand Down
4 changes: 4 additions & 0 deletions django_tables/templates/django_tables/basic_table.html
Expand Up @@ -14,6 +14,10 @@
<td>{{ value }}</td>
{% endfor %}
</tr>
{% empty %}
{% if table.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endif %}
{% endfor %}
</tbody>
</table>
Expand Down
4 changes: 4 additions & 0 deletions django_tables/templates/django_tables/table.html
Expand Up @@ -24,6 +24,10 @@
<td>{{ cell }}</td>
{% endfor %}
</tr>
{% empty %}
{% if table.empty_text %}
<tr><td colspan="{{ table.columns|length }}">{{ table.empty_text }}</td></tr>
{% endif %}
{% endfor %}
</tbody>
</table>
Expand Down
7 changes: 6 additions & 1 deletion docs/index.rst
Expand Up @@ -486,7 +486,12 @@ API Reference

.. attribute:: sortable

Does the table support ordering?
The default value for determining if a :class:`.Column` is sortable.

If the ``Table`` and ``Column`` don't specify a value, a column's
``sortable`` value will fallback to this. object specify. This provides
an easy mechanism to disable sorting on an entire table, without adding
``sortable=False`` to each ``Column`` in a ``Table``.

Default: :const:`True`

Expand Down
3 changes: 2 additions & 1 deletion tests/__init__.py
Expand Up @@ -27,6 +27,7 @@
from .models import models
from .utils import utils
from .rows import rows
from .columns import columns


everything = Tests([core, templates, models, utils, rows])
everything = Tests([core, templates, models, utils, rows, columns])
30 changes: 30 additions & 0 deletions tests/columns.py
@@ -0,0 +1,30 @@
"""Test the core table functionality."""
from attest import Tests, Assert
import django_tables as tables
from django_tables import utils


columns = Tests()


@columns.test
def sortable():
class SimpleTable(tables.Table):
name = tables.Column()
Assert(SimpleTable([]).columns['name'].sortable) is True

class SimpleTable(tables.Table):
name = tables.Column()

class Meta:
sortable = False
Assert(SimpleTable([]).columns['name'].sortable) is False

class SimpleTable(tables.Table):
name = tables.Column()

class Meta:
sortable = True
Assert(SimpleTable([]).columns['name'].sortable) is True


49 changes: 49 additions & 0 deletions tests/core.py
Expand Up @@ -117,6 +117,34 @@ def sorting(ctx):
table = ctx.SortedTable(ctx.memory_data)
Assert(1) == table.rows[0]['i']

# column's can't be sorted if they're not allowed to be
class TestTable(tables.Table):
a = tables.Column(sortable=False)
b = tables.Column()

table = TestTable([], order_by='a')
Assert(table.order_by) == ()

table = TestTable([], order_by='b')
Assert(table.order_by) == ('b', )

# sorting disabled by default
class TestTable(tables.Table):
a = tables.Column(sortable=True)
b = tables.Column()

class Meta:
sortable = False

table = TestTable([], order_by='a')
Assert(table.order_by) == ('a', )

table = TestTable([], order_by='b')
Assert(table.order_by) == ()

table = TestTable([], sortable=True, order_by='b')
Assert(table.order_by) == ('b', )


@core.test
def column_count(context):
Expand Down Expand Up @@ -173,3 +201,24 @@ class BookTable(tables.Table):
with Assert.raises(Http404) as error:
books.paginate(Paginator, page=9999, per_page=10)
books.paginate(Paginator, page='abc', per_page=10)


@core.test
def empty_text():
class TestTable(tables.Table):
a = tables.Column()

table = TestTable([])
Assert(table.empty_text) is None

class TestTable(tables.Table):
a = tables.Column()

class Meta:
empty_text = 'nothing here'

table = TestTable([])
Assert(table.empty_text) == 'nothing here'

table = TestTable([], empty_text='still nothing')
Assert(table.empty_text) == 'still nothing'

0 comments on commit d20176a

Please sign in to comment.