Skip to content

Commit

Permalink
Implemented Pinned Rows for issue: jieter#406
Browse files Browse the repository at this point in the history
  • Loading branch information
djk2 committed Jan 31, 2017
1 parent 77c46e6 commit 445903f
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 6 deletions.
83 changes: 80 additions & 3 deletions django_tables2/rows.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,24 +205,92 @@ def items(self):
yield (column, self.get_cell(column.name))


class BoundPinnedRow(BoundRow):

@property
def attrs(self):
'''
Return the attributes for a certain pinned row.
Add 'pinned-row' to css class attribute
'''
row_attrs = computed_values(self._table.pinned_row_attrs, self._record)
cssClass = "pinned-row"
cssClass = " ".join([
'even' if next(self._table._counter) % 2 == 0 else 'odd',
cssClass,
row_attrs.get('class', "")
])
row_attrs['class'] = cssClass
return AttributeDict(row_attrs)

def __iter__(self):
'''
Iterate over the rendered values for cells in the row.
Under the hood this method just makes a call to
`.BoundPinnedRow.__getitem__` for each cell.
'''
for column, value in self.items():
# this uses __getitem__, using the name (rather than the accessor)
# is correct – it's what __getitem__ expects.
yield value

def _get_and_render_with(self, name, render_func):
"""
Get raw value from record render
this value using by render_func
"""
accessor = A(name)
value = accessor.resolve(context=self._record, quiet=True) or self._table.default
return value


class BoundRows(object):
'''
Container for spawning `.BoundRow` objects.
Arguments:
data: iterable of records
table: the `~.Table` in which the rows exist
pinned_data: dictionary with iterable of records for top and/or
bottom pinned rows. Example
{
'top' : [ iterable structure ] | None,
'bottom' : [ iterable structure ] | None
}
This is used for `~.Table.rows`.
'''
def __init__(self, data, table):
def __init__(self, data, table, pinned_data=None):
self.data = data
self.table = table
self.top_pinned_data = pinned_data.get('top')
self.bottom_pinned_data = pinned_data.get('bottom')

def generator_pined_row(self, data):
"""
Generator for top and bottom pinned rows
"""
if data is not None:
if hasattr(data, '__iter__') is False:
raise ValueError('The data for pinned rows must be iterable')
else:
# If pinned data is iterable
for pinned_record in data:
yield BoundPinnedRow(pinned_record, table=self.table)

def __iter__(self):
# Top pinned rows
for pinned_record in self.generator_pined_row(self.top_pinned_data):
yield pinned_record

for record in self.data:
yield BoundRow(record, table=self.table)

# Bottom pinned rows
for pinned_record in self.generator_pined_row(self.bottom_pinned_data):
yield pinned_record

def __len__(self):
return len(self.data)

Expand All @@ -231,5 +299,14 @@ def __getitem__(self, key):
Slicing returns a new `~.BoundRows` instance, indexing returns a single
`~.BoundRow` instance.
'''
container = BoundRows if isinstance(key, slice) else BoundRow
return container(self.data[key], table=self.table)
if isinstance(key, slice):
return BoundRows(
self.data[key],
table=self.table,
pinned_data={
'top': self.top_pinned_data,
'bottom': self.bottom_pinned_data
}
)
else:
return BoundRow(self.data[key], table=self.table)
37 changes: 34 additions & 3 deletions django_tables2/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ def __init__(self, options=None):
super(TableOptions, self).__init__()
self.attrs = AttributeDict(getattr(options, 'attrs', {}))
self.row_attrs = getattr(options, 'row_attrs', {})
self.pinned_row_attrs = getattr(options, 'pinned_row_attrs', {})
self.default = getattr(options, 'default', '—')
self.empty_text = getattr(options, 'empty_text', None)
self.fields = getattr(options, 'fields', None)
Expand Down Expand Up @@ -308,6 +309,8 @@ class TableBase(object):
Allows custom HTML attributes to be specified which will be added
to the ``<tr>`` tag of the rendered table.
pinned_row_attrs: Same as row_attrs but for pinned rows
sequence (iterable): The sequence/order of columns the columns (from
left to right).
Expand Down Expand Up @@ -344,18 +347,28 @@ class TableBase(object):
TableDataClass = TableData

def __init__(self, data, order_by=None, orderable=None, empty_text=None,
exclude=None, attrs=None, row_attrs=None, sequence=None,
prefix=None, order_by_field=None, page_field=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):

super(TableBase, self).__init__()

self.exclude = exclude or self._meta.exclude
self.sequence = sequence
self.data = self.TableDataClass(data=data, table=self)
if default is None:
default = self._meta.default
self.default = default
self.rows = BoundRows(data=self.data, table=self)

# Pinned rows #406
self.pinned_row_attrs = AttributeDict(pinned_row_attrs or self._meta.pinned_row_attrs)
self.pinned_data = {
'top': self.get_top_pinned_data(),
'bottom': self.get_bottom_pinned_data()
}

self.rows = BoundRows(data=self.data, table=self, pinned_data=self.pinned_data)
attrs = computed_values(attrs if attrs is not None else self._meta.attrs)
self.attrs = AttributeDict(attrs)
self.row_attrs = AttributeDict(row_attrs or self._meta.row_attrs)
Expand Down Expand Up @@ -411,6 +424,24 @@ def __init__(self, data, order_by=None, orderable=None, empty_text=None,

self._counter = count()

def get_top_pinned_data(self):
"""
Return data for top pinned rows containing data for each row.
Iterable type like: queryset, list of dicts, list of objects.
Default return None and should be override in subclass of ~.Table\
For None value top pinned rows are not render.
"""
return None

def get_bottom_pinned_data(self):
"""
Return data for bottom pinned rows containing data for each row.
Iterable type like: queryset, list of dicts, list of objects.
Default return None and should be override in subclass of ~.Table\
For None value bottom pinned rows are not render.
"""
return None

def as_html(self, request):
'''
Render the table to an HTML table, adding `request` to the context.
Expand Down

0 comments on commit 445903f

Please sign in to comment.