Adds a summation row option to the datatables.
Implements blueprint summation-row. Change-Id: I81c984bbf830182854e8f2227e6eae8909e91eef
This commit is contained in:
parent
96388c7fa9
commit
8fd77f047f
@ -120,6 +120,13 @@ class Column(html.HTMLElement):
|
|||||||
A string or callable to be used for cells which have no data.
|
A string or callable to be used for cells which have no data.
|
||||||
Defaults to the string ``"-"``.
|
Defaults to the string ``"-"``.
|
||||||
|
|
||||||
|
.. attribute:: summation
|
||||||
|
|
||||||
|
A string containing the name of a summation method to be used in
|
||||||
|
the generation of a summary row for this column. By default the
|
||||||
|
options are ``"sum"`` or ``"average"``, which behave as expected.
|
||||||
|
Optional.
|
||||||
|
|
||||||
.. attribute:: filters
|
.. attribute:: filters
|
||||||
|
|
||||||
A list of functions (often template filters) to be applied to the
|
A list of functions (often template filters) to be applied to the
|
||||||
@ -136,6 +143,10 @@ class Column(html.HTMLElement):
|
|||||||
A dict of HTML attribute strings which should be added to this column.
|
A dict of HTML attribute strings which should be added to this column.
|
||||||
Example: ``attrs={"data-foo": "bar"}``.
|
Example: ``attrs={"data-foo": "bar"}``.
|
||||||
"""
|
"""
|
||||||
|
summation_methods = {
|
||||||
|
"sum": sum,
|
||||||
|
"average": lambda data: sum(data, 0.0) / len(data)
|
||||||
|
}
|
||||||
# Used to retain order when instantiating columns on a table
|
# Used to retain order when instantiating columns on a table
|
||||||
creation_counter = 0
|
creation_counter = 0
|
||||||
# Used for special auto-generated columns
|
# Used for special auto-generated columns
|
||||||
@ -162,8 +173,8 @@ class Column(html.HTMLElement):
|
|||||||
|
|
||||||
def __init__(self, transform, verbose_name=None, sortable=False,
|
def __init__(self, transform, verbose_name=None, sortable=False,
|
||||||
link=None, hidden=False, attrs=None, status=False,
|
link=None, hidden=False, attrs=None, status=False,
|
||||||
status_choices=None, display_choices=None,
|
status_choices=None, display_choices=None, empty_value=None,
|
||||||
empty_value=None, filters=None, classes=None):
|
filters=None, classes=None, summation=None):
|
||||||
self.classes = classes or getattr(self, "classes", [])
|
self.classes = classes or getattr(self, "classes", [])
|
||||||
super(Column, self).__init__()
|
super(Column, self).__init__()
|
||||||
self.attrs.update(attrs or {})
|
self.attrs.update(attrs or {})
|
||||||
@ -190,6 +201,12 @@ class Column(html.HTMLElement):
|
|||||||
self.status_choices = status_choices
|
self.status_choices = status_choices
|
||||||
self.display_choices = display_choices
|
self.display_choices = display_choices
|
||||||
|
|
||||||
|
if summation is not None and summation not in self.summation_methods:
|
||||||
|
raise ValueError("Summation method %s must be one of %s."
|
||||||
|
% (summation,
|
||||||
|
", ".join(self.summation_methods.keys())))
|
||||||
|
self.summation = summation
|
||||||
|
|
||||||
self.creation_counter = Column.creation_counter
|
self.creation_counter = Column.creation_counter
|
||||||
Column.creation_counter += 1
|
Column.creation_counter += 1
|
||||||
|
|
||||||
@ -204,18 +221,12 @@ class Column(html.HTMLElement):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<%s: %s>' % (self.__class__.__name__, self.name)
|
return '<%s: %s>' % (self.__class__.__name__, self.name)
|
||||||
|
|
||||||
def get_data(self, datum):
|
def get_raw_data(self, datum):
|
||||||
"""
|
"""
|
||||||
Returns the appropriate data for this column from the given input.
|
Returns the raw data for this column, before any filters or formatting
|
||||||
|
are applied to it. This is useful when doing calculations on data in
|
||||||
The return value will be either the attribute specified for this column
|
the table.
|
||||||
or the return value of the attr:`~horizon.tables.Column.transform`
|
|
||||||
method for this column.
|
|
||||||
"""
|
"""
|
||||||
datum_id = self.table.get_object_id(datum)
|
|
||||||
if datum_id in self.table._data_cache[self]:
|
|
||||||
return self.table._data_cache[self][datum_id]
|
|
||||||
|
|
||||||
# Callable transformations
|
# Callable transformations
|
||||||
if callable(self.transform):
|
if callable(self.transform):
|
||||||
data = self.transform(datum)
|
data = self.transform(datum)
|
||||||
@ -233,6 +244,20 @@ class Column(html.HTMLElement):
|
|||||||
msg = termcolors.colorize(msg, **PALETTE['ERROR'])
|
msg = termcolors.colorize(msg, **PALETTE['ERROR'])
|
||||||
LOG.warning(msg)
|
LOG.warning(msg)
|
||||||
data = None
|
data = None
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_data(self, datum):
|
||||||
|
"""
|
||||||
|
Returns the final display data for this column from the given inputs.
|
||||||
|
|
||||||
|
The return value will be either the attribute specified for this column
|
||||||
|
or the return value of the attr:`~horizon.tables.Column.transform`
|
||||||
|
method for this column.
|
||||||
|
"""
|
||||||
|
datum_id = self.table.get_object_id(datum)
|
||||||
|
if datum_id in self.table._data_cache[self]:
|
||||||
|
return self.table._data_cache[self][datum_id]
|
||||||
|
data = self.get_raw_data(datum)
|
||||||
display_value = None
|
display_value = None
|
||||||
if self.display_choices:
|
if self.display_choices:
|
||||||
display_value = [display for (value, display) in
|
display_value = [display for (value, display) in
|
||||||
@ -262,6 +287,20 @@ class Column(html.HTMLElement):
|
|||||||
except urlresolvers.NoReverseMatch:
|
except urlresolvers.NoReverseMatch:
|
||||||
return self.link
|
return self.link
|
||||||
|
|
||||||
|
def get_summation(self):
|
||||||
|
"""
|
||||||
|
Returns the summary value for the data in this column if a
|
||||||
|
valid summation method is specified for it. Otherwise returns ``None``.
|
||||||
|
"""
|
||||||
|
if self.summation not in self.summation_methods:
|
||||||
|
return None
|
||||||
|
summation_function = self.summation_methods[self.summation]
|
||||||
|
data = [self.get_raw_data(datum) for datum in self.table.data]
|
||||||
|
summation = summation_function(data)
|
||||||
|
for filter_func in self.filters:
|
||||||
|
summation = filter_func(summation)
|
||||||
|
return summation
|
||||||
|
|
||||||
|
|
||||||
class Row(html.HTMLElement):
|
class Row(html.HTMLElement):
|
||||||
""" Represents a row in the table.
|
""" Represents a row in the table.
|
||||||
@ -743,6 +782,9 @@ class DataTable(object):
|
|||||||
for action in self.base_actions.values():
|
for action in self.base_actions.values():
|
||||||
action.table = self
|
action.table = self
|
||||||
|
|
||||||
|
self.needs_summary_row = any([col.summation
|
||||||
|
for col in self.columns.values()])
|
||||||
|
|
||||||
def __unicode__(self):
|
def __unicode__(self):
|
||||||
return unicode(self._meta.verbose_name)
|
return unicode(self._meta.verbose_name)
|
||||||
|
|
||||||
|
@ -26,6 +26,17 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
<tfoot>
|
<tfoot>
|
||||||
|
{% if table.needs_summary_row %}
|
||||||
|
<tr class="summation">
|
||||||
|
{% for column in columns %}
|
||||||
|
{% if forloop.first %}
|
||||||
|
<td>{% trans "Summary" %}</td>
|
||||||
|
{% else %}
|
||||||
|
<td>{{ column.get_summation|default:"–"}}</td>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="{{ table.get_columns|length }}">
|
<td colspan="{{ table.get_columns|length }}">
|
||||||
<span>{% blocktrans count counter=rows|length %}Displaying {{ counter }} item{% plural %}Displaying {{ counter }} items{% endblocktrans %}</span>
|
<span>{% blocktrans count counter=rows|length %}Displaying {{ counter }} item{% plural %}Displaying {{ counter }} items{% endblocktrans %}</span>
|
||||||
|
@ -51,6 +51,11 @@ TEST_DATA_3 = (
|
|||||||
FakeObject('1', 'object_1', 'value_1', 'up', 'optional_1', 'excluded_1'),
|
FakeObject('1', 'object_1', 'value_1', 'up', 'optional_1', 'excluded_1'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
TEST_DATA_4 = (
|
||||||
|
FakeObject('1', 'object_1', 2, 'up'),
|
||||||
|
FakeObject('2', 'object_2', 4, 'up'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MyLinkAction(tables.LinkAction):
|
class MyLinkAction(tables.LinkAction):
|
||||||
name = "login"
|
name = "login"
|
||||||
@ -147,7 +152,8 @@ class MyTable(tables.DataTable):
|
|||||||
value = tables.Column('value',
|
value = tables.Column('value',
|
||||||
sortable=True,
|
sortable=True,
|
||||||
link='http://example.com/',
|
link='http://example.com/',
|
||||||
attrs={'class': 'green blue'})
|
attrs={'class': 'green blue'},
|
||||||
|
summation="average")
|
||||||
status = tables.Column('status', link=get_link)
|
status = tables.Column('status', link=get_link)
|
||||||
optional = tables.Column('optional', empty_value='N/A')
|
optional = tables.Column('optional', empty_value='N/A')
|
||||||
excluded = tables.Column('excluded')
|
excluded = tables.Column('excluded')
|
||||||
@ -582,3 +588,26 @@ class DataTableTests(test.TestCase):
|
|||||||
id(t2cols[0].table))
|
id(t2cols[0].table))
|
||||||
self.assertNotEqual(id(t1cols[0].table._data_cache),
|
self.assertNotEqual(id(t1cols[0].table._data_cache),
|
||||||
id(t2cols[0].table._data_cache))
|
id(t2cols[0].table._data_cache))
|
||||||
|
|
||||||
|
def test_summation_row(self):
|
||||||
|
# Test with the "average" method.
|
||||||
|
table = MyTable(self.request, TEST_DATA_4)
|
||||||
|
res = http.HttpResponse(table.render())
|
||||||
|
self.assertContains(res, '<tr class="summation"', 1)
|
||||||
|
self.assertContains(res, '<td>Summary</td>', 1)
|
||||||
|
self.assertContains(res, '<td>3.0</td>', 1)
|
||||||
|
|
||||||
|
# Test again with the "sum" method.
|
||||||
|
table.columns['value'].summation = "sum"
|
||||||
|
res = http.HttpResponse(table.render())
|
||||||
|
self.assertContains(res, '<tr class="summation"', 1)
|
||||||
|
self.assertContains(res, '<td>Summary</td>', 1)
|
||||||
|
self.assertContains(res, '<td>6</td>', 1)
|
||||||
|
|
||||||
|
# One last test with no summation.
|
||||||
|
table.columns['value'].summation = None
|
||||||
|
table.needs_summary_row = False
|
||||||
|
res = http.HttpResponse(table.render())
|
||||||
|
self.assertNotContains(res, '<tr class="summation"')
|
||||||
|
self.assertNotContains(res, '<td>3.0</td>')
|
||||||
|
self.assertNotContains(res, '<td>6</td>')
|
||||||
|
Loading…
x
Reference in New Issue
Block a user