Skip to content

Commit

Permalink
Include columns with visible=False in Table.as_values() fixex #676
Browse files Browse the repository at this point in the history
  • Loading branch information
jieter committed Jul 22, 2019
1 parent 3b437c9 commit 6428575
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 56 deletions.
55 changes: 19 additions & 36 deletions django_tables2/columns/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@


class Library:
"""
A collection of columns.
"""
"""A collection of columns."""

def __init__(self):
self.columns = []
Expand Down Expand Up @@ -61,9 +59,7 @@ def column_for_field(self, field):


class LinkTransform:
"""
Object used to generate attributes for the `<a>`-tag to wrap the cell content in.
"""
"""Object used to generate attributes for the `<a>`-tag to wrap the cell content in."""

viewname = None
accessor = None
Expand Down Expand Up @@ -199,6 +195,8 @@ class Column:
influence row ordering/sorting.
verbose_name (str): A human readable version of the column name.
visible (bool): If `True`, this column will be rendered.
Columns with `visible=False` will not be rendered, but will be included
in ``.Table.as_values()`` and thus also in :ref:`export`.
localize: If the cells in this column will be localized by the
`localize` filter:
Expand Down Expand Up @@ -706,16 +704,12 @@ def verbose_name(self):

@property
def visible(self):
"""
Returns a `bool` depending on whether this column is visible.
"""
"""Returns a `bool` depending on whether this column is visible."""
return self.column.visible

@property
def localize(self):
"""
Returns `True`, `False` or `None` as described in ``Column.localize``
"""
"""Returns `True`, `False` or `None` as described in ``Column.localize``"""
return self.column.localize


Expand Down Expand Up @@ -744,13 +738,13 @@ def __init__(self, table, base_columns):
self._table = table
self.columns = OrderedDict()
for name, column in base_columns.items():
self.columns[name] = bc = BoundColumn(table, column, name)
bc.render = getattr(table, "render_" + name, column.render)
self.columns[name] = bound_column = BoundColumn(table, column, name)
bound_column.render = getattr(table, "render_" + name, column.render)
# How the value is defined: 1. value_<name> 2. render_<name> 3. column.value.
bc.value = getattr(
bound_column.value = getattr(
table, "value_" + name, getattr(table, "render_" + name, column.value)
)
bc.order = getattr(table, "order_" + name, column.order)
bound_column.order = getattr(table, "order_" + name, column.order)

def iternames(self):
return (name for name, column in self.iteritems())
Expand All @@ -770,8 +764,7 @@ def all(self):

def iteritems(self):
"""
Return an iterator of ``(name, column)`` pairs (where ``column`` is a
`BoundColumn`).
Return an iterator of ``(name, column)`` pairs (where ``column`` is a `BoundColumn`).
This method is the mechanism for retrieving columns that takes into
consideration all of the ordering and filtering modifiers that a table
Expand All @@ -796,21 +789,14 @@ def iterorderable(self):
"""
return (x for x in self.iterall() if x.orderable)

def orderable(self):
return list(self.iterorderable())

def itervisible(self):
"""
Same as `.iterorderable` but only returns visible `.BoundColumn`
objects.
Same as `.iterorderable` but only returns visible `.BoundColumn` objects.
This is geared towards table rendering.
"""
return (x for x in self.iterall() if x.visible)

def visible(self):
return list(self.itervisible())

def hide(self, name):
"""
Hide a column.
Expand All @@ -830,9 +816,7 @@ def show(self, name):
self.columns[name].column.visible = True

def __iter__(self):
"""
Convenience API, alias of `.itervisible`.
"""
"""Convenience API, alias of `.itervisible`."""
return self.itervisible()

def __contains__(self, item):
Expand All @@ -848,11 +832,8 @@ def __contains__(self, item):
return item in self.iterall()

def __len__(self):
"""
Return how many `~.BoundColumn` objects are contained (and
visible).
"""
return len(self.visible())
"""Return how many `~.BoundColumn` objects are contained (and visible)."""
return len(list(self.itervisible()))

def __getitem__(self, index):
"""
Expand All @@ -875,7 +856,9 @@ def __getitem__(self, index):
if column.name == index:
return column
raise KeyError(
"Column with name '%s' does not exist; " "choices are: %s" % (index, self.names())
"Column with name '{}' does not exist; choices are: {}".format(index, self.names())
)
else:
raise TypeError("Column indices must be integers or str, not %s" % type(index).__name__)
raise TypeError(
"Column indices must be integers or str, not {}".format(type(index).__name__)
)
20 changes: 10 additions & 10 deletions django_tables2/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,23 +470,23 @@ def value_name(self, value):
will have a value wrapped in `<span>` in the rendered HTML, and just returns
the value when `as_values()` is called.
Note that any invisible columns will be part of the row iterator.
"""
if exclude_columns is None:
exclude_columns = ()

def excluded(column):
return column.column.exclude_from_export or column.name in exclude_columns

yield [
force_str(column.header, strings_only=True)
for column in self.columns
if not excluded(column)
columns = [
column
for column in self.columns.iterall()
if not (column.column.exclude_from_export or column.name in exclude_columns)
]

yield [force_str(column.header, strings_only=True) for column in columns]

for row in self.rows:
yield [
force_str(row.get_cell_value(column.name), strings_only=True)
for column in row.table.columns
if not excluded(column)
force_str(row.get_cell_value(column.name), strings_only=True) for column in columns
]

def has_footer(self):
Expand Down
13 changes: 11 additions & 2 deletions docs/pages/export.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,17 @@ If your custom column produces HTML, you should override this method and return
the actual value.


Excluding columns
-----------------
Including and excluding columns
-------------------------------

Some data might be rendered in the HTML version of the table using color coding,
but need a different representation in an export format. Use columns with `visible=False`
to include columns in the export, but not visible in the regular rendering::

class Table(tables.Table):
name = columns.Column(exclude_from_export=True)
first_name = columns.Column(visible=False)
last_name = columns.Column(visible=False)

Certain columns do not make sense while exporting data: you might show images or
have a column with buttons you want to exclude from the export.
Expand Down
17 changes: 10 additions & 7 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,8 @@ def test_omitting_data(self):
UnorderedTable()

def test_column_named_items(self):
"""
A column named items must not make the table fail
https://github.com/bradleyayers/django-tables2/issues/316
"""
"""A column named items must not make the table fail."""
# https://github.com/bradleyayers/django-tables2/issues/316

class ItemsTable(tables.Table):
items = tables.Column()
Expand Down Expand Up @@ -592,10 +590,15 @@ class Table(tables.Table):

self.assertEqual(list(Table([]).as_values()), [["Name"]])

def test_as_values_visible_False(self):
class Table(tables.Table):
name = tables.Column()
website = tables.Column(visible=False)

self.assertEqual(list(Table([]).as_values()), [["Name", "Website"]])

def test_as_values_empty_values(self):
"""
Table's as_values() method returns `None` for missing values
"""
"""Table's as_values() method returns `None` for missing values."""

class Table(tables.Table):
name = tables.Column()
Expand Down
29 changes: 28 additions & 1 deletion tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,9 +187,9 @@ class OccupationTable(tables.Table):


class OccupationView(ExportMixin, tables.SingleTableView):
model = Occupation
table_class = OccupationTable
table_pagination = {"per_page": 1}
model = Occupation
template_name = "django_tables2/bootstrap.html"


Expand Down Expand Up @@ -245,6 +245,33 @@ def get_queryset(self):
)
self.assertEqual(data, expected_csv)

def test_export_invisible_columns(self):
"""Verify columns with visible=False *do* get exported."""

DATA = [{"name": "Bess W. Fletcher", "website": "teammonka.com"}]

class Table(tables.Table):
name = tables.Column()
website = tables.Column(visible=False)

class View(ExportMixin, tables.SingleTableView):
table_class = Table
table_pagination = {"per_page": 1}
template_name = "django_tables2/bootstrap.html"

def get_queryset(self):
return DATA

response = View.as_view()(build_request())
self.assertNotContains(response, "teammonka.com")

response = View.as_view()(build_request("/?_export=csv"))

data = response.getvalue().decode()

expected_csv = "\r\n".join(("Name,Website", "Bess W. Fletcher,teammonka.com", ""))
self.assertEqual(data, expected_csv)

def test_should_work_with_foreign_key_fields(self):
class OccupationWithForeignKeyFieldsTable(tables.Table):
name = tables.Column()
Expand Down

0 comments on commit 6428575

Please sign in to comment.