Configurable cell_class in DataTable

There are two changes in this proposal. One is to move the logic
for extracting cell data from the row datum (and handling auto columns)
to the Cell class, into a separate method for easy extending. The
second is to make that extending possible by making the cell class
configurable in the DataTable's meta.

Those changes are supposed to make it easier to extend the DataTable
with new kinds of cells -- for example, cells that contain an editable
widget, a progress bar, an icon, etc. as well as make it easier to
modify the behavior of the auto columns -- for example, by making it
possible to have some of the multi_select checkboxes preselected. Those
changes would also make it easier to integrate DataTable with a Django
FormSet.

Right now, in order to modify any of the proposed Cell.get_data
behavior, one has to make their own Row class and practically copy-paste
the whole Row.load_cells method, as there is no easy way to extend it.

Change-Id: I29277a9e77e1c413193fe80d3f8cfe001bf5d709
Closes-Bug: #1229677
This commit is contained in:
Radomir Dopieralski 2013-09-24 11:15:31 +02:00
parent 5cd6efb668
commit fdf920e714

View File

@ -500,44 +500,7 @@ class Row(html.HTMLElement):
datum = self.datum
cells = []
for column in table.columns.values():
if column.auto == "multi_select":
widget = forms.CheckboxInput(check_test=lambda value: False)
# Convert value to string to avoid accidental type conversion
data = widget.render('object_ids',
unicode(table.get_object_id(datum)),
{'class': 'table-row-multi-select'})
table._data_cache[column][table.get_object_id(datum)] = data
elif column.auto == "form_field":
widget = column.form_field
if issubclass(widget.__class__, forms.Field):
widget = widget.widget
widget_name = "%s__%s" % \
(column.name,
unicode(table.get_object_id(datum)))
# Create local copy of attributes, so it don't change column
# class form_field_attributes
form_field_attributes = {}
form_field_attributes.update(column.form_field_attributes)
# Adding id of the input so it pairs with label correctly
form_field_attributes['id'] = widget_name
data = widget.render(widget_name,
column.get_data(datum),
form_field_attributes)
table._data_cache[column][table.get_object_id(datum)] = data
elif column.auto == "actions":
data = table.render_row_actions(datum)
table._data_cache[column][table.get_object_id(datum)] = data
else:
data = column.get_data(datum)
cell = Cell(datum, data, column, self)
if cell.inline_edit_available:
cell.attrs['data-cell-name'] = column.name
cell.attrs['data-update-url'] = cell.get_ajax_update_url()
cell = table._meta.cell_class(datum, column, self)
cells.append((column.name or column.auto, cell))
self.cells = SortedDict(cells)
@ -609,13 +572,13 @@ class Row(html.HTMLElement):
class Cell(html.HTMLElement):
"""Represents a single cell in the table."""
def __init__(self, datum, data, column, row, attrs=None, classes=None):
def __init__(self, datum, column, row, attrs=None, classes=None):
self.classes = classes or getattr(self, "classes", [])
super(Cell, self).__init__()
self.attrs.update(attrs or {})
self.datum = datum
self.data = data
self.column = column
self.row = row
self.wrap_list = column.wrap_list
@ -623,7 +586,47 @@ class Cell(html.HTMLElement):
# initialize the update action if available
if self.inline_edit_available:
self.update_action = self.column.update_action()
self.attrs['data-cell-name'] = column.name
self.attrs['data-update-url'] = self.get_ajax_update_url()
self.inline_edit_mod = False
self.data = self.get_data(datum, column, row)
def get_data(self, datum, column, row):
"""Fetches the data to be displayed in this cell."""
table = row.table
if column.auto == "multi_select":
widget = forms.CheckboxInput(check_test=lambda value: False)
# Convert value to string to avoid accidental type conversion
data = widget.render('object_ids',
unicode(table.get_object_id(datum)),
{'class': 'table-row-multi-select'})
table._data_cache[column][table.get_object_id(datum)] = data
elif column.auto == "form_field":
widget = column.form_field
if issubclass(widget.__class__, forms.Field):
widget = widget.widget
widget_name = "%s__%s" % \
(column.name,
unicode(table.get_object_id(datum)))
# Create local copy of attributes, so it don't change column
# class form_field_attributes
form_field_attributes = {}
form_field_attributes.update(column.form_field_attributes)
# Adding id of the input so it pairs with label correctly
form_field_attributes['id'] = widget_name
data = widget.render(widget_name,
column.get_data(datum),
form_field_attributes)
table._data_cache[column][table.get_object_id(datum)] = data
elif column.auto == "actions":
data = table.render_row_actions(datum)
table._data_cache[column][table.get_object_id(datum)] = data
else:
data = column.get_data(datum)
return data
def __repr__(self):
return '<%s: %s, %s>' % (self.__class__.__name__,
@ -812,6 +815,11 @@ class DataTableOptions(object):
The row status is used by other Horizon components to trigger tasks
such as dynamic AJAX updating.
.. attribute:: cell_class
The class which should be used for rendering the cells of this table.
Optional. Default: :class:`~horizon.tables.Cell`.
.. attribute:: row_class
The class which should be used for rendering the rows of this table.
@ -857,6 +865,7 @@ class DataTableOptions(object):
self.status_columns = getattr(options, 'status_columns', [])
self.table_actions = getattr(options, 'table_actions', [])
self.row_actions = getattr(options, 'row_actions', [])
self.cell_class = getattr(options, 'cell_class', Cell)
self.row_class = getattr(options, 'row_class', Row)
self.column_class = getattr(options, 'column_class', Column)
self.pagination_param = getattr(options, 'pagination_param', 'marker')