622 lines
17 KiB
Python
622 lines
17 KiB
Python
# coding: utf-8
|
|
"""Test the core table functionality."""
|
|
from __future__ import absolute_import, unicode_literals
|
|
import copy
|
|
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
|
|
import django_tables2 as tables
|
|
from django_tables2.tables import DeclarativeColumnsMetaclass
|
|
import six
|
|
import itertools
|
|
import pytest
|
|
from .utils import warns
|
|
|
|
|
|
class UnorderedTable(tables.Table):
|
|
i = tables.Column()
|
|
alpha = tables.Column()
|
|
beta = tables.Column()
|
|
|
|
|
|
class OrderedTable(UnorderedTable):
|
|
class Meta:
|
|
order_by = 'alpha'
|
|
|
|
|
|
MEMORY_DATA = [
|
|
{'i': 2, 'alpha': 'b', 'beta': 'b'},
|
|
{'i': 1, 'alpha': 'a', 'beta': 'c'},
|
|
{'i': 3, 'alpha': 'c', 'beta': 'a'},
|
|
]
|
|
|
|
|
|
def test_declarations():
|
|
"""Test defining tables by declaration."""
|
|
class GeoAreaTable(tables.Table):
|
|
name = tables.Column()
|
|
population = tables.Column()
|
|
|
|
assert len(GeoAreaTable.base_columns) == 2
|
|
assert 'name' in GeoAreaTable.base_columns
|
|
assert not hasattr(GeoAreaTable, 'name')
|
|
|
|
class CountryTable(GeoAreaTable):
|
|
capital = tables.Column()
|
|
|
|
assert len(CountryTable.base_columns) == 3
|
|
assert 'capital' in CountryTable.base_columns
|
|
|
|
# multiple inheritance
|
|
class AddedMixin(tables.Table):
|
|
added = tables.Column()
|
|
|
|
class CityTable(GeoAreaTable, AddedMixin):
|
|
mayor = tables.Column()
|
|
|
|
assert len(CityTable.base_columns) == 4
|
|
assert 'added' in CityTable.base_columns
|
|
|
|
|
|
def test_metaclass_inheritance():
|
|
class Tweaker(type):
|
|
"""Adds an attribute "tweaked" to all classes"""
|
|
def __new__(cls, name, bases, attrs):
|
|
attrs['tweaked'] = True
|
|
return super(Tweaker, cls).__new__(cls, name, bases, attrs)
|
|
|
|
class Meta(Tweaker, DeclarativeColumnsMetaclass):
|
|
pass
|
|
|
|
class TweakedTableBase(tables.Table):
|
|
__metaclass__ = Meta
|
|
name = tables.Column()
|
|
|
|
# Python 2/3 compatible way to enable the metaclass
|
|
TweakedTable = Meta(str('TweakedTable'), (TweakedTableBase, ), {})
|
|
|
|
table = TweakedTable([])
|
|
assert 'name' in table.columns
|
|
assert table.tweaked
|
|
|
|
# now flip the order
|
|
class FlippedMeta(DeclarativeColumnsMetaclass, Tweaker):
|
|
pass
|
|
|
|
class FlippedTweakedTableBase(tables.Table):
|
|
name = tables.Column()
|
|
|
|
# Python 2/3 compatible way to enable the metaclass
|
|
FlippedTweakedTable = FlippedMeta(str('FlippedTweakedTable'), (FlippedTweakedTableBase, ), {})
|
|
|
|
table = FlippedTweakedTable([])
|
|
assert 'name' in table.columns
|
|
assert table.tweaked
|
|
|
|
|
|
def test_attrs():
|
|
class TestTable(tables.Table):
|
|
class Meta:
|
|
attrs = {}
|
|
assert {} == TestTable([]).attrs
|
|
|
|
class TestTable2(tables.Table):
|
|
class Meta:
|
|
attrs = {"a": "b"}
|
|
assert {"a": "b"} == TestTable2([]).attrs
|
|
|
|
class TestTable3(tables.Table):
|
|
pass
|
|
assert {} == TestTable3([]).attrs
|
|
assert {"a": "b"} == TestTable3([], attrs={"a": "b"}).attrs
|
|
|
|
class TestTable4(tables.Table):
|
|
class Meta:
|
|
attrs = {"a": "b"}
|
|
assert {"c": "d"} == TestTable4([], attrs={"c": "d"}).attrs
|
|
|
|
|
|
def test_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
|
|
|
|
|
|
def test_data_knows_its_name():
|
|
table = tables.Table([{}])
|
|
assert table.data.verbose_name == "item"
|
|
assert table.data.verbose_name_plural == "items"
|
|
|
|
|
|
def test_datasource_untouched():
|
|
"""Ensure that data that is provided to the table (the datasource) is not
|
|
modified by table operations.
|
|
"""
|
|
original_data = copy.deepcopy(MEMORY_DATA)
|
|
|
|
table = UnorderedTable(MEMORY_DATA)
|
|
table.order_by = 'i'
|
|
list(table.rows)
|
|
assert MEMORY_DATA == original_data
|
|
|
|
table = UnorderedTable(MEMORY_DATA)
|
|
table.order_by = 'beta'
|
|
list(table.rows)
|
|
assert MEMORY_DATA == original_data
|
|
|
|
|
|
def test_should_support_tuple_data_source():
|
|
class SimpleTable(tables.Table):
|
|
name = tables.Column()
|
|
|
|
table = SimpleTable((
|
|
{'name': 'brad'},
|
|
{'name': 'davina'},
|
|
))
|
|
|
|
assert len(table.rows) == 2
|
|
|
|
|
|
@pytest.mark.skipif(six.PY3, reason="Haystack requires Python 2.x")
|
|
def test_should_support_haystack_data_source():
|
|
from haystack.query import SearchQuerySet
|
|
|
|
class PersonTable(tables.Table):
|
|
first_name = tables.Column()
|
|
|
|
table = PersonTable(SearchQuerySet().all())
|
|
table.as_html()
|
|
|
|
|
|
def test_data_validation():
|
|
with pytest.raises(ValueError):
|
|
table = OrderedTable(None)
|
|
|
|
class Bad:
|
|
def __len__(self):
|
|
pass
|
|
|
|
with pytest.raises(ValueError):
|
|
table = OrderedTable(Bad())
|
|
|
|
class Ok:
|
|
def __len__(self):
|
|
return 1
|
|
def __getitem__(self, pos):
|
|
if pos != 0:
|
|
raise IndexError()
|
|
return {'a': 1}
|
|
|
|
table = OrderedTable(Ok())
|
|
assert len(table.rows) == 1
|
|
|
|
|
|
def test_ordering():
|
|
# fallback to Table.Meta
|
|
assert ('alpha', ) == OrderedTable([], order_by=None).order_by == OrderedTable([]).order_by
|
|
|
|
# values of order_by are wrapped in tuples before being returned
|
|
assert OrderedTable([], order_by='alpha').order_by == ('alpha', )
|
|
assert OrderedTable([], order_by=('beta',)).order_by == ('beta', )
|
|
|
|
table = OrderedTable([])
|
|
table.order_by = []
|
|
assert () == table.order_by == OrderedTable([], order_by=[]).order_by
|
|
|
|
table = OrderedTable([])
|
|
table.order_by = ()
|
|
assert () == table.order_by == OrderedTable([], order_by=()).order_by
|
|
|
|
table = OrderedTable([])
|
|
table.order_by = ''
|
|
assert () == table.order_by == OrderedTable([], order_by='').order_by
|
|
|
|
# apply an ordering
|
|
table = UnorderedTable([])
|
|
table.order_by = 'alpha'
|
|
assert ('alpha', ) == UnorderedTable([], order_by='alpha').order_by == table.order_by
|
|
|
|
table = OrderedTable([])
|
|
table.order_by = 'alpha'
|
|
assert ('alpha', ) == OrderedTable([], order_by='alpha').order_by == table.order_by
|
|
|
|
# let's check the data
|
|
table = OrderedTable(MEMORY_DATA, order_by='beta')
|
|
assert 3 == table.rows[0]['i']
|
|
|
|
table = OrderedTable(MEMORY_DATA, order_by='-beta')
|
|
assert 1 == table.rows[0]['i']
|
|
|
|
# allow fallback to Table.Meta.order_by
|
|
table = OrderedTable(MEMORY_DATA)
|
|
assert 1 == table.rows[0]['i']
|
|
|
|
# column's can't be ordered if they're not allowed to be
|
|
class TestTable2(tables.Table):
|
|
a = tables.Column(orderable=False)
|
|
b = tables.Column()
|
|
|
|
table = TestTable2([], order_by='a')
|
|
assert table.order_by == ()
|
|
|
|
table = TestTable2([], order_by='b')
|
|
assert table.order_by == ('b', )
|
|
|
|
# ordering disabled by default
|
|
class TestTable3(tables.Table):
|
|
a = tables.Column(orderable=True)
|
|
b = tables.Column()
|
|
|
|
class Meta:
|
|
orderable = False
|
|
|
|
table = TestTable3([], order_by='a')
|
|
assert table.order_by == ('a', )
|
|
|
|
table = TestTable3([], order_by='b')
|
|
assert table.order_by == ()
|
|
|
|
table = TestTable3([], orderable=True, order_by='b')
|
|
assert table.order_by == ('b', )
|
|
|
|
with warns(DeprecationWarning) as captured:
|
|
tables.Column(sortable=True)
|
|
tables.Column(sortable=False)
|
|
|
|
class TestTable4(tables.Table):
|
|
class Meta:
|
|
sortable = True
|
|
|
|
class TestTable4(tables.Table):
|
|
class Meta:
|
|
sortable = False
|
|
|
|
assert len(captured) == 4
|
|
|
|
|
|
def test_ordering_different_types():
|
|
from datetime import datetime
|
|
|
|
data = [
|
|
{'i': 1, 'alpha': datetime.now(), 'beta': [1]},
|
|
{'i': {}, 'alpha': None, 'beta': ''},
|
|
{'i': 2, 'alpha': None, 'beta': []},
|
|
]
|
|
|
|
table = OrderedTable(data)
|
|
assert "—" == table.rows[0]['alpha']
|
|
|
|
table = OrderedTable(data, order_by='i')
|
|
if six.PY3:
|
|
assert {} == table.rows[0]['i']
|
|
else:
|
|
assert 1 == table.rows[0]['i']
|
|
|
|
|
|
table = OrderedTable(data, order_by='beta')
|
|
assert [] == table.rows[0]['beta']
|
|
|
|
|
|
def test_multi_column_ordering():
|
|
brad = {"first_name": "Bradley", "last_name": "Ayers"}
|
|
brad2 = {"first_name": "Bradley", "last_name": "Fake"}
|
|
chris = {"first_name": "Chris", "last_name": "Doble"}
|
|
davina = {"first_name": "Davina", "last_name": "Adisusila"}
|
|
ross = {"first_name": "Ross", "last_name": "Ayers"}
|
|
|
|
people = [brad, brad2, chris, davina, ross]
|
|
|
|
class PersonTable(tables.Table):
|
|
first_name = tables.Column()
|
|
last_name = tables.Column()
|
|
|
|
table = PersonTable(people, order_by=("first_name", "last_name"))
|
|
assert [brad, brad2, chris, davina, ross] == [r.record for r in table.rows]
|
|
|
|
table = PersonTable(people, order_by=("first_name", "-last_name"))
|
|
assert [brad2, brad, chris, davina, ross] == [r.record for r in table.rows]
|
|
|
|
# let's try column order_by using multiple keys
|
|
class PersonTable(tables.Table):
|
|
name = tables.Column(order_by=("first_name", "last_name"))
|
|
|
|
# add 'name' key for each person.
|
|
for person in people:
|
|
person['name'] = "{p[first_name]} {p[last_name]}".format(p=person)
|
|
assert brad['name'] == "Bradley Ayers"
|
|
|
|
table = PersonTable(people, order_by="name")
|
|
assert [brad, brad2, chris, davina, ross] == [r.record for r in table.rows]
|
|
|
|
table = PersonTable(people, order_by="-name")
|
|
assert [ross, davina, chris, brad2, brad] == [r.record for r in table.rows]
|
|
|
|
|
|
def test_column_count():
|
|
class SimpleTable(tables.Table):
|
|
visible = tables.Column(visible=True)
|
|
hidden = tables.Column(visible=False)
|
|
|
|
# The columns container supports the len() builtin
|
|
assert len(SimpleTable([]).columns) == 1
|
|
|
|
|
|
def test_column_accessor():
|
|
class SimpleTable(UnorderedTable):
|
|
col1 = tables.Column(accessor='alpha.upper.isupper')
|
|
col2 = tables.Column(accessor='alpha.upper')
|
|
table = SimpleTable(MEMORY_DATA)
|
|
row = table.rows[0]
|
|
assert row['col1'] is True
|
|
assert row['col2'] == 'B'
|
|
|
|
|
|
def test_exclude_columns():
|
|
"""
|
|
Defining ``Table.Meta.exclude`` or providing an ``exclude`` argument when
|
|
instantiating a table should have the same effect -- exclude those columns
|
|
from the table. It should have the same effect as not defining the
|
|
columns originally.
|
|
"""
|
|
# Table(..., exclude=...)
|
|
table = UnorderedTable([], exclude=("i"))
|
|
assert [c.name for c in table.columns] == ["alpha", "beta"]
|
|
|
|
# Table.Meta: exclude=...
|
|
class PartialTable(UnorderedTable):
|
|
class Meta:
|
|
exclude = ("alpha", )
|
|
table = PartialTable([])
|
|
assert [c.name for c in table.columns] == ["i", "beta"]
|
|
|
|
# Inheritence -- exclude in parent, add in child
|
|
class AddonTable(PartialTable):
|
|
added = tables.Column()
|
|
table = AddonTable([])
|
|
assert [c.name for c in table.columns] == ["i", "beta", "added"]
|
|
|
|
# Inheritence -- exclude in child
|
|
class ExcludeTable(UnorderedTable):
|
|
added = tables.Column()
|
|
class Meta:
|
|
exclude = ("beta", )
|
|
table = ExcludeTable([])
|
|
assert [c.name for c in table.columns] == ["i", "alpha", "added"]
|
|
|
|
|
|
def test_table_exclude_property_should_override_constructor_argument():
|
|
class SimpleTable(tables.Table):
|
|
a = tables.Column()
|
|
b = tables.Column()
|
|
|
|
table = SimpleTable([], exclude=('b', ))
|
|
assert [c.name for c in table.columns] == ['a']
|
|
table.exclude = ('a', )
|
|
assert [c.name for c in table.columns] == ['b']
|
|
|
|
|
|
def test_pagination():
|
|
class BookTable(tables.Table):
|
|
name = tables.Column()
|
|
|
|
# create some sample data
|
|
data = []
|
|
for i in range(100):
|
|
data.append({"name": "Book No. %d" % i})
|
|
books = BookTable(data)
|
|
|
|
# external paginator
|
|
paginator = Paginator(books.rows, 10)
|
|
assert paginator.num_pages == 10
|
|
page = paginator.page(1)
|
|
assert page.has_previous() is False
|
|
assert page.has_next() is True
|
|
|
|
# integrated paginator
|
|
books.paginate(page=1)
|
|
assert hasattr(books, "page") is True
|
|
|
|
books.paginate(page=1, per_page=10)
|
|
assert len(list(books.page.object_list)) == 10
|
|
|
|
# new attributes
|
|
assert books.paginator.num_pages == 10
|
|
assert books.page.has_previous() is False
|
|
assert books.page.has_next() is True
|
|
|
|
# accessing a non-existant page raises 404
|
|
with pytest.raises(EmptyPage):
|
|
books.paginate(Paginator, page=9999, per_page=10)
|
|
|
|
with pytest.raises(PageNotAnInteger):
|
|
books.paginate(Paginator, page='abc', per_page=10)
|
|
|
|
|
|
def test_pagination_shouldnt_prevent_multiple_rendering():
|
|
class SimpleTable(tables.Table):
|
|
name = tables.Column()
|
|
|
|
table = SimpleTable([{'name': 'brad'}])
|
|
table.paginate()
|
|
|
|
assert table.as_html() == table.as_html()
|
|
|
|
|
|
def test_empty_text():
|
|
class TestTable(tables.Table):
|
|
a = tables.Column()
|
|
|
|
table = TestTable([])
|
|
assert table.empty_text is None
|
|
|
|
class TestTable2(tables.Table):
|
|
a = tables.Column()
|
|
|
|
class Meta:
|
|
empty_text = 'nothing here'
|
|
|
|
table = TestTable2([])
|
|
assert table.empty_text == 'nothing here'
|
|
|
|
table = TestTable2([], empty_text='still nothing')
|
|
assert table.empty_text == 'still nothing'
|
|
|
|
|
|
def test_prefix():
|
|
"""Test that table prefixes affect the names of querystring parameters"""
|
|
class TableA(tables.Table):
|
|
name = tables.Column()
|
|
|
|
class Meta:
|
|
prefix = "x"
|
|
|
|
assert "x" == TableA([]).prefix
|
|
|
|
class TableB(tables.Table):
|
|
name = tables.Column()
|
|
|
|
assert "" == TableB([]).prefix
|
|
assert "x" == TableB([], prefix="x").prefix
|
|
|
|
table = TableB([])
|
|
table.prefix = "x"
|
|
assert "x" == table.prefix
|
|
|
|
|
|
def test_field_names():
|
|
class TableA(tables.Table):
|
|
class Meta:
|
|
order_by_field = "abc"
|
|
page_field = "def"
|
|
per_page_field = "ghi"
|
|
|
|
table = TableA([])
|
|
assert "abc" == table.order_by_field
|
|
assert "def" == table.page_field
|
|
assert "ghi" == table.per_page_field
|
|
|
|
|
|
def test_field_names_with_prefix():
|
|
class TableA(tables.Table):
|
|
class Meta:
|
|
order_by_field = "sort"
|
|
page_field = "page"
|
|
per_page_field = "per_page"
|
|
prefix = "1-"
|
|
|
|
table = TableA([])
|
|
assert "1-sort" == table.prefixed_order_by_field
|
|
assert "1-page" == table.prefixed_page_field
|
|
assert "1-per_page" == table.prefixed_per_page_field
|
|
|
|
class TableB(tables.Table):
|
|
class Meta:
|
|
order_by_field = "sort"
|
|
page_field = "page"
|
|
per_page_field = "per_page"
|
|
|
|
table = TableB([], prefix="1-")
|
|
assert "1-sort" == table.prefixed_order_by_field
|
|
assert "1-page" == table.prefixed_page_field
|
|
assert "1-per_page" == table.prefixed_per_page_field
|
|
|
|
table = TableB([])
|
|
table.prefix = "1-"
|
|
assert "1-sort" == table.prefixed_order_by_field
|
|
assert "1-page" == table.prefixed_page_field
|
|
assert "1-per_page" == table.prefixed_per_page_field
|
|
|
|
|
|
def test_should_support_a_template_to_be_specified():
|
|
class ConstructorSpecifiedTemplateTable(tables.Table):
|
|
name = tables.Column()
|
|
|
|
table = ConstructorSpecifiedTemplateTable([], template="dummy.html")
|
|
assert table.template == "dummy.html"
|
|
|
|
class PropertySpecifiedTemplateTable(tables.Table):
|
|
name = tables.Column()
|
|
|
|
table = PropertySpecifiedTemplateTable([])
|
|
table.template = "dummy.html"
|
|
assert table.template == "dummy.html"
|
|
|
|
class DefaultTable(tables.Table):
|
|
pass
|
|
|
|
table = DefaultTable([])
|
|
assert table.template == "django_tables2/table.html"
|
|
|
|
|
|
def test_template_in_meta_class_declaration_should_be_honored():
|
|
class MetaDeclarationSpecifiedTemplateTable(tables.Table):
|
|
name = tables.Column()
|
|
|
|
class Meta:
|
|
template = "dummy.html"
|
|
|
|
table = MetaDeclarationSpecifiedTemplateTable([])
|
|
assert table.template == "dummy.html"
|
|
assert table.as_html() == "dummy template contents\n"
|
|
|
|
|
|
def test_should_support_rendering_multiple_times():
|
|
class MultiRenderTable(tables.Table):
|
|
name = tables.Column()
|
|
|
|
# test list data
|
|
table = MultiRenderTable([{'name': 'brad'}])
|
|
assert table.as_html() == table.as_html()
|
|
|
|
|
|
def test_column_defaults_are_honored():
|
|
class Table(tables.Table):
|
|
name = tables.Column(default="abcd")
|
|
|
|
class Meta:
|
|
default = "efgh"
|
|
|
|
table = Table([{}], default="ijkl")
|
|
assert table.rows[0]['name'] == "abcd"
|
|
|
|
|
|
def test_table_meta_defaults_are_honored():
|
|
class Table(tables.Table):
|
|
name = tables.Column()
|
|
|
|
class Meta:
|
|
default = "abcd"
|
|
|
|
table = Table([{}])
|
|
assert table.rows[0]['name'] == "abcd"
|
|
|
|
|
|
def test_table_defaults_are_honored():
|
|
class Table(tables.Table):
|
|
name = tables.Column()
|
|
|
|
table = Table([{}], default="abcd")
|
|
assert table.rows[0]['name'] == "abcd"
|
|
|
|
table = Table([{}], default="abcd")
|
|
table.default = "efgh"
|
|
assert table.rows[0]['name'] == "efgh"
|
|
|
|
|
|
def test_list_table_data_supports_ordering():
|
|
class Table(tables.Table):
|
|
name = tables.Column()
|
|
|
|
data = [
|
|
{"name": "Bradley"},
|
|
{"name": "Davina"},
|
|
]
|
|
|
|
table = Table(data)
|
|
assert table.rows[0]["name"] == "Bradley"
|
|
table.order_by = "-name"
|
|
assert table.rows[0]["name"] == "Davina"
|