Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cleanups + new test for export + invisible columns in export. #677

Merged
merged 5 commits into from
Jul 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
.python-version
.idea
*.sw[po]
pip-wheel-metadata
9 changes: 4 additions & 5 deletions django_tables2/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from .tables import Table, TableBase, table_factory
from .columns import (
BooleanColumn,
Column,
CheckBoxColumn,
Column,
DateColumn,
DateTimeColumn,
EmailColumn,
Expand All @@ -16,10 +15,10 @@
URLColumn,
)
from .config import RequestConfig
from .utils import A
from .paginators import LazyPaginator
from .views import SingleTableMixin, SingleTableView, MultiTableMixin

from .tables import Table, TableBase, table_factory
from .utils import A
from .views import MultiTableMixin, SingleTableMixin, SingleTableView

__version__ = "2.0.6"

Expand Down
4 changes: 2 additions & 2 deletions django_tables2/columns/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .base import library, BoundColumn, BoundColumns, Column
from .base import BoundColumn, BoundColumns, Column, library
from .booleancolumn import BooleanColumn
from .checkboxcolumn import CheckBoxColumn
from .datecolumn import DateColumn
Expand All @@ -9,8 +9,8 @@
from .linkcolumn import LinkColumn, RelatedLinkColumn
from .manytomanycolumn import ManyToManyColumn
from .templatecolumn import TemplateColumn
from .urlcolumn import URLColumn
from .timecolumn import TimeColumn
from .urlcolumn import URLColumn

__all__ = (
"library",
Expand Down
61 changes: 22 additions & 39 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 @@ -399,17 +397,17 @@ def order(self, queryset, is_descending):
@classmethod
def from_field(cls, field):
"""
Return a specialised column for the model field or `None`.
Return a specialized column for the model field or `None`.

Arguments:
field (Model Field instance): the field that needs a suitable column
Returns:
`.Column` object or `None`

If the column is not specialised for the given model field, it should
If the column is not specialized for the given model field, it should
return `None`. This gives other columns the opportunity to do better.

If the column is specialised, it should return an instance of itself
If the column is specialized, it should return an instance of itself
that is configured appropriately for the field.
"""
# Since this method is inherited by every subclass, only provide a
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__)
)
10 changes: 7 additions & 3 deletions django_tables2/export/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,17 @@ def __init__(self, export_format, table, exclude_columns=None):
raise TypeError('Export format "{}" is not supported.'.format(export_format))

self.format = export_format
self.dataset = self.table_to_dataset(table, exclude_columns)

self.dataset = Dataset()
def table_to_dataset(self, table, exclude_columns):
"""Transform a table to a tablib dataset."""
dataset = Dataset()
for i, row in enumerate(table.as_values(exclude_columns=exclude_columns)):
if i == 0:
self.dataset.headers = row
dataset.headers = row
else:
self.dataset.append(row)
dataset.append(row)
return dataset

@classmethod
def is_valid_format(self, export_format):
Expand Down
9 changes: 7 additions & 2 deletions django_tables2/export/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class ExportMixin:
`ExportMixin` looks for some attributes on the class to change it's behavior:

Attributes:
export_class (TableExport): Allows using a custom implementation of `TableExport`.
export_name (str): is the name of file that will be exported, without extension.
export_trigger_param (str): is the name of the GET attribute used to trigger
the export. It's value decides the export format, refer to
Expand All @@ -20,17 +21,21 @@ class ExportMixin:
class Table(tables.Table):
name = tables.Column()
buttons = tables.TemplateColumn(exclude_from_export=True, template_name=...)
export_formats (iterable): export formats to render a set of buttons in the template.
"""

export_class = TableExport
export_name = "table"
export_trigger_param = "_export"
exclude_columns = ()

export_formats = (TableExport.CSV,)

def get_export_filename(self, export_format):
return "{}.{}".format(self.export_name, export_format)

def create_export(self, export_format):
exporter = TableExport(
exporter = self.export_class(
export_format=export_format,
table=self.get_table(**self.get_table_kwargs()),
exclude_columns=self.exclude_columns,
Expand All @@ -40,7 +45,7 @@ def create_export(self, export_format):

def render_to_response(self, context, **kwargs):
export_format = self.request.GET.get(self.export_trigger_param, None)
if TableExport.is_valid_format(export_format):
if self.export_class.is_valid_format(export_format):
return self.create_export(export_format)

return super().render_to_response(context, **kwargs)
1 change: 0 additions & 1 deletion django_tables2/paginators.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

from django.core.paginator import EmptyPage, Page, PageNotAnInteger, Paginator
from django.utils.translation import gettext as _

Expand Down
24 changes: 11 additions & 13 deletions django_tables2/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,25 +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):
if column.column.exclude_from_export:
return True
return 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 Expand Up @@ -689,7 +687,7 @@ def get_column_class_names(self, classes_set, bound_column):

def table_factory(model, table=Table, fields=None, exclude=None, localize=None):
"""
Returns Table class for given `model`, equivalent to defining a custom table class::
Return Table class for given `model`, equivalent to defining a custom table class::

class MyTable(tables.Table):
class Meta:
Expand Down
2 changes: 1 addition & 1 deletion django_tables2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def get_table_pagination(self, table):
paginate["per_page"] = self.paginate_by
if hasattr(self, "paginator_class"):
paginate["paginator_class"] = self.paginator_class
if getattr(self, "paginate_orphans", 0) is not 0:
if getattr(self, "paginate_orphans", 0) != 0:
paginate["orphans"] = self.paginate_orphans

# table_pagination overrides any MultipleObjectMixin attributes
Expand Down
21 changes: 15 additions & 6 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 All @@ -94,7 +103,7 @@ You can define the columns you want to exclude in several ways::
exporter = TableExport("csv", table, exclude_columns=("image", "buttons"))


If you use the ``~.ExportMixin``, add an ``exclude_columns`` attribute to your class::
If you use the ``django_tables2.export.ExportMixin``, add an ``exclude_columns`` attribute to your class::

class TableView(ExportMixin, tables.SingleTableView):
table_class = MyTable
Expand All @@ -106,7 +115,7 @@ If you use the ``~.ExportMixin``, add an ``exclude_columns`` attribute to your c
Generating export URLs
----------------------

You can use the ``querystring`` template tag included with django_tables2
You can use the ``export_url`` template tag included with django_tables2
to render a link to export the data as ``csv``::

{% export_url "csv" %}
Expand All @@ -116,7 +125,7 @@ in combination when filtering table items.

If you want to render more than one button, you could use something like this::

{% for format in table.export_formats %}
{% for format in view.export_formats %}
<a href="{% export_url format %}">
download <code>.{{ format }}</code>
</a>
Expand All @@ -125,4 +134,4 @@ If you want to render more than one button, you could use something like this::
.. note::

This example assumes you define a list of possible
export formats on your table instance in attribute ``export_formats``
export formats on your view instance in attribute ``export_formats``.
2 changes: 1 addition & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
-r ../requirements/common.pip
Sphinx==1.6.5
Sphinx==2.1.2
sphinx_rtd_theme
recommonmark
django
Expand Down
1 change: 1 addition & 0 deletions docs/spelling_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ rst
Sapovits
screenshots
sortability
str
subclasses
subclassing
th
Expand Down
Loading