Add support for computed values.
This commit is contained in:
parent
1db7b612a8
commit
86056eedeb
|
@ -4,7 +4,7 @@ from . import columns
|
|||
from .config import RequestConfig
|
||||
from .rows import BoundRows
|
||||
from .utils import (Accessor, AttributeDict, build_request, cached_property,
|
||||
OrderBy, OrderByTuple, segment, Sequence)
|
||||
computed_values, OrderBy, OrderByTuple, segment, Sequence)
|
||||
import copy
|
||||
from django.core.paginator import Paginator
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
|
@ -390,8 +390,9 @@ class TableBase(object):
|
|||
default = self._meta.default
|
||||
self.default = default
|
||||
self.rows = BoundRows(data=self.data, table=self)
|
||||
self.attrs = attrs
|
||||
self.empty_text = empty_text
|
||||
self.attrs = AttributeDict(computed_values(attrs if attrs is not None
|
||||
else self._meta.attrs))
|
||||
self.empty_text = empty_text if empty_text is not None else self._meta.empty_text
|
||||
if sortable is not None:
|
||||
warnings.warn("`sortable` is deprecated, use `orderable` instead.",
|
||||
DeprecationWarning)
|
||||
|
@ -454,7 +455,7 @@ class TableBase(object):
|
|||
|
||||
@property
|
||||
def attrs(self):
|
||||
return self._attrs if self._attrs is not None else self._meta.attrs
|
||||
return self._attrs
|
||||
|
||||
@attrs.setter
|
||||
def attrs(self, value):
|
||||
|
@ -462,8 +463,7 @@ class TableBase(object):
|
|||
|
||||
@property
|
||||
def empty_text(self):
|
||||
return (self._empty_text if self._empty_text is not None
|
||||
else self._meta.empty_text)
|
||||
return self._empty_text
|
||||
|
||||
@empty_text.setter
|
||||
def empty_text(self, value):
|
||||
|
|
|
@ -421,7 +421,7 @@ class AttributeDict(dict):
|
|||
:rtype: `~django.utils.safestring.SafeUnicode` object
|
||||
|
||||
"""
|
||||
return mark_safe(' '.join(['%s="%s"' % (k, escape(v))
|
||||
return mark_safe(' '.join(['%s="%s"' % (k, escape(v if not callable(v) else v()))
|
||||
for k, v in six.iteritems(self)]))
|
||||
|
||||
|
||||
|
@ -551,3 +551,45 @@ def total_ordering(cls):
|
|||
opfunc.__doc__ = getattr(int, opname).__doc__
|
||||
setattr(cls, opname, opfunc)
|
||||
return cls
|
||||
|
||||
|
||||
def computed_values(d):
|
||||
"""
|
||||
Computes a new `dict` that has callable values replaced with the return values.
|
||||
|
||||
Simple example:
|
||||
|
||||
>>> compute_values({"foo": lambda: "bar"})
|
||||
{"foo": "bar"}
|
||||
|
||||
Arbitrarily deep structures are supported. The logic is as follows:
|
||||
|
||||
1. If the value is callable, call it and make that the new value.
|
||||
2. If the value is an instance of dict, use ComputableDict to compute its keys.
|
||||
|
||||
Example:
|
||||
|
||||
>>> def parents():
|
||||
... return {
|
||||
... "father": lambda: "Foo",
|
||||
... "mother": "Bar"
|
||||
... }
|
||||
...
|
||||
>>> a = {
|
||||
... "name": "Brad",
|
||||
... "parents": parents
|
||||
... }
|
||||
...
|
||||
>>> computed_values(a)
|
||||
{"name": "Brad", "parents": {"father": "Foo", "mother": "Bar"}}
|
||||
|
||||
:rtype: dict
|
||||
"""
|
||||
result = {}
|
||||
for k, v in six.iteritems(d):
|
||||
if callable(v):
|
||||
v = v()
|
||||
if isinstance(v, dict):
|
||||
v = computed_values(v)
|
||||
result[k] = v
|
||||
return result
|
||||
|
|
|
@ -886,6 +886,25 @@ API Reference
|
|||
class Meta:
|
||||
attrs = {"class": "paleblue"}
|
||||
|
||||
.. versionadded:: 0.15.0
|
||||
|
||||
It's possible to use callables to create *dynamic* values. A few caveats:
|
||||
|
||||
- It's not supported for ``dict`` keys, i.e. only values.
|
||||
- All values will be resolved on table instantiation.
|
||||
|
||||
Consider this example where a unique ``id`` is given to each instance
|
||||
of the table::
|
||||
|
||||
import itertools
|
||||
counter = itertools.count()
|
||||
|
||||
class UniqueIdTable(tables.Table):
|
||||
name = tables.Column()
|
||||
|
||||
class Meta:
|
||||
attrs = {"id": lambda: "table_%d" % next(counter)}
|
||||
|
||||
.. note::
|
||||
|
||||
This functionality is also available via the ``attrs`` keyword
|
||||
|
|
|
@ -7,6 +7,7 @@ from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
|||
import django_tables2 as tables
|
||||
from django_tables2.tables import DeclarativeColumnsMetaclass
|
||||
import six
|
||||
import itertools
|
||||
|
||||
|
||||
core = Tests()
|
||||
|
@ -118,6 +119,18 @@ def attrs():
|
|||
assert {"c": "d"} == TestTable4([], attrs={"c": "d"}).attrs
|
||||
|
||||
|
||||
@core.test
|
||||
def attrs_support_computed_values():
|
||||
counter = itertools.count()
|
||||
|
||||
class TestTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {"id": lambda: "test_table_%d" % next(counter)}
|
||||
|
||||
assert {"id": "test_table_0"} == TestTable([]).attrs
|
||||
assert {"id": "test_table_1"} == TestTable([]).attrs
|
||||
|
||||
|
||||
@core.test
|
||||
def data_knows_its_name():
|
||||
table = tables.Table([{}])
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# coding: utf-8
|
||||
from attest import assert_hook, raises, Tests
|
||||
from django_tables2.utils import (Accessor, AttributeDict, OrderByTuple,
|
||||
OrderBy, segment)
|
||||
from django_tables2.utils import (Accessor, AttributeDict, computed_values,
|
||||
OrderByTuple, OrderBy, segment)
|
||||
import itertools
|
||||
import six
|
||||
|
||||
|
||||
|
@ -131,6 +132,18 @@ def attribute_dict_handles_escaping():
|
|||
assert x.as_html() == 'x=""'x&"'
|
||||
|
||||
|
||||
@utils.test
|
||||
def compute_values_supports_shallow_structures():
|
||||
x = computed_values({"foo": lambda: "bar"})
|
||||
assert x == {"foo": "bar"}
|
||||
|
||||
|
||||
@utils.test
|
||||
def compute_values_supports_shallow_structures():
|
||||
x = computed_values({"foo": lambda: {"bar": lambda: "baz"}})
|
||||
assert x == {"foo": {"bar": "baz"}}
|
||||
|
||||
|
||||
@utils.test
|
||||
def segment_should_return_all_candidates():
|
||||
assert set(segment(("a", "-b", "c"), {
|
||||
|
|
Loading…
Reference in New Issue