Skip to content

Commit

Permalink
Added optional Table argument to add extra columns to an instance.
Browse files Browse the repository at this point in the history
Fixes #403, #70
  • Loading branch information
jieter committed May 24, 2017
1 parent 74b4843 commit 817d711
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 36 deletions.
4 changes: 2 additions & 2 deletions django_tables2/columns/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -533,10 +533,10 @@ class BoundColumns(object):
Arguments:
table (`.Table`): the table containing the columns
'''
def __init__(self, table):
def __init__(self, table, base_columns):
self._table = table
self.columns = OrderedDict()
for name, column in six.iteritems(table.base_columns):
for name, column in six.iteritems(base_columns):
self.columns[name] = bc = BoundColumn(table, column, name)
bc.render = getattr(table, 'render_' + name, column.render)
bc.order = getattr(table, 'order_' + name, column.order)
Expand Down
31 changes: 22 additions & 9 deletions django_tables2/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def __init__(self, data, table):
self.data = data
self.table = table

super(TableData, self).__init__()

def __getitem__(self, key):
'''
Slicing returns a new `.TableData` instance, indexing returns a
Expand Down Expand Up @@ -316,7 +318,6 @@ def __new__(mcs, name, bases, attrs):

if localize_column is not None:
base_columns[col_name].localize = localize_column

attrs['base_columns'] = base_columns
return super(DeclarativeColumnsMetaclass, mcs).__new__(mcs, name, bases, attrs)

Expand Down Expand Up @@ -422,12 +423,16 @@ class TableBase(object):
show_footer (bool): If `False`, the table footer will not be rendered,
even if some columns have a footer, defaults to `True`.
extra_columns (str, `.Column`): list of `(name, column)`-tuples containing
extra columns to add to the instance.
'''
def __init__(self, data, order_by=None, orderable=None, empty_text=None,
exclude=None, attrs=None, row_attrs=None, pinned_row_attrs=None,
sequence=None, prefix=None, order_by_field=None, page_field=None,
per_page_field=None, template=None, default=None, request=None,
show_header=None, show_footer=True):
show_header=None, show_footer=True, extra_columns=None):
super(TableBase, self).__init__()

self.exclude = exclude or self._meta.exclude
self.sequence = sequence
Expand Down Expand Up @@ -459,24 +464,29 @@ def __init__(self, data, order_by=None, orderable=None, empty_text=None,
# 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)
base_columns = copy.deepcopy(type(self).base_columns)

if extra_columns is not None:
for name, column in extra_columns:
base_columns[name] = column

# Keep fully expanded ``sequence`` at _sequence so it's easily accessible
# during render. The priority is as follows:
# 1. sequence passed in as an argument
# 2. sequence declared in ``Meta``
# 3. sequence defaults to '...'
if sequence is not None:
self._sequence = Sequence(sequence)
self._sequence.expand(self.base_columns.keys())
self._sequence.expand(base_columns.keys())
elif self._meta.sequence:
self._sequence = self._meta.sequence
else:
if self._meta.fields is not None:
self._sequence = Sequence(tuple(self._meta.fields) + ('...', ))
else:
self._sequence = Sequence(('...', ))
self._sequence.expand(self.base_columns.keys())
self.columns = columns.BoundColumns(self)
self._sequence.expand(base_columns.keys())
self.columns = columns.BoundColumns(self, base_columns)
# `None` value for order_by means no order is specified. This means we
# `shouldn't touch our data's ordering in any way. *However*
# `table.order_by = None` means "remove any ordering from the data"
Expand Down Expand Up @@ -739,6 +749,9 @@ def get_column_class_names(self, classes_set, bound_column):


# Python 2/3 compatible way to enable the metaclass
Table = DeclarativeColumnsMetaclass(str('Table'), (TableBase, ), {})
# ensure the Table class has the right class docstring
Table.__doc__ = TableBase.__doc__
@six.add_metaclass(DeclarativeColumnsMetaclass)
class Table(TableBase):
# ensure the Table class has the right class docstring
__doc__ = TableBase.__doc__

# Table = DeclarativeColumnsMetaclass(str('Table'), (TableBase, ), {})
42 changes: 18 additions & 24 deletions tests/test_dynamically_add_columns.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,33 @@
# coding: utf-8
from __future__ import absolute_import, unicode_literals

import pytest

import django_tables2 as tables


@pytest.mark.skip('not yet fixed, issue #403')
def test_dynamically_adding_columns():
class Table(tables.Table):
'''
This table allows adding columns while initializing the table.
'''
name = tables.Column()

def __init__(self, data, extra_columns=None, *args, **kwargs):
'''
Pass in a list of tuples of extra columns to add in the
format (colunm_name, column)
'''
if extra_columns:
for col_name, col in extra_columns:
self.base_columns[col_name] = col
super(Table, self).__init__(data, *args, **kwargs)
'''
When adding columns to self.base_columns, they are actually added to
the class attribute `Table.base_columns`, and not to the instance
attribute, `table.base_columns`
issue #403
'''
data = [
{'name': 'Adrian', 'country': 'Australia'},
{'name': 'Adrian', 'country': 'Brazil'},
{'name': 'Audrey', 'country': 'Chile'},
{'name': 'Bassie', 'country': 'Belgium'},
]
table = Table(data, extra_columns=[('country', tables.Column())])
assert table.columns.columns.keys() == ['name', 'country']

# a new instance should not have the extra columns added to the
# first instance.
table2 = Table(data)
assert table2.columns.columns.keys() == ['name']
class MyTable(tables.Table):
name = tables.Column()

# this is obvious:
assert list(MyTable(data).columns.columns.keys()) == ['name']

assert list(MyTable(data, extra_columns=[
('country', tables.Column())
]).columns.columns.keys()) == ['name', 'country']

# this new instance should not have the extra columns added to the first instance.
assert list(MyTable(data).columns.columns.keys()) == ['name']
2 changes: 1 addition & 1 deletion tests/test_faq.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def _test_counter(Table, expected='<td class="counter">0</td>'):
return html


def test_row_counter_render_method():
def test_row_counter_using_render_method():
class CountryTable(tables.Table):
counter = tables.Column(empty_values=(), orderable=False)
name = tables.Column()
Expand Down

1 comment on commit 817d711

@intiocean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Thanks @jieter - looks good

Please sign in to comment.