class BoundColumns(object):
"""
- Container for spawning BoundColumns.
+ Container for spawning :class:`.BoundColumn` objects.
This is bound to a table and provides its :attr:`.Table.columns` property.
It provides access to those columns in different ways (iterator,
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
+ A ``BoundColumns`` object is a container for holding
+ ``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
+ ``dict``. ``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
Exposes a consistent API for :term:`table data`. It currently supports a
:class:`QuerySet`, or a :class:`list` of :class:`dict` objects.
- This class is used by :class:.Table` to wrap any
+ This class is used by :class:`.Table` to wrap any
input table data.
"""
as well.
"""
- def __new__(cls, name, bases, attrs, parent_cols_from=None):
+ def __new__(cls, name, bases, attrs):
"""Ughhh document this :)"""
# extract declared columns
columns = [(name, attrs.pop(name)) for name, column in attrs.items()
# well. Note that we loop over the bases in *reverse* - this is
# necessary to preserve the correct order of columns.
for base in bases[::-1]:
- cols_attr = (parent_cols_from if (parent_cols_from and
- hasattr(base, parent_cols_from))
- else 'base_columns')
- if hasattr(base, cols_attr):
- columns = getattr(base, cols_attr).items() + columns
+ if hasattr(base, "base_columns"):
+ columns = base.base_columns.items() + columns
# Note that we are reusing an existing ``base_columns`` attribute.
# This is because in certain inheritance cases (mixing normal and
# ModelTables) this metaclass might be executed twice, and we need
# to avoid overriding previous data (because we pop() from attrs,
# the second time around columns might not be registered again).
# An example would be:
- # class MyNewTable(MyOldNonModelTable, tables.ModelTable): pass
- if not 'base_columns' in attrs:
- attrs['base_columns'] = SortedDict()
- attrs['base_columns'].update(SortedDict(columns))
- attrs['_meta'] = TableOptions(attrs.get('Meta', None))
+ # class MyNewTable(MyOldNonTable, tables.Table): pass
+ if not "base_columns" in attrs:
+ attrs["base_columns"] = SortedDict()
+ attrs["base_columns"].update(SortedDict(columns))
+ attrs["_meta"] = opts = TableOptions(attrs.get("Meta", None))
+ for ex in opts.exclude:
+ if ex in attrs["base_columns"]:
+ attrs["base_columns"].pop(ex)
return type.__new__(cls, name, bases, attrs)
"""
Extracts and exposes options for a :class:`.Table` from a ``class Meta``
when the table is defined.
+
+ :param options: options for a table
+ :type options: :class:`Meta` on a :class:`.Table`
"""
def __init__(self, options=None):
- """
-
- :param options: options for a table
- :type options: :class:`Meta` on a :class:`.Table`
-
- """
super(TableOptions, self).__init__()
- self.sortable = getattr(options, 'sortable', True)
- order_by = getattr(options, 'order_by', ())
+ self.attrs = AttributeDict(getattr(options, "attrs", {}))
+ self.empty_text = getattr(options, "empty_text", None)
+ self.exclude = getattr(options, "exclude", ())
+ order_by = getattr(options, "order_by", ())
if isinstance(order_by, basestring):
order_by = (order_by, )
self.order_by = OrderByTuple(order_by)
- self.attrs = AttributeDict(getattr(options, 'attrs', {}))
- self.empty_text = getattr(options, 'empty_text', None)
+ self.sortable = getattr(options, "sortable", True)
class Table(StrAndUnicode):
__metaclass__ = DeclarativeColumnsMetaclass
TableDataClass = TableData
- def __init__(self, data, order_by=None, sortable=None, empty_text=None):
- self._rows = BoundRows(self) # bound rows
- self._columns = BoundColumns(self) # bound columns
+ def __init__(self, data, order_by=None, sortable=None, empty_text=None,
+ exclude=None):
+ self._rows = BoundRows(self)
+ self._columns = BoundColumns(self)
self._data = self.TableDataClass(data=data, table=self)
self.empty_text = empty_text
self.sortable = sortable
self.order_by = self._meta.order_by
else:
self.order_by = order_by
-
# 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.
self.base_columns = copy.deepcopy(type(self).base_columns)
+ self.exclude = exclude or ()
+ for ex in self.exclude:
+ if ex in self.base_columns:
+ self.base_columns.pop(ex)
def __unicode__(self):
return self.as_html()
.. class:: Table.Meta
+ Provides a way to define *global* settings for table, as opposed to
+ defining them for each instance.
+
.. attribute:: attrs
Allows custom HTML attributes to be specified which will be added to
:meth:`~django_tables.tables.Table.as_html` or the
:ref:`template-tags.render_table` template tag.
+ This is typically used to enable a theme for a table (which is done by
+ adding a CSS class to the ``<table>`` element). i.e.::
+
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+
+ class Meta:
+ attrs = {"class": "paleblue"}
+
+ :type: ``dict``
+
Default: ``{}``
- :type: :class:`dict`
+ .. attribute:: empty_text
+
+ Defines the text to display when the table has no rows.
+
+ .. attribute:: exclude
+
+ Defines which columns should be excluded from the table. This is useful
+ in subclasses to exclude columns in a parent. e.g.
+
+ >>> class Person(tables.Table):
+ ... first_name = tables.Column()
+ ... last_name = tables.Column()
+ ...
+ >>> Person.base_columns
+ {'first_name': <django_tables.columns.Column object at 0x10046df10>,
+ 'last_name': <django_tables.columns.Column object at 0x10046d8d0>}
+ >>> class ForgetfulPerson(Person):
+ ... class Meta:
+ ... exclude = ("last_name", )
+ ...
+ >>> ForgetfulPerson.base_columns
+ {'first_name': <django_tables.columns.Column object at 0x10046df10>}
+
+ :type: tuple of ``string`` objects
+
+ Default: ``()``
+
+ .. attribute:: order_by
+
+ The default ordering. e.g. ``('name', '-age')``. A hyphen ``-`` can be
+ used to prefix a column name to indicate *descending* order.
+
+ :type: :class:`tuple`
+
+ Default: ``()``
.. attribute:: sortable
an easy mechanism to disable sorting on an entire table, without adding
``sortable=False`` to each ``Column`` in a ``Table``.
- Default: :const:`True`
-
:type: :class:`bool`
- .. attribute:: order_by
-
- The default ordering. e.g. ``('name', '-age')``
-
- Default: ``()``
-
- :type: :class:`tuple`
+ Default: :const:`True`
:class:`TableData` Objects:
:members: __init__, order_by, __getitem__, __len__
-:class:`TableOptions` Objects:
-------------------------------
-
-.. autoclass:: django_tables.tables.TableOptions
- :members:
-
-
:class:`Column` Objects:
------------------------
import django_tables as tables
from django_tables import utils
+
core = Tests()
-@core.context
-def context():
- class Context(object):
- memory_data = [
- {'i': 2, 'alpha': 'b', 'beta': 'b'},
- {'i': 1, 'alpha': 'a', 'beta': 'c'},
- {'i': 3, 'alpha': 'c', 'beta': 'a'},
- ]
+class UnsortedTable(tables.Table):
+ i = tables.Column()
+ alpha = tables.Column()
+ beta = tables.Column()
- class UnsortedTable(tables.Table):
- i = tables.Column()
- alpha = tables.Column()
- beta = tables.Column()
- class SortedTable(UnsortedTable):
- class Meta:
- order_by = 'alpha'
+class SortedTable(UnsortedTable):
+ class Meta:
+ order_by = 'alpha'
- table = UnsortedTable(memory_data)
- yield Context
+MEMORY_DATA = [
+ {'i': 2, 'alpha': 'b', 'beta': 'b'},
+ {'i': 1, 'alpha': 'a', 'beta': 'c'},
+ {'i': 3, 'alpha': 'c', 'beta': 'a'},
+]
@core.test
-def declarations(context):
+def declarations():
"""Test defining tables by declaration."""
class GeoAreaTable(tables.Table):
name = tables.Column()
@core.test
-def datasource_untouched(context):
+def datasource_untouched():
"""Ensure that data that is provided to the table (the datasource) is not
modified by table operations.
"""
- original_data = copy.deepcopy(context.memory_data)
+ original_data = copy.deepcopy(MEMORY_DATA)
- table = context.UnsortedTable(context.memory_data)
+ table = UnsortedTable(MEMORY_DATA)
table.order_by = 'i'
list(table.rows)
- assert context.memory_data == Assert(original_data)
+ assert MEMORY_DATA == Assert(original_data)
- table = context.UnsortedTable(context.memory_data)
+ table = UnsortedTable(MEMORY_DATA)
table.order_by = 'beta'
list(table.rows)
- assert context.memory_data == Assert(original_data)
+ assert MEMORY_DATA == Assert(original_data)
@core.test
-def sorting(ctx):
+def sorting():
# fallback to Table.Meta
- Assert(('alpha', )) == ctx.SortedTable([], order_by=None).order_by == ctx.SortedTable([]).order_by
+ Assert(('alpha', )) == SortedTable([], order_by=None).order_by == SortedTable([]).order_by
# values of order_by are wrapped in tuples before being returned
- Assert(ctx.SortedTable([], order_by='alpha').order_by) == ('alpha', )
- Assert(ctx.SortedTable([], order_by=('beta',)).order_by) == ('beta', )
+ Assert(SortedTable([], order_by='alpha').order_by) == ('alpha', )
+ Assert(SortedTable([], order_by=('beta',)).order_by) == ('beta', )
# "no sorting"
- table = ctx.SortedTable([])
+ table = SortedTable([])
table.order_by = []
- Assert(()) == table.order_by == ctx.SortedTable([], order_by=[]).order_by
+ Assert(()) == table.order_by == SortedTable([], order_by=[]).order_by
- table = ctx.SortedTable([])
+ table = SortedTable([])
table.order_by = ()
- Assert(()) == table.order_by == ctx.SortedTable([], order_by=()).order_by
+ Assert(()) == table.order_by == SortedTable([], order_by=()).order_by
- table = ctx.SortedTable([])
+ table = SortedTable([])
table.order_by = ''
- Assert(()) == table.order_by == ctx.SortedTable([], order_by='').order_by
+ Assert(()) == table.order_by == SortedTable([], order_by='').order_by
# apply a sorting
- table = ctx.UnsortedTable([])
+ table = UnsortedTable([])
table.order_by = 'alpha'
- Assert(('alpha', )) == ctx.UnsortedTable([], order_by='alpha').order_by == table.order_by
+ Assert(('alpha', )) == UnsortedTable([], order_by='alpha').order_by == table.order_by
- table = ctx.SortedTable([])
+ table = SortedTable([])
table.order_by = 'alpha'
- Assert(('alpha', )) == ctx.SortedTable([], order_by='alpha').order_by == table.order_by
+ Assert(('alpha', )) == SortedTable([], order_by='alpha').order_by == table.order_by
# let's check the data
- table = ctx.SortedTable(ctx.memory_data, order_by='beta')
+ table = SortedTable(MEMORY_DATA, order_by='beta')
Assert(3) == table.rows[0]['i']
# allow fallback to Table.Meta.order_by
- table = ctx.SortedTable(ctx.memory_data)
+ table = SortedTable(MEMORY_DATA)
Assert(1) == table.rows[0]['i']
# column's can't be sorted if they're not allowed to be
@core.test
-def column_count(context):
+def column_count():
class SimpleTable(tables.Table):
visible = tables.Column(visible=True)
hidden = tables.Column(visible=False)
@core.test
-def column_accessor(context):
- class SimpleTable(context.UnsortedTable):
+def column_accessor():
+ class SimpleTable(UnsortedTable):
col1 = tables.Column(accessor='alpha.upper.isupper')
col2 = tables.Column(accessor='alpha.upper')
- table = SimpleTable(context.memory_data)
+ table = SimpleTable(MEMORY_DATA)
row = table.rows[0]
Assert(row['col1']) is True
Assert(row['col2']) == 'B'
+@core.test
+def exclude_columns():
+ """
+ Defining ``Table.Meta.exclude`` or providing an ``exclude`` argument when
+ instantiating a table should have the same effect -- exclude those columns
+ from the table. It should have the same effect as not defining the
+ columns originally.
+ """
+ # Table(..., exclude=...)
+ table = UnsortedTable([], exclude=("i"))
+ Assert([c.name for c in table.columns]) == ["alpha", "beta"]
+
+ # Table.Meta: exclude=...
+ class PartialTable(UnsortedTable):
+ class Meta:
+ exclude = ("alpha", )
+ table = PartialTable([])
+ Assert([c.name for c in table.columns]) == ["i", "beta"]
+
+ # Inheritence -- exclude in parent, add in child
+ class AddonTable(PartialTable):
+ added = tables.Column()
+ table = AddonTable([])
+ Assert([c.name for c in table.columns]) == ["i", "beta", "added"]
+
+ # Inheritence -- exclude in child
+ class ExcludeTable(UnsortedTable):
+ added = tables.Column()
+ class Meta:
+ exclude = ("alpha", )
+ table = ExcludeTable([])
+ Assert([c.name for c in table.columns]) == ["i", "beta", "added"]
+
+
@core.test
def pagination():
class BookTable(tables.Table):
# create some sample data
data = []
for i in range(100):
- data.append({'name': 'Book No. %d' % i})
+ data.append({"name": "Book No. %d" % i})
books = BookTable(data)
# external paginator
# integrated paginator
books.paginate(page=1)
- Assert(hasattr(books, 'page')) is True
+ Assert(hasattr(books, "page")) is True
books.paginate(page=1, per_page=10)
Assert(len(list(books.page.object_list))) == 10