Skip to content

Commit

Permalink
Configurable cell_class in DataTable
Browse files Browse the repository at this point in the history
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
  • Loading branch information
deshipu committed Dec 10, 2013
1 parent 5cd6efb commit fdf920e
Showing 1 changed file with 49 additions and 40 deletions.
89 changes: 49 additions & 40 deletions horizon/tables/base.py
Expand Up @@ -500,44 +500,7 @@ def load_cells(self, datum=None):
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)

Expand Down Expand Up @@ -609,21 +572,61 @@ def get_data(self, request, obj_id):

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
self.inline_edit_available = self.column.update_action is not None
# 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__,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -857,6 +865,7 @@ def __init__(self, options):
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')
Expand Down

0 comments on commit fdf920e

Please sign in to comment.