# 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 %}' '{% querystring "name"="Brad" foo.bar=value %}') # Should be something like: ?name=Brad&a=b&c=5&age=21 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 %}' '{% querystring "name"="Brad" without a_var %}') 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 %}' '{% querystring without "a" "name" %}') 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 %}a b {% nospaceless %}c d {% endnospaceless %}lic{% endspaceless %}") assert template.render(Context()) == "ab c d lic" def test_whitespace_is_preserved(): class TestTable(tables.Table): name = tables.Column(verbose_name=mark_safe("foo bar")) html = TestTable([{"name": mark_safe("foo bar")}]).as_html() tree = parse(html) assert "foo bar" in lxml.etree.tostring(tree.findall('.//thead/tr/th')[0], encoding='unicode') assert "foo bar" 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 '{0}'.format(expected_reults[None]) in html # unlocalize html = get_cond_localized_table(False)(simple_test_data).as_html() assert '{0}'.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 '{0}'.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 '{0}'.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 '{0}'.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 '{0}'.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 '{0}'.format(expected_reults[True]) in html # localize html = TableLocalize(simple_test_data).as_html() assert '{0}'.format(expected_reults[True]) in html # unlocalize html = TableUnlocalize(simple_test_data).as_html() assert '{0}'.format(expected_reults[False]) in html # test unlocalize higher precedence html = TableLocalizePrecedence(simple_test_data).as_html() assert '{0}'.format(expected_reults[False]) in html