399 lines
15 KiB
Python
399 lines
15 KiB
Python
# coding: utf-8
|
||
from __future__ import unicode_literals
|
||
|
||
from django.test import TransactionTestCase
|
||
import django
|
||
from django.core.exceptions import ImproperlyConfigured
|
||
from django.template import Template, RequestContext, Context
|
||
from django.utils.translation import ugettext_lazy
|
||
from django.utils.safestring import mark_safe
|
||
|
||
from .app.models import Person, Region
|
||
import django_tables2 as tables
|
||
from django_tables2.config import RequestConfig
|
||
from django_tables2.utils import build_request
|
||
|
||
try:
|
||
from urlparse import parse_qs
|
||
except ImportError:
|
||
from urllib.parse import parse_qs
|
||
import lxml.etree
|
||
import lxml.html
|
||
import six
|
||
from .utils import parse, translation
|
||
import pytest
|
||
|
||
|
||
class CountryTable(tables.Table):
|
||
name = tables.Column()
|
||
capital = tables.Column(orderable=False,
|
||
verbose_name=ugettext_lazy("Capital"))
|
||
population = tables.Column(verbose_name='Population Size')
|
||
currency = tables.Column(visible=False)
|
||
tld = tables.Column(visible=False, verbose_name='Domain')
|
||
calling_code = tables.Column(accessor='cc',
|
||
verbose_name='Phone Ext.')
|
||
|
||
|
||
MEMORY_DATA = [
|
||
{'name': 'Germany', 'capital': 'Berlin', 'population': 83,
|
||
'currency': 'Euro (€)', 'tld': 'de', 'cc': 49},
|
||
{'name': 'France', 'population': 64, 'currency': 'Euro (€)',
|
||
'tld': 'fr', 'cc': 33},
|
||
{'name': 'Netherlands', 'capital': 'Amsterdam', 'cc': '31'},
|
||
{'name': 'Austria', 'cc': 43, 'currency': 'Euro (€)',
|
||
'population': 8}
|
||
]
|
||
|
||
|
||
def test_as_html():
|
||
table = CountryTable(MEMORY_DATA)
|
||
root = parse(table.as_html())
|
||
assert len(root.findall('.//thead/tr')) == 1
|
||
assert len(root.findall('.//thead/tr/th')) == 4
|
||
assert len(root.findall('.//tbody/tr')) == 4
|
||
assert len(root.findall('.//tbody/tr/td')) == 16
|
||
|
||
# no data with no empty_text
|
||
table = CountryTable([])
|
||
root = parse(table.as_html())
|
||
assert 1 == len(root.findall('.//thead/tr'))
|
||
assert 4 == len(root.findall('.//thead/tr/th'))
|
||
assert 0 == len(root.findall('.//tbody/tr'))
|
||
|
||
# no data WITH empty_text
|
||
table = CountryTable([], empty_text='this table is empty')
|
||
root = parse(table.as_html())
|
||
assert 1 == len(root.findall('.//thead/tr'))
|
||
assert 4 == len(root.findall('.//thead/tr/th'))
|
||
assert 1 == len(root.findall('.//tbody/tr'))
|
||
assert 1 == len(root.findall('.//tbody/tr/td'))
|
||
assert int(root.find('.//tbody/tr/td').attrib['colspan']) == len(root.findall('.//thead/tr/th'))
|
||
assert root.find('.//tbody/tr/td').text == 'this table is empty'
|
||
|
||
# with custom template
|
||
table = CountryTable([], template="django_tables2/table.html")
|
||
table.as_html()
|
||
|
||
|
||
def test_custom_rendering():
|
||
"""For good measure, render some actual templates."""
|
||
countries = CountryTable(MEMORY_DATA)
|
||
context = Context({'countries': countries})
|
||
|
||
# automatic and manual column verbose names
|
||
template = Template('{% for column in countries.columns %}{{ column }}/'
|
||
'{{ column.name }} {% endfor %}')
|
||
result = ('Name/name Capital/capital Population Size/population '
|
||
'Phone Ext./calling_code ')
|
||
assert result == template.render(context)
|
||
|
||
# row values
|
||
template = Template('{% for row in countries.rows %}{% for value in row %}'
|
||
'{{ value }} {% endfor %}{% endfor %}')
|
||
result = ('Germany Berlin 83 49 France — 64 33 Netherlands Amsterdam '
|
||
'— 31 Austria — 8 43 ')
|
||
assert result == template.render(context)
|
||
|
||
|
||
def test_render_table_templatetag(settings):
|
||
# ensure it works with a multi-order-by
|
||
request = build_request('/')
|
||
table = CountryTable(MEMORY_DATA, order_by=('name', 'population'))
|
||
RequestConfig(request).configure(table)
|
||
template = Template('{% load django_tables2 %}{% render_table table %}')
|
||
html = template.render(Context({'request': request, 'table': table}))
|
||
|
||
root = parse(html)
|
||
assert len(root.findall('.//thead/tr')) == 1
|
||
assert len(root.findall('.//thead/tr/th')) == 4
|
||
assert len(root.findall('.//tbody/tr')) == 4
|
||
assert len(root.findall('.//tbody/tr/td')) == 16
|
||
assert root.find('ul[@class="pagination"]/li[@class="cardinality"]').text == '4 items'
|
||
|
||
# no data with no empty_text
|
||
table = CountryTable([])
|
||
template = Template('{% load django_tables2 %}{% render_table table %}')
|
||
html = template.render(Context({'request': build_request('/'), 'table': table}))
|
||
root = parse(html)
|
||
assert len(root.findall('.//thead/tr')) == 1
|
||
assert len(root.findall('.//thead/tr/th')) == 4
|
||
assert len(root.findall('.//tbody/tr')) == 0
|
||
|
||
# no data WITH empty_text
|
||
request = build_request('/')
|
||
table = CountryTable([], empty_text='this table is empty')
|
||
RequestConfig(request).configure(table)
|
||
template = Template('{% load django_tables2 %}{% render_table table %}')
|
||
html = template.render(Context({'request': request, 'table': table}))
|
||
root = parse(html)
|
||
assert len(root.findall('.//thead/tr')) == 1
|
||
assert len(root.findall('.//thead/tr/th')) == 4
|
||
assert len(root.findall('.//tbody/tr')) == 1
|
||
assert len(root.findall('.//tbody/tr/td')) == 1
|
||
assert int(root.find('.//tbody/tr/td').attrib['colspan']) == len(root.findall('.//thead/tr/th'))
|
||
assert root.find('.//tbody/tr/td').text == 'this table is empty'
|
||
|
||
# variable that doesn't exist (issue #8)
|
||
template = Template('{% load django_tables2 %}'
|
||
'{% render_table this_doesnt_exist %}')
|
||
with pytest.raises(ValueError):
|
||
settings.DEBUG = True
|
||
template.render(Context())
|
||
|
||
# Should still be noisy with debug off
|
||
with pytest.raises(ValueError):
|
||
settings.DEBUG = False
|
||
template.render(Context())
|
||
|
||
|
||
def test_render_table_should_support_template_argument():
|
||
table = CountryTable(MEMORY_DATA, order_by=('name', 'population'))
|
||
template = Template('{% load django_tables2 %}'
|
||
'{% render_table table "dummy.html" %}')
|
||
request = build_request('/')
|
||
context = RequestContext(request, {'table': table})
|
||
assert template.render(context) == 'dummy template contents\n'
|
||
|
||
|
||
@pytest.mark.django_db
|
||
def test_render_table_supports_queryset():
|
||
for name in ("Mackay", "Brisbane", "Maryborough"):
|
||
Region.objects.create(name=name)
|
||
template = Template('{% load django_tables2 %}{% render_table qs %}')
|
||
html = template.render(Context({'qs': Region.objects.all(),
|
||
'request': build_request('/')}))
|
||
|
||
root = parse(html)
|
||
assert [e.text for e in root.findall('.//thead/tr/th/a')] == ["ID", "name", "mayor"]
|
||
td = [[td.text for td in tr.findall('td')] for tr in root.findall('.//tbody/tr')]
|
||
db = []
|
||
for region in Region.objects.all():
|
||
db.append([six.text_type(region.id), region.name, "—"])
|
||
assert td == db
|
||
|
||
|
||
def test_querystring_templatetag():
|
||
template = Template('{% load django_tables2 %}'
|
||
'<b>{% querystring "name"="Brad" foo.bar=value %}</b>')
|
||
|
||
# Should be something like: <root>?name=Brad&a=b&c=5&age=21</root>
|
||
xml = template.render(Context({
|
||
"request": build_request('/?a=b&name=dog&c=5'),
|
||
"foo": {"bar": "age"},
|
||
"value": 21,
|
||
}))
|
||
|
||
# Ensure it's valid XML, retrieve the URL
|
||
url = parse(xml).text
|
||
|
||
qs = parse_qs(url[1:]) # everything after the ? pylint: disable=C0103
|
||
assert qs["name"] == ["Brad"]
|
||
assert qs["age"] == ["21"]
|
||
assert qs["a"] == ["b"]
|
||
assert qs["c"] == ["5"]
|
||
|
||
|
||
def test_querystring_templatetag_requires_request():
|
||
with pytest.raises(ImproperlyConfigured):
|
||
(Template('{% load django_tables2 %}{% querystring "name"="Brad" %}')
|
||
.render(Context()))
|
||
|
||
|
||
def test_querystring_templatetag_supports_without():
|
||
context = Context({
|
||
"request": build_request('/?a=b&name=dog&c=5'),
|
||
"a_var": "a",
|
||
})
|
||
|
||
template = Template('{% load django_tables2 %}'
|
||
'<b>{% querystring "name"="Brad" without a_var %}</b>')
|
||
url = parse(template.render(context)).text
|
||
qs = parse_qs(url[1:]) # trim the ? pylint: disable=C0103
|
||
assert set(qs.keys()) == set(["name", "c"])
|
||
|
||
# Try with only exclusions
|
||
template = Template('{% load django_tables2 %}'
|
||
'<b>{% querystring without "a" "name" %}</b>')
|
||
url = parse(template.render(context)).text
|
||
qs = parse_qs(url[1:]) # trim the ? pylint: disable=C0103
|
||
assert set(qs.keys()) == set(["c"])
|
||
|
||
|
||
def test_title_should_only_apply_to_words_without_uppercase_letters():
|
||
expectations = {
|
||
"a brown fox": "A Brown Fox",
|
||
"a brown foX": "A Brown foX",
|
||
"black FBI": "Black FBI",
|
||
"f.b.i": "F.B.I",
|
||
"start 6pm": "Start 6pm",
|
||
}
|
||
|
||
for raw, expected in expectations.items():
|
||
template = Template("{% load django_tables2 %}{{ x|title }}")
|
||
assert template.render(Context({"x": raw})) == expected
|
||
|
||
|
||
def test_nospaceless_works():
|
||
template = Template("{% load django_tables2 %}"
|
||
"{% spaceless %}<b>a</b> <i>b {% nospaceless %}<b>c</b> <b>d</b> {% endnospaceless %}lic</i>{% endspaceless %}")
|
||
assert template.render(Context()) == "<b>a</b><i>b <b>c</b> <b>d</b> lic</i>"
|
||
|
||
|
||
def test_whitespace_is_preserved():
|
||
class TestTable(tables.Table):
|
||
name = tables.Column(verbose_name=mark_safe("<b>foo</b> <i>bar</i>"))
|
||
|
||
html = TestTable([{"name": mark_safe("<b>foo</b> <i>bar</i>")}]).as_html()
|
||
|
||
tree = parse(html)
|
||
|
||
assert "<b>foo</b> <i>bar</i>" in lxml.etree.tostring(tree.findall('.//thead/tr/th')[0], encoding='unicode')
|
||
assert "<b>foo</b> <i>bar</i>" in lxml.etree.tostring(tree.findall('.//tbody/tr/td')[0], encoding='unicode')
|
||
|
||
|
||
@pytest.mark.django_db
|
||
def test_as_html_db_queries(transactional_db):
|
||
class PersonTable(tables.Table):
|
||
class Meta:
|
||
model = Person
|
||
|
||
# with queries(count=1):
|
||
# PersonTable(Person.objects.all()).as_html()
|
||
|
||
@pytest.mark.django_db
|
||
class TestQueries(TransactionTestCase):
|
||
def test_as_html_db_queries(self):
|
||
class PersonTable(tables.Table):
|
||
class Meta:
|
||
model = Person
|
||
|
||
with self.assertNumQueries(1):
|
||
PersonTable(Person.objects.all()).as_html()
|
||
|
||
def test_render_table_db_queries(self):
|
||
Person.objects.create(first_name="brad", last_name="ayers")
|
||
Person.objects.create(first_name="davina", last_name="adisusila")
|
||
|
||
class PersonTable(tables.Table):
|
||
class Meta:
|
||
model = Person
|
||
per_page = 1
|
||
|
||
with self.assertNumQueries(2):
|
||
# one query for pagination: .count()
|
||
# one query for page records: .all()[start:end]
|
||
request = build_request('/')
|
||
table = PersonTable(Person.objects.all())
|
||
RequestConfig(request).configure(table)
|
||
# render
|
||
(Template('{% load django_tables2 %}{% render_table table %}')
|
||
.render(Context({'table': table, 'request': request})))
|
||
|
||
|
||
@pytest.mark.skipif(django.VERSION < (1, 3), reason="requires Django >= 1.3")
|
||
def test_localization_check(settings):
|
||
def get_cond_localized_table(localizeit=None):
|
||
'''
|
||
helper function for defining Table class conditionally
|
||
'''
|
||
class TestTable(tables.Table):
|
||
name = tables.Column(verbose_name="my column", localize=localizeit)
|
||
return TestTable
|
||
|
||
simple_test_data = [{'name': 1234.5}]
|
||
expected_reults = {
|
||
None: '1234.5',
|
||
False: '1234.5',
|
||
True: '1 234,5' # non-breaking space
|
||
}
|
||
|
||
# no localization
|
||
html = get_cond_localized_table(None)(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[None]) in html
|
||
|
||
# unlocalize
|
||
html = get_cond_localized_table(False)(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html
|
||
|
||
settings.USE_L10N = True
|
||
settings.USE_THOUSAND_SEPARATOR = True
|
||
|
||
with translation("pl"):
|
||
# with default polish locales and enabled thousand separator
|
||
# 1234.5 is formatted as "1 234,5" with nbsp
|
||
html = get_cond_localized_table(True)(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
|
||
|
||
# with localize = False there should be no formatting
|
||
html = get_cond_localized_table(False)(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html
|
||
|
||
# with localize = None and USE_L10N = True
|
||
# there should be the same formatting as with localize = True
|
||
html = get_cond_localized_table(None)(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
|
||
|
||
|
||
@pytest.mark.skipif(django.VERSION < (1, 3), reason="requires Django >= 1.3")
|
||
def test_localization_check_in_meta(settings):
|
||
class TableNoLocalize(tables.Table):
|
||
name = tables.Column(verbose_name="my column")
|
||
|
||
class Meta:
|
||
default = "---"
|
||
|
||
class TableLocalize(tables.Table):
|
||
name = tables.Column(verbose_name="my column")
|
||
|
||
class Meta:
|
||
default = "---"
|
||
localize = ('name',)
|
||
|
||
class TableUnlocalize(tables.Table):
|
||
name = tables.Column(verbose_name="my column")
|
||
|
||
class Meta:
|
||
default = "---"
|
||
unlocalize = ('name',)
|
||
|
||
class TableLocalizePrecedence(tables.Table):
|
||
name = tables.Column(verbose_name="my column")
|
||
|
||
class Meta:
|
||
default = "---"
|
||
unlocalize = ('name',)
|
||
localize = ('name',)
|
||
|
||
simple_test_data = [{'name': 1234.5}]
|
||
expected_reults = {
|
||
None: '1234.5',
|
||
False: '1234.5',
|
||
True: '1{0}234,5'.format(' ') # non-breaking space
|
||
}
|
||
|
||
# No localize
|
||
html = TableNoLocalize(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[None]) in html
|
||
|
||
settings.USE_L10N = True
|
||
settings.USE_THOUSAND_SEPARATOR = True
|
||
|
||
with translation("pl"):
|
||
# the same as in localization_check.
|
||
# with localization and polish locale we get formatted output
|
||
html = TableNoLocalize(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
|
||
|
||
# localize
|
||
html = TableLocalize(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
|
||
|
||
# unlocalize
|
||
html = TableUnlocalize(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html
|
||
|
||
# test unlocalize higher precedence
|
||
html = TableLocalizePrecedence(simple_test_data).as_html()
|
||
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html
|