Convert to py.test.

This commit is contained in:
Bradley Ayers 2015-04-19 22:29:16 +10:00
parent 9a15a4381a
commit 8d7626855e
36 changed files with 1282 additions and 1436 deletions

View File

@ -83,6 +83,7 @@ v1.0.0
- Added Python 3.4 support.
- Added Django 1.7 and Django 1.8 support.
- Dropped Python 2.6 and 3.2 support.
- Convert tests to using py.test.
v0.16.0
-------

View File

@ -242,10 +242,11 @@ class Column(object): # pylint: disable=R0902
# Since this method is inherited by every subclass, only provide a
# column if this class was asked directly.
if cls is Column:
# django 1.8 fix, but maintain compatibility
if 'django.db.models.fields.related.ManyToOneRel' in str(field.__class__):
return cls(verbose_name=field.get_related_field().verbose_name)
return cls(verbose_name=field.verbose_name)
if hasattr(field, "get_related_field"):
verbose_name = field.get_related_field().verbose_name
else:
verbose_name = field.verbose_name
return cls(verbose_name=verbose_name)
class BoundColumn(object):
@ -456,8 +457,8 @@ class BoundColumn(object):
# in anything useful.
name = title(self.name.replace('_', ' '))
# Try to use a tmodel field's verbose_name
if hasattr(self.table.data, 'queryset'):
# Try to use a model field's verbose_name
if hasattr(self.table.data, 'queryset') and hasattr(self.table.data.queryset, 'model'):
model = self.table.data.queryset.model
parts = self.accessor.split('.')
field = None

View File

@ -112,7 +112,7 @@ class BoundRow(object):
try:
field = penultimate._meta.get_field(remainder)
display = getattr(penultimate, 'get_%s_display' % remainder, None)
if field.choices and display:
if getattr(field, "choices", ()) and display:
value = display()
remainder = None
except FieldDoesNotExist:

8
requirements/common.pip Normal file
View File

@ -0,0 +1,8 @@
django-haystack>=2.0.0,<2.1.0
fudge
lxml
pylint
pytz>0
six
pytest
pytest-django

View File

@ -0,0 +1,2 @@
-r common.pip
Django>=1.2,<1.3

View File

@ -0,0 +1,2 @@
-r common.pip
Django>=1.3,<1.4

View File

@ -0,0 +1,2 @@
-r common.pip
Django>=1.4,<1.5

View File

@ -0,0 +1,2 @@
-r common.pip
Django>=1.5,<1.6

View File

@ -0,0 +1,2 @@
-r common.pip
Django>=1.6,<1.7

View File

@ -0,0 +1,2 @@
-r common.pip
Django>=1.7,<1.8

View File

@ -0,0 +1,2 @@
-r common.pip
Django>=1.8,<1.9

View File

@ -0,0 +1,2 @@
-r common.pip
http://github.com/django/django/tarball/master

View File

@ -22,9 +22,6 @@ setup(
install_requires=['Django >=1.2', 'six'],
test_loader='tests:loader',
test_suite='tests.everything',
classifiers=[
'Environment :: Web Environment',
'Framework :: Django',

View File

@ -1,49 +0,0 @@
# coding: utf-8
from __future__ import absolute_import, unicode_literals
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'tests.app.settings'
from attest import Tests
import django_attest
from .columns import columns
from .config import config
from .core import core
from .models import models
from .rows import rows
from .templates import templates
from .utils import utils
from .views import views
loader = django_attest.FancyReporter.test_loader
everything = Tests([columns, config, core, models, rows, templates, utils,
views])
# -----------------------------------------------------------------------------
junit = Tests()
@junit.test
def make_junit_output():
import xmlrunner
runner = xmlrunner.XMLTestRunner(output=b'reports')
runner.run(everything.test_suite())
# -----------------------------------------------------------------------------
pylint = Tests()
@pylint.test
def make_pylint_output():
from os.path import expanduser
from pylint.lint import Run
from pylint.reporters.text import ParseableTextReporter
if not os.path.exists('reports'):
os.mkdir('reports')
with open('reports/pylint.report', 'wb') as handle:
args = ['django_tables2', 'example', 'tests']
Run(args, reporter=ParseableTextReporter(output=handle), exit=False)

View File

@ -2,8 +2,7 @@
from __future__ import unicode_literals
from django.db import models
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy
from django.utils.translation import ugettext
from django.utils.translation import ugettext, ugettext_lazy
import six

View File

@ -1,838 +0,0 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from attest import assert_hook, Tests, warns # pylint: disable=W0611
from datetime import date, datetime
from django_attest import settings, TestContext
import django_tables2 as tables
from django_tables2.utils import build_request
from django_tables2 import A, Attrs
from django.db import models
from django.db.models.fields.files import FieldFile
from django.core.urlresolvers import reverse
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage
from django.template import Context, Template
from django.utils.translation import ugettext
from django.utils.safestring import mark_safe, SafeData
try:
from django.utils import timezone
except ImportError:
timezone = None
from os.path import dirname, join
import pytz
from .app.models import Person
from .templates import attrs, parse
booleancolumn = Tests()
@booleancolumn.test
def should_be_used_for_booleanfield():
class BoolModel(models.Model):
field = models.BooleanField()
class Table(tables.Table):
class Meta:
model = BoolModel
column = Table.base_columns["field"]
assert type(column) == tables.BooleanColumn
assert column.empty_values != ()
@booleancolumn.test
def should_be_used_for_nullbooleanfield():
class NullBoolModel(models.Model):
field = models.NullBooleanField()
class Table(tables.Table):
class Meta:
model = NullBoolModel
column = Table.base_columns["field"]
assert type(column) == tables.BooleanColumn
assert column.empty_values == ()
@booleancolumn.test
def treat_none_different_from_false():
class Table(tables.Table):
col = tables.BooleanColumn(null=False, default="---")
table = Table([{"col": None}])
assert table.rows[0]["col"] == "---"
@booleancolumn.test
def treat_none_as_false():
class Table(tables.Table):
col = tables.BooleanColumn(null=True)
table = Table([{"col": None}])
assert table.rows[0]["col"] == '<span class="false">✘</span>'
@booleancolumn.test
def span_attrs():
class Table(tables.Table):
col = tables.BooleanColumn(attrs={"span": {"key": "value"}})
table = Table([{"col": True}])
assert attrs(table.rows[0]["col"]) == {"class": "true", "key": "value"}
checkboxcolumn = Tests()
@checkboxcolumn.test
def attrs_should_be_translated_for_backwards_compatibility():
with warns(DeprecationWarning):
class TestTable(tables.Table):
col = tables.CheckBoxColumn(header_attrs={"th_key": "th_value"},
attrs={"td_key": "td_value"})
table = TestTable([{"col": "data"}])
assert attrs(table.columns["col"].header) == {"type": "checkbox", "th_key": "th_value"}
assert attrs(table.rows[0]["col"]) == {"type": "checkbox", "td_key": "td_value", "value": "data", "name": "col"}
@checkboxcolumn.test
def new_attrs_should_be_supported():
with warns(DeprecationWarning):
class TestTable(tables.Table):
col1 = tables.CheckBoxColumn(attrs=Attrs(th__input={"th_key": "th_value"},
td__input={"td_key": "td_value"}))
col2 = tables.CheckBoxColumn(attrs=Attrs(input={"key": "value"}))
table = TestTable([{"col1": "data", "col2": "data"}])
assert attrs(table.columns["col1"].header) == {"type": "checkbox", "th_key": "th_value"}
assert attrs(table.rows[0]["col1"]) == {"type": "checkbox", "td_key": "td_value", "value": "data", "name": "col1"}
assert attrs(table.columns["col2"].header) == {"type": "checkbox", "key": "value"}
assert attrs(table.rows[0]["col2"]) == {"type": "checkbox", "key": "value", "value": "data", "name": "col2"}
general = Tests()
@general.test
def column_render_supports_kwargs():
class TestColumn(tables.Column):
def render(self, **kwargs):
expected = set(("record", "value", "column", "bound_column",
"bound_row", "table"))
actual = set(kwargs.keys())
assert actual == expected
return "success"
class TestTable(tables.Table):
foo = TestColumn()
table = TestTable([{"foo": "bar"}])
assert table.rows[0]["foo"] == "success"
@general.test
def column_header_should_use_titlised_verbose_name_unless_given_explicitly():
class SimpleTable(tables.Table):
basic = tables.Column()
acronym = tables.Column(verbose_name="has FBI help")
table = SimpleTable([])
assert table.columns["basic"].header == "Basic"
assert table.columns["acronym"].header == "has FBI help"
@general.test
def should_support_safe_verbose_name():
class SimpleTable(tables.Table):
safe = tables.Column(verbose_name=mark_safe("<b>Safe</b>"))
table = SimpleTable([])
assert isinstance(table.columns["safe"].header, SafeData)
@general.test
def should_support_safe_verbose_name_via_model():
class PersonTable(tables.Table):
safe = tables.Column()
table = PersonTable(Person.objects.all())
assert isinstance(table.columns["safe"].header, SafeData)
@general.test
def sortable_backwards_compatibility():
# Table.Meta.sortable (not set)
class SimpleTable(tables.Table):
name = tables.Column()
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is True
# Table.Meta.sortable = False
with warns(DeprecationWarning):
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
sortable = False
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is False # backwards compatible
assert table.columns['name'].orderable is False
# Table.Meta.sortable = True
with warns(DeprecationWarning):
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
sortable = True
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is True # backwards compatible
assert table.columns['name'].orderable is True
@general.test
def orderable():
# Table.Meta.orderable = False
class SimpleTable(tables.Table):
name = tables.Column()
table = SimpleTable([])
assert table.columns['name'].orderable is True
# Table.Meta.orderable = False
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
orderable = False
table = SimpleTable([])
assert table.columns['name'].orderable is False
with warns(DeprecationWarning):
assert table.columns['name'].sortable is False # backwards compatible
# Table.Meta.orderable = True
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
orderable = True
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is True # backwards compatible
assert table.columns['name'].orderable is True
@general.test
def order_by_defaults_to_accessor():
class SimpleTable(tables.Table):
foo = tables.Column(accessor="bar")
table = SimpleTable([])
assert table.columns["foo"].order_by == ("bar", )
@general.test
def supports_order_by():
class SimpleTable(tables.Table):
name = tables.Column(order_by=("last_name", "-first_name"))
age = tables.Column()
table = SimpleTable([], order_by=("-age", ))
# alias
assert table.columns["name"].order_by_alias == "name"
assert table.columns["age"].order_by_alias == "-age"
# order by
assert table.columns["name"].order_by == ("last_name", "-first_name")
assert table.columns["age"].order_by == ("-age", )
# now try with name ordered
table = SimpleTable([], order_by=("-name", ))
# alias
assert table.columns["name"].order_by_alias == "-name"
assert table.columns["age"].order_by_alias == "age"
# alias next
assert table.columns["name"].order_by_alias.next == "name"
assert table.columns["age"].order_by_alias.next == "age"
# order by
assert table.columns["name"].order_by == ("-last_name", "first_name")
assert table.columns["age"].order_by == ("age", )
@general.test
def supports_is_ordered():
class SimpleTable(tables.Table):
name = tables.Column()
# sorted
table = SimpleTable([], order_by='name')
assert table.columns["name"].is_ordered
# unsorted
table = SimpleTable([])
assert not table.columns["name"].is_ordered
@general.test
def translation():
"""
Tests different types of values for the ``verbose_name`` property of a
column.
"""
class TranslationTable(tables.Table):
normal = tables.Column(verbose_name=ugettext("Normal"))
lazy = tables.Column(verbose_name=ugettext("Lazy"))
table = TranslationTable([])
assert "Normal" == table.columns["normal"].header
assert "Lazy" == table.columns["lazy"].header
@general.test
def sequence():
"""
Ensures that the sequence of columns is configurable.
"""
class TestTable(tables.Table):
a = tables.Column()
b = tables.Column()
c = tables.Column()
assert ["a", "b", "c"] == TestTable([]).columns.names()
assert ["b", "a", "c"] == TestTable([], sequence=("b", "a", "c")).columns.names()
class TestTable2(TestTable):
class Meta:
sequence = ("b", "a", "c")
assert ["b", "a", "c"] == TestTable2([]).columns.names()
assert ["a", "b", "c"] == TestTable2([], sequence=("a", "b", "c")).columns.names()
class TestTable3(TestTable):
class Meta:
sequence = ("c", )
assert ["c", "a", "b"] == TestTable3([]).columns.names()
assert ["c", "a", "b"] == TestTable([], sequence=("c", )).columns.names()
class TestTable4(TestTable):
class Meta:
sequence = ("...", )
assert ["a", "b", "c"] == TestTable4([]).columns.names()
assert ["a", "b", "c"] == TestTable([], sequence=("...", )).columns.names()
class TestTable5(TestTable):
class Meta:
sequence = ("b", "...")
assert ["b", "a", "c"] == TestTable5([]).columns.names()
assert ["b", "a", "c"] == TestTable([], sequence=("b", "...")).columns.names()
class TestTable6(TestTable):
class Meta:
sequence = ("...", "b")
assert ["a", "c", "b"] == TestTable6([]).columns.names()
assert ["a", "c", "b"] == TestTable([], sequence=("...", "b")).columns.names()
class TestTable7(TestTable):
class Meta:
sequence = ("b", "...", "a")
assert ["b", "c", "a"] == TestTable7([]).columns.names()
assert ["b", "c", "a"] == TestTable([], sequence=("b", "...", "a")).columns.names()
# Let's test inheritence
class TestTable8(TestTable):
d = tables.Column()
e = tables.Column()
f = tables.Column()
class Meta:
sequence = ("d", "...")
class TestTable9(TestTable):
d = tables.Column()
e = tables.Column()
f = tables.Column()
assert ["d", "a", "b", "c", "e", "f"] == TestTable8([]).columns.names()
assert ["d", "a", "b", "c", "e", "f"] == TestTable9([], sequence=("d", "...")).columns.names()
@general.test
def should_support_both_meta_sequence_and_constructor_exclude():
"""
Issue #32 describes a problem when both ``Meta.sequence`` and
``Table(..., exclude=...)`` are used on a single table. The bug caused an
exception to be raised when the table was iterated.
"""
class SequencedTable(tables.Table):
a = tables.Column()
b = tables.Column()
c = tables.Column()
class Meta:
sequence = ('a', '...')
table = SequencedTable([], exclude=('c', ))
table.as_html()
@general.test
def bound_columns_should_support_indexing():
class SimpleTable(tables.Table):
a = tables.Column()
b = tables.Column()
table = SimpleTable([])
assert 'b' == table.columns[1].name
assert 'b' == table.columns['b'].name
@general.test
def cell_attrs_applies_to_td_and_th():
class SimpleTable(tables.Table):
a = tables.Column(attrs={"cell": {"key": "value"}})
# providing data ensures 1 row is rendered
table = SimpleTable([{"a": "value"}])
root = parse(table.as_html())
assert root.findall('.//thead/tr/th')[0].attrib == {"key": "value", "class": "a orderable sortable"}
assert root.findall('.//tbody/tr/td')[0].attrib == {"key": "value", "class": "a"}
@general.test
def cells_are_automatically_given_column_name_as_class():
class SimpleTable(tables.Table):
a = tables.Column()
table = SimpleTable([{"a": "value"}])
root = parse(table.as_html())
assert root.findall('.//thead/tr/th')[0].attrib == {"class": "a orderable sortable"}
assert root.findall('.//tbody/tr/td')[0].attrib == {"class": "a"}
@general.test
def th_are_given_sortable_class_if_column_is_orderable():
class SimpleTable(tables.Table):
a = tables.Column()
b = tables.Column(orderable=False)
table = SimpleTable([{"a": "value"}])
root = parse(table.as_html())
# return classes of an element as a set
classes = lambda x: set(x.attrib["class"].split())
assert "sortable" in classes(root.findall('.//thead/tr/th')[0])
assert "sortable" not in classes(root.findall('.//thead/tr/th')[1])
# Now try with an ordered table
table = SimpleTable([], order_by="a")
root = parse(table.as_html())
# return classes of an element as a set
assert "sortable" in classes(root.findall('.//thead/tr/th')[0])
assert "asc" in classes(root.findall('.//thead/tr/th')[0])
assert "sortable" not in classes(root.findall('.//thead/tr/th')[1])
@general.test
def empty_values_triggers_default():
class Table(tables.Table):
a = tables.Column(empty_values=(1, 2), default="--")
table = Table([{"a": 1}, {"a": 2}, {"a": 3}, {"a": 4}])
assert [x["a"] for x in table.rows] == ["--", "--", 3, 4]
linkcolumn = Tests()
linkcolumn.context(TestContext())
@linkcolumn.test
def unicode():
"""Test LinkColumn"""
# test unicode values + headings
class UnicodeTable(tables.Table):
first_name = tables.LinkColumn('person', args=[A('pk')])
last_name = tables.LinkColumn('person', args=[A('pk')], verbose_name='äÚ¨´ˆÁ˜¨ˆ˜˘Ú…Ò˚ˆπ∆ˆ´')
dataset = [
{'pk': 1, 'first_name': 'Brädley', 'last_name': '∆yers'},
{'pk': 2, 'first_name': 'Chr…s', 'last_name': 'DÒble'},
]
table = UnicodeTable(dataset)
request = build_request('/some-url/')
template = Template('{% load django_tables2 %}{% render_table table %}')
html = template.render(Context({'request': request, 'table': table}))
assert 'Brädley' in html
assert '∆yers' in html
assert 'Chr…s' in html
assert 'DÒble' in html
@linkcolumn.test
def null_foreign_key():
class PersonTable(tables.Table):
first_name = tables.Column()
last_name = tables.Column()
occupation = tables.LinkColumn('occupation', args=[A('occupation.pk')])
Person.objects.create(first_name='bradley', last_name='ayers')
table = PersonTable(Person.objects.all())
table.as_html()
@linkcolumn.test
def kwargs():
class PersonTable(tables.Table):
a = tables.LinkColumn('occupation', kwargs={"pk": A('a')})
html = PersonTable([{"a": 0}, {"a": 1}]).as_html()
assert reverse("occupation", kwargs={"pk": 0}) in html
assert reverse("occupation", kwargs={"pk": 1}) in html
@linkcolumn.test
def html_escape_value():
class PersonTable(tables.Table):
name = tables.LinkColumn("escaping", kwargs={"pk": A("pk")})
table = PersonTable([{"name": "<brad>", "pk": 1}])
assert table.rows[0]["name"] == '<a href="/&amp;&#39;%22/1/">&lt;brad&gt;</a>'
@linkcolumn.test
def old_style_attrs_should_still_work():
with warns(DeprecationWarning):
class TestTable(tables.Table):
col = tables.LinkColumn('occupation', kwargs={"pk": A('col')},
attrs={"title": "Occupation Title"})
table = TestTable([{"col": 0}])
assert attrs(table.rows[0]["col"]) == {"href": reverse("occupation", kwargs={"pk": 0}),
"title": "Occupation Title"}
@linkcolumn.test
def a_attrs_should_be_supported():
class TestTable(tables.Table):
col = tables.LinkColumn('occupation', kwargs={"pk": A('col')},
attrs={"a": {"title": "Occupation Title"}})
table = TestTable([{"col": 0}])
assert attrs(table.rows[0]["col"]) == {"href": reverse("occupation", kwargs={"pk": 0}),
"title": "Occupation Title"}
@linkcolumn.test
def defaults():
class Table(tables.Table):
link = tables.LinkColumn('occupation', kwargs={"pk": 1}, default="xyz")
table = Table([{}])
assert table.rows[0]['link'] == 'xyz'
templatecolumn = Tests()
@templatecolumn.test
def should_handle_context_on_table():
class TestTable(tables.Table):
col_code = tables.TemplateColumn(template_code="code:{{ record.col }}{{ STATIC_URL }}")
col_name = tables.TemplateColumn(template_name="test_template_column.html")
table = TestTable([{"col": "brad"}])
assert table.rows[0]["col_code"] == "code:brad"
assert table.rows[0]["col_name"] == "name:brad"
table.context = Context({"STATIC_URL": "/static/"})
assert table.rows[0]["col_code"] == "code:brad/static/"
assert table.rows[0]["col_name"] == "name:brad/static/"
@templatecolumn.test
def should_support_default():
class Table(tables.Table):
foo = tables.TemplateColumn("default={{ default }}", default="bar")
table = Table([{}])
assert table.rows[0]["foo"] == "default=bar"
@templatecolumn.test
def should_support_value():
class Table(tables.Table):
foo = tables.TemplateColumn("value={{ value }}")
table = Table([{"foo": "bar"}])
assert table.rows[0]["foo"] == "value=bar"
urlcolumn = Tests()
@urlcolumn.test
def should_turn_url_into_hyperlink():
class TestTable(tables.Table):
url = tables.URLColumn()
table = TestTable([{"url": "http://example.com"}])
assert table.rows[0]["url"] == '<a href="http://example.com">http://example.com</a>'
@urlcolumn.test
def should_be_used_for_urlfields():
class URLModel(models.Model):
field = models.URLField()
class Table(tables.Table):
class Meta:
model = URLModel
assert type(Table.base_columns["field"]) == tables.URLColumn
emailcolumn = Tests()
@emailcolumn.test
def should_turn_email_address_into_hyperlink():
class Table(tables.Table):
email = tables.EmailColumn()
table = Table([{"email": "test@example.com"}])
assert table.rows[0]["email"] == '<a href="mailto:test@example.com">test@example.com</a>'
@emailcolumn.test
def should_render_default_for_blank():
class Table(tables.Table):
email = tables.EmailColumn(default="---")
table = Table([{"email": ""}])
assert table.rows[0]["email"] == '---'
@emailcolumn.test
def should_be_used_for_datetimefields():
class EmailModel(models.Model):
field = models.EmailField()
class Table(tables.Table):
class Meta:
model = EmailModel
assert type(Table.base_columns["field"]) == tables.EmailColumn
datecolumn = Tests()
# Format string: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#date
# D -- Day of the week, textual, 3 letters -- 'Fri'
# b -- Month, textual, 3 letters, lowercase -- 'jan'
# Y -- Year, 4 digits. -- '1999'
@datecolumn.test
def should_handle_explicit_format():
class TestTable(tables.Table):
date = tables.DateColumn(format="D b Y")
class Meta:
default = ""
table = TestTable([{"date": date(2012, 9, 11)},
{"date": None}])
assert table.rows[0]["date"] == "Tue sep 2012"
assert table.rows[1]["date"] == ""
@datecolumn.test
def should_handle_long_format():
with settings(DATE_FORMAT="D Y b"):
class TestTable(tables.Table):
date = tables.DateColumn(short=False)
class Meta:
default = ""
table = TestTable([{"date": date(2012, 9, 11)},
{"date": None}])
assert table.rows[0]["date"] == "Tue 2012 sep"
assert table.rows[1]["date"] == ""
@datecolumn.test
def should_handle_short_format():
with settings(SHORT_DATE_FORMAT="b Y D"):
class TestTable(tables.Table):
date = tables.DateColumn(short=True)
class Meta:
default = ""
table = TestTable([{"date": date(2012, 9, 11)},
{"date": None}])
assert table.rows[0]["date"] == "sep 2012 Tue"
assert table.rows[1]["date"] == ""
@datecolumn.test
def should_be_used_for_datefields():
class DateModel(models.Model):
field = models.DateField()
class Table(tables.Table):
class Meta:
model = DateModel
assert type(Table.base_columns["field"]) == tables.DateColumn
datetimecolumn = Tests()
# Format string: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#date
# D -- Day of the week, textual, 3 letters -- 'Fri'
# b -- Month, textual, 3 letters, lowercase -- 'jan'
# Y -- Year, 4 digits. -- '1999'
# A -- 'AM' or 'PM'. -- 'AM'
# f -- Time, in 12-hour hours[:minutes] -- '1', '1:30'
@datetimecolumn.context
def dt():
dt = datetime(2012, 9, 11, 12, 30, 0)
if timezone:
# If the version of Django has timezone support, convert from naive to
# UTC, the test project uses Australia/Brisbane so regardless the
# output from the column should be the same.
dt = pytz.timezone("Australia/Brisbane").localize(dt)
yield dt
@datetimecolumn.test
def should_handle_explicit_format(dt):
class TestTable(tables.Table):
date = tables.DateTimeColumn(format="D b Y")
class Meta:
default = ""
table = TestTable([{"date": dt}, {"date": None}])
assert table.rows[0]["date"] == "Tue sep 2012"
assert table.rows[1]["date"] == ""
@datetimecolumn.test
def should_handle_long_format(dt):
class TestTable(tables.Table):
date = tables.DateTimeColumn(short=False)
class Meta:
default = ""
with settings(DATETIME_FORMAT="D Y b A f"):
table = TestTable([{"date": dt}, {"date": None}])
assert table.rows[0]["date"] == "Tue 2012 sep PM 12:30"
assert table.rows[1]["date"] == ""
@datetimecolumn.test
def should_handle_short_format(dt):
class TestTable(tables.Table):
date = tables.DateTimeColumn(short=True)
class Meta:
default = ""
with settings(SHORT_DATETIME_FORMAT="b Y D A f"):
table = TestTable([{"date": dt}, {"date": None}])
assert table.rows[0]["date"] == "sep 2012 Tue PM 12:30"
assert table.rows[1]["date"] == ""
@datetimecolumn.test
def should_be_used_for_datetimefields():
class DateTimeModel(models.Model):
field = models.DateTimeField()
class Table(tables.Table):
class Meta:
model = DateTimeModel
assert type(Table.base_columns["field"]) == tables.DateTimeColumn
filecolumn = Tests()
@filecolumn.context
def template_storage_and_column():
"""Provide a storage that exposes the test templates"""
root = join(dirname(__file__), "app", "templates")
storage = FileSystemStorage(location=root, base_url="/baseurl/")
column = tables.FileColumn(attrs={"span": {"class": "span"},
"a": {"class": "a"}})
yield column, storage
@filecolumn.test
def should_be_used_for_filefields():
class FileModel(models.Model):
field = models.FileField()
class Table(tables.Table):
class Meta:
model = FileModel
assert type(Table.base_columns["field"]) == tables.FileColumn
@filecolumn.test
def filecolumn_supports_storage_file(column, storage):
file_ = storage.open("child/foo.html")
try:
root = parse(column.render(value=file_))
finally:
file_.close()
path = file_.name
assert root.tag == "span"
assert root.attrib == {"class": "span exists", "title": path}
assert root.text == "foo.html"
@filecolumn.test
def filecolumn_supports_contentfile(column):
name = "foobar.html"
file_ = ContentFile('')
file_.name = name # Django <1.4 compatible
root = parse(column.render(value=file_))
assert root.tag == "span"
assert root.attrib == {"title": name, "class": "span"}
assert root.text == "foobar.html"
@filecolumn.test
def filecolumn_supports_fieldfile(column, storage):
field = models.FileField(storage=storage)
name = "child/foo.html"
fieldfile = FieldFile(instance=None, field=field, name=name)
root = parse(column.render(value=fieldfile))
assert root.tag == "a"
assert root.attrib == {"class": "a exists", "title": name,
"href": "/baseurl/child/foo.html"}
assert root.text == "foo.html"
# Now try a file that doesn't exist
name = "child/does_not_exist.html"
fieldfile = FieldFile(instance=None, field=field, name=name)
html = column.render(value=fieldfile)
root = parse(html)
assert root.tag == "a"
assert root.attrib == {"class": "a missing", "title": name,
"href": "/baseurl/child/does_not_exist.html"}
assert root.text == "does_not_exist.html"
columns = Tests([booleancolumn, checkboxcolumn, datecolumn, datetimecolumn,
emailcolumn, filecolumn, general, linkcolumn, templatecolumn,
urlcolumn])

View File

View File

@ -0,0 +1,56 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
import django_tables2 as tables
from django.db import models
from ..utils import attrs
def test_should_be_used_for_booleanfield():
class BoolModel(models.Model):
field = models.BooleanField()
class Table(tables.Table):
class Meta:
model = BoolModel
column = Table.base_columns["field"]
assert type(column) == tables.BooleanColumn
assert column.empty_values != ()
def test_should_be_used_for_nullbooleanfield():
class NullBoolModel(models.Model):
field = models.NullBooleanField()
class Table(tables.Table):
class Meta:
model = NullBoolModel
column = Table.base_columns["field"]
assert type(column) == tables.BooleanColumn
assert column.empty_values == ()
def test_treat_none_different_from_false():
class Table(tables.Table):
col = tables.BooleanColumn(null=False, default="---")
table = Table([{"col": None}])
assert table.rows[0]["col"] == "---"
def test_treat_none_as_false():
class Table(tables.Table):
col = tables.BooleanColumn(null=True)
table = Table([{"col": None}])
assert table.rows[0]["col"] == '<span class="false">✘</span>'
def test_span_attrs():
class Table(tables.Table):
col = tables.BooleanColumn(attrs={"span": {"key": "value"}})
table = Table([{"col": True}])
assert attrs(table.rows[0]["col"]) == {"class": "true", "key": "value"}

View File

@ -0,0 +1,31 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
import django_tables2 as tables
from django_tables2 import Attrs
from ..utils import attrs, warns
def test_attrs_should_be_translated_for_backwards_compatibility():
with warns(DeprecationWarning):
class TestTable(tables.Table):
col = tables.CheckBoxColumn(header_attrs={"th_key": "th_value"},
attrs={"td_key": "td_value"})
table = TestTable([{"col": "data"}])
assert attrs(table.columns["col"].header) == {"type": "checkbox", "th_key": "th_value"}
assert attrs(table.rows[0]["col"]) == {"type": "checkbox", "td_key": "td_value", "value": "data", "name": "col"}
def new_attrs_should_be_supported():
with warns(DeprecationWarning):
class TestTable(tables.Table):
col1 = tables.CheckBoxColumn(attrs=Attrs(th__input={"th_key": "th_value"},
td__input={"td_key": "td_value"}))
col2 = tables.CheckBoxColumn(attrs=Attrs(input={"key": "value"}))
table = TestTable([{"col1": "data", "col2": "data"}])
assert attrs(table.columns["col1"].header) == {"type": "checkbox", "th_key": "th_value"}
assert attrs(table.rows[0]["col1"]) == {"type": "checkbox", "td_key": "td_value", "value": "data", "name": "col1"}
assert attrs(table.columns["col2"].header) == {"type": "checkbox", "key": "value"}
assert attrs(table.rows[0]["col2"]) == {"type": "checkbox", "key": "value", "value": "data", "name": "col2"}

View File

@ -0,0 +1,66 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from datetime import date
from django.db import models
import django_tables2 as tables
# Format string: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#date
# D -- Day of the week, textual, 3 letters -- 'Fri'
# b -- Month, textual, 3 letters, lowercase -- 'jan'
# Y -- Year, 4 digits. -- '1999'
def test_should_handle_explicit_format():
class TestTable(tables.Table):
date = tables.DateColumn(format="D b Y")
class Meta:
default = ""
table = TestTable([{"date": date(2012, 9, 11)},
{"date": None}])
assert table.rows[0]["date"] == "Tue sep 2012"
assert table.rows[1]["date"] == ""
def test_should_handle_long_format(settings):
settings.DATE_FORMAT = "D Y b"
class TestTable(tables.Table):
date = tables.DateColumn(short=False)
class Meta:
default = ""
table = TestTable([{"date": date(2012, 9, 11)},
{"date": None}])
assert table.rows[0]["date"] == "Tue 2012 sep"
assert table.rows[1]["date"] == ""
def test_should_handle_short_format(settings):
settings.SHORT_DATE_FORMAT = "b Y D"
class TestTable(tables.Table):
date = tables.DateColumn(short=True)
class Meta:
default = ""
table = TestTable([{"date": date(2012, 9, 11)},
{"date": None}])
assert table.rows[0]["date"] == "sep 2012 Tue"
assert table.rows[1]["date"] == ""
def test_should_be_used_for_datefields():
class DateModel(models.Model):
field = models.DateField()
class Table(tables.Table):
class Meta:
model = DateModel
assert type(Table.base_columns["field"]) == tables.DateColumn

View File

@ -0,0 +1,81 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from datetime import datetime
from django.db import models
import django_tables2 as tables
try:
from django.utils import timezone
except ImportError:
timezone = None
import pytz
import pytest
# Format string: https://docs.djangoproject.com/en/1.4/ref/templates/builtins/#date
# D -- Day of the week, textual, 3 letters -- 'Fri'
# b -- Month, textual, 3 letters, lowercase -- 'jan'
# Y -- Year, 4 digits. -- '1999'
# A -- 'AM' or 'PM'. -- 'AM'
# f -- Time, in 12-hour hours[:minutes] -- '1', '1:30'
@pytest.yield_fixture
def dt():
dt = datetime(2012, 9, 11, 12, 30, 0)
if timezone:
# If the version of Django has timezone support, convert from naive to
# UTC, the test project uses Australia/Brisbane so regardless the
# output from the column should be the same.
dt = pytz.timezone("Australia/Brisbane").localize(dt)
yield dt
def test_should_handle_explicit_format(dt):
class TestTable(tables.Table):
date = tables.DateTimeColumn(format="D b Y")
class Meta:
default = ""
table = TestTable([{"date": dt}, {"date": None}])
assert table.rows[0]["date"] == "Tue sep 2012"
assert table.rows[1]["date"] == ""
def test_should_handle_long_format(dt, settings):
class TestTable(tables.Table):
date = tables.DateTimeColumn(short=False)
class Meta:
default = ""
settings.DATETIME_FORMAT = "D Y b A f"
table = TestTable([{"date": dt}, {"date": None}])
assert table.rows[0]["date"] == "Tue 2012 sep PM 12:30"
assert table.rows[1]["date"] == ""
def test_should_handle_short_format(dt, settings):
class TestTable(tables.Table):
date = tables.DateTimeColumn(short=True)
class Meta:
default = ""
settings.SHORT_DATETIME_FORMAT = "b Y D A f"
table = TestTable([{"date": dt}, {"date": None}])
assert table.rows[0]["date"] == "sep 2012 Tue PM 12:30"
assert table.rows[1]["date"] == ""
def test_should_be_used_for_datetimefields():
class DateTimeModel(models.Model):
field = models.DateTimeField()
class Table(tables.Table):
class Meta:
model = DateTimeModel
assert type(Table.base_columns["field"]) == tables.DateTimeColumn

View File

@ -0,0 +1,39 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from django.db import models
import django_tables2 as tables
try:
from django.utils import timezone
except ImportError:
timezone = None
def test_should_turn_email_address_into_hyperlink():
class Table(tables.Table):
email = tables.EmailColumn()
table = Table([{"email": "test@example.com"}])
assert table.rows[0]["email"] == '<a href="mailto:test@example.com">test@example.com</a>'
def test_should_render_default_for_blank():
class Table(tables.Table):
email = tables.EmailColumn(default="---")
table = Table([{"email": ""}])
assert table.rows[0]["email"] == '---'
def test_should_be_used_for_datetimefields():
class EmailModel(models.Model):
field = models.EmailField()
class Table(tables.Table):
class Meta:
model = EmailModel
assert type(Table.base_columns["field"]) == tables.EmailColumn

View File

@ -0,0 +1,81 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from os.path import dirname, join
from django.db import models
from django.db.models.fields.files import FieldFile
from django.core.files.base import ContentFile
from django.core.files.storage import FileSystemStorage
import pytest
import django_tables2 as tables
from ..utils import parse
@pytest.yield_fixture
def storage():
"""Provide a storage that exposes the test templates"""
root = join(dirname(__file__), "..", "app", "templates")
yield FileSystemStorage(location=root, base_url="/baseurl/")
@pytest.yield_fixture
def column():
yield tables.FileColumn(attrs={"span": {"class": "span"},
"a": {"class": "a"}})
def test_should_be_used_for_filefields():
class FileModel(models.Model):
field = models.FileField()
class Table(tables.Table):
class Meta:
model = FileModel
assert type(Table.base_columns["field"]) == tables.FileColumn
def test_filecolumn_supports_storage_file(column, storage):
file_ = storage.open("child/foo.html")
try:
root = parse(column.render(value=file_))
finally:
file_.close()
path = file_.name
assert root.tag == "span"
assert root.attrib == {"class": "span exists", "title": path}
assert root.text == "foo.html"
def test_filecolumn_supports_contentfile(column):
name = "foobar.html"
file_ = ContentFile('')
file_.name = name # Django <1.4 compatible
root = parse(column.render(value=file_))
assert root.tag == "span"
assert root.attrib == {"title": name, "class": "span"}
assert root.text == "foobar.html"
def test_filecolumn_supports_fieldfile(column, storage):
field = models.FileField(storage=storage)
name = "child/foo.html"
fieldfile = FieldFile(instance=None, field=field, name=name)
root = parse(column.render(value=fieldfile))
assert root.tag == "a"
assert root.attrib == {"class": "a exists", "title": name,
"href": "/baseurl/child/foo.html"}
assert root.text == "foo.html"
# Now try a file that doesn't exist
name = "child/does_not_exist.html"
fieldfile = FieldFile(instance=None, field=field, name=name)
html = column.render(value=fieldfile)
root = parse(html)
assert root.tag == "a"
assert root.attrib == {"class": "a missing", "title": name,
"href": "/baseurl/child/does_not_exist.html"}
assert root.text == "does_not_exist.html"

View File

@ -0,0 +1,317 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy
from django.utils.safestring import mark_safe, SafeData
import django_tables2 as tables
from ..app.models import Person
from ..utils import parse, warns
def test_column_render_supports_kwargs():
class TestColumn(tables.Column):
def render(self, **kwargs):
expected = {"record", "value", "column", "bound_column", "bound_row", "table"}
actual = set(kwargs.keys())
assert actual == expected
return "success"
class TestTable(tables.Table):
foo = TestColumn()
table = TestTable([{"foo": "bar"}])
assert table.rows[0]["foo"] == "success"
def test_column_header_should_use_titlised_verbose_name_unless_given_explicitly():
class SimpleTable(tables.Table):
basic = tables.Column()
acronym = tables.Column(verbose_name="has FBI help")
table = SimpleTable([])
assert table.columns["basic"].header == "Basic"
assert table.columns["acronym"].header == "has FBI help"
def test_should_support_safe_verbose_name():
class SimpleTable(tables.Table):
safe = tables.Column(verbose_name=mark_safe("<b>Safe</b>"))
table = SimpleTable([])
assert isinstance(table.columns["safe"].header, SafeData)
def test_should_support_safe_verbose_name_via_model():
class PersonTable(tables.Table):
safe = tables.Column()
table = PersonTable(Person.objects.all())
assert isinstance(table.columns["safe"].header, SafeData)
def test_sortable_backwards_compatibility():
# Table.Meta.sortable (not set)
class SimpleTable(tables.Table):
name = tables.Column()
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is True
# Table.Meta.sortable = False
with warns(DeprecationWarning):
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
sortable = False
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is False # backwards compatible
assert table.columns['name'].orderable is False
# Table.Meta.sortable = True
with warns(DeprecationWarning):
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
sortable = True
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is True # backwards compatible
assert table.columns['name'].orderable is True
def test_orderable():
# Table.Meta.orderable = False
class SimpleTable(tables.Table):
name = tables.Column()
table = SimpleTable([])
assert table.columns['name'].orderable is True
# Table.Meta.orderable = False
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
orderable = False
table = SimpleTable([])
assert table.columns['name'].orderable is False
with warns(DeprecationWarning):
assert table.columns['name'].sortable is False # backwards compatible
# Table.Meta.orderable = True
class SimpleTable(tables.Table):
name = tables.Column()
class Meta:
orderable = True
table = SimpleTable([])
with warns(DeprecationWarning):
assert table.columns['name'].sortable is True # backwards compatible
assert table.columns['name'].orderable is True
def test_order_by_defaults_to_accessor():
class SimpleTable(tables.Table):
foo = tables.Column(accessor="bar")
table = SimpleTable([])
assert table.columns["foo"].order_by == ("bar", )
def test_supports_order_by():
class SimpleTable(tables.Table):
name = tables.Column(order_by=("last_name", "-first_name"))
age = tables.Column()
table = SimpleTable([], order_by=("-age", ))
# alias
assert table.columns["name"].order_by_alias == "name"
assert table.columns["age"].order_by_alias == "-age"
# order by
assert table.columns["name"].order_by == ("last_name", "-first_name")
assert table.columns["age"].order_by == ("-age", )
# now try with name ordered
table = SimpleTable([], order_by=("-name", ))
# alias
assert table.columns["name"].order_by_alias == "-name"
assert table.columns["age"].order_by_alias == "age"
# alias next
assert table.columns["name"].order_by_alias.next == "name"
assert table.columns["age"].order_by_alias.next == "age"
# order by
assert table.columns["name"].order_by == ("-last_name", "first_name")
assert table.columns["age"].order_by == ("age", )
def test_supports_is_ordered():
class SimpleTable(tables.Table):
name = tables.Column()
# sorted
table = SimpleTable([], order_by='name')
assert table.columns["name"].is_ordered
# unsorted
table = SimpleTable([])
assert not table.columns["name"].is_ordered
def test_translation():
"""
Tests different types of values for the ``verbose_name`` property of a
column.
"""
class TranslationTable(tables.Table):
text = tables.Column(verbose_name=ugettext_lazy("Text"))
table = TranslationTable([])
assert "Text" == table.columns["text"].header
def test_sequence():
"""
Ensures that the sequence of columns is configurable.
"""
class TestTable(tables.Table):
a = tables.Column()
b = tables.Column()
c = tables.Column()
assert ["a", "b", "c"] == TestTable([]).columns.names()
assert ["b", "a", "c"] == TestTable([], sequence=("b", "a", "c")).columns.names()
class TestTable2(TestTable):
class Meta:
sequence = ("b", "a", "c")
assert ["b", "a", "c"] == TestTable2([]).columns.names()
assert ["a", "b", "c"] == TestTable2([], sequence=("a", "b", "c")).columns.names()
class TestTable3(TestTable):
class Meta:
sequence = ("c", )
assert ["c", "a", "b"] == TestTable3([]).columns.names()
assert ["c", "a", "b"] == TestTable([], sequence=("c", )).columns.names()
class TestTable4(TestTable):
class Meta:
sequence = ("...", )
assert ["a", "b", "c"] == TestTable4([]).columns.names()
assert ["a", "b", "c"] == TestTable([], sequence=("...", )).columns.names()
class TestTable5(TestTable):
class Meta:
sequence = ("b", "...")
assert ["b", "a", "c"] == TestTable5([]).columns.names()
assert ["b", "a", "c"] == TestTable([], sequence=("b", "...")).columns.names()
class TestTable6(TestTable):
class Meta:
sequence = ("...", "b")
assert ["a", "c", "b"] == TestTable6([]).columns.names()
assert ["a", "c", "b"] == TestTable([], sequence=("...", "b")).columns.names()
class TestTable7(TestTable):
class Meta:
sequence = ("b", "...", "a")
assert ["b", "c", "a"] == TestTable7([]).columns.names()
assert ["b", "c", "a"] == TestTable([], sequence=("b", "...", "a")).columns.names()
# Let's test inheritence
class TestTable8(TestTable):
d = tables.Column()
e = tables.Column()
f = tables.Column()
class Meta:
sequence = ("d", "...")
class TestTable9(TestTable):
d = tables.Column()
e = tables.Column()
f = tables.Column()
assert ["d", "a", "b", "c", "e", "f"] == TestTable8([]).columns.names()
assert ["d", "a", "b", "c", "e", "f"] == TestTable9([], sequence=("d", "...")).columns.names()
def test_should_support_both_meta_sequence_and_constructor_exclude():
"""
Issue #32 describes a problem when both ``Meta.sequence`` and
``Table(..., exclude=...)`` are used on a single table. The bug caused an
exception to be raised when the table was iterated.
"""
class SequencedTable(tables.Table):
a = tables.Column()
b = tables.Column()
c = tables.Column()
class Meta:
sequence = ('a', '...')
table = SequencedTable([], exclude=('c', ))
table.as_html()
def test_bound_columns_should_support_indexing():
class SimpleTable(tables.Table):
a = tables.Column()
b = tables.Column()
table = SimpleTable([])
assert 'b' == table.columns[1].name
assert 'b' == table.columns['b'].name
def test_cell_attrs_applies_to_td_and_th():
class SimpleTable(tables.Table):
a = tables.Column(attrs={"cell": {"key": "value"}})
# providing data ensures 1 row is rendered
table = SimpleTable([{"a": "value"}])
root = parse(table.as_html())
assert root.findall('.//thead/tr/th')[0].attrib == {"key": "value", "class": "a orderable sortable"}
assert root.findall('.//tbody/tr/td')[0].attrib == {"key": "value", "class": "a"}
def test_cells_are_automatically_given_column_name_as_class():
class SimpleTable(tables.Table):
a = tables.Column()
table = SimpleTable([{"a": "value"}])
root = parse(table.as_html())
assert root.findall('.//thead/tr/th')[0].attrib == {"class": "a orderable sortable"}
assert root.findall('.//tbody/tr/td')[0].attrib == {"class": "a"}
def test_th_are_given_sortable_class_if_column_is_orderable():
class SimpleTable(tables.Table):
a = tables.Column()
b = tables.Column(orderable=False)
table = SimpleTable([{"a": "value"}])
root = parse(table.as_html())
# return classes of an element as a set
classes = lambda x: set(x.attrib["class"].split())
assert "sortable" in classes(root.findall('.//thead/tr/th')[0])
assert "sortable" not in classes(root.findall('.//thead/tr/th')[1])
# Now try with an ordered table
table = SimpleTable([], order_by="a")
root = parse(table.as_html())
# return classes of an element as a set
assert "sortable" in classes(root.findall('.//thead/tr/th')[0])
assert "asc" in classes(root.findall('.//thead/tr/th')[0])
assert "sortable" not in classes(root.findall('.//thead/tr/th')[1])
def test_empty_values_triggers_default():
class Table(tables.Table):
a = tables.Column(empty_values=(1, 2), default="--")
table = Table([{"a": 1}, {"a": 2}, {"a": 3}, {"a": 4}])
assert [x["a"] for x in table.rows] == ["--", "--", 3, 4]

View File

@ -0,0 +1,95 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from django.core.urlresolvers import reverse
from django.template import Context, Template
import pytest
import django_tables2 as tables
from django_tables2.utils import build_request
from django_tables2 import A
from ..app.models import Person
from ..utils import attrs, warns
def test_unicode():
"""Test LinkColumn"""
# test unicode values + headings
class UnicodeTable(tables.Table):
first_name = tables.LinkColumn('person', args=[A('pk')])
last_name = tables.LinkColumn('person', args=[A('pk')], verbose_name='äÚ¨´ˆÁ˜¨ˆ˜˘Ú…Ò˚ˆπ∆ˆ´')
dataset = [
{'pk': 1, 'first_name': 'Brädley', 'last_name': '∆yers'},
{'pk': 2, 'first_name': 'Chr…s', 'last_name': 'DÒble'},
]
table = UnicodeTable(dataset)
request = build_request('/some-url/')
template = Template('{% load django_tables2 %}{% render_table table %}')
html = template.render(Context({'request': request, 'table': table}))
assert 'Brädley' in html
assert '∆yers' in html
assert 'Chr…s' in html
assert 'DÒble' in html
@pytest.mark.django_db
def test_null_foreign_key():
class PersonTable(tables.Table):
first_name = tables.Column()
last_name = tables.Column()
occupation = tables.LinkColumn('occupation', args=[A('occupation.pk')])
Person.objects.create(first_name='bradley', last_name='ayers')
table = PersonTable(Person.objects.all())
table.as_html()
def test_kwargs():
class PersonTable(tables.Table):
a = tables.LinkColumn('occupation', kwargs={"pk": A('a')})
html = PersonTable([{"a": 0}, {"a": 1}]).as_html()
assert reverse("occupation", kwargs={"pk": 0}) in html
assert reverse("occupation", kwargs={"pk": 1}) in html
def test_html_escape_value():
class PersonTable(tables.Table):
name = tables.LinkColumn("escaping", kwargs={"pk": A("pk")})
table = PersonTable([{"name": "<brad>", "pk": 1}])
assert table.rows[0]["name"] == '<a href="/&amp;&#39;%22/1/">&lt;brad&gt;</a>'
def test_old_style_attrs_should_still_work():
with warns(DeprecationWarning):
class TestTable(tables.Table):
col = tables.LinkColumn('occupation', kwargs={"pk": A('col')},
attrs={"title": "Occupation Title"})
table = TestTable([{"col": 0}])
assert attrs(table.rows[0]["col"]) == {"href": reverse("occupation", kwargs={"pk": 0}),
"title": "Occupation Title"}
def test_a_attrs_should_be_supported():
class TestTable(tables.Table):
col = tables.LinkColumn('occupation', kwargs={"pk": A('col')},
attrs={"a": {"title": "Occupation Title"}})
table = TestTable([{"col": 0}])
assert attrs(table.rows[0]["col"]) == {"href": reverse("occupation", kwargs={"pk": 0}),
"title": "Occupation Title"}
def test_defaults():
class Table(tables.Table):
link = tables.LinkColumn('occupation', kwargs={"pk": 1}, default="xyz")
table = Table([{}])
assert table.rows[0]['link'] == 'xyz'

View File

@ -0,0 +1,36 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from django.template import Context
import django_tables2 as tables
def test_should_handle_context_on_table():
class TestTable(tables.Table):
col_code = tables.TemplateColumn(template_code="code:{{ record.col }}{{ STATIC_URL }}")
col_name = tables.TemplateColumn(template_name="test_template_column.html")
table = TestTable([{"col": "brad"}])
assert table.rows[0]["col_code"] == "code:brad"
assert table.rows[0]["col_name"] == "name:brad"
table.context = Context({"STATIC_URL": "/static/"})
assert table.rows[0]["col_code"] == "code:brad/static/"
assert table.rows[0]["col_name"] == "name:brad/static/"
def test_should_support_default():
class Table(tables.Table):
foo = tables.TemplateColumn("default={{ default }}", default="bar")
table = Table([{}])
assert table.rows[0]["foo"] == "default=bar"
def test_should_support_value():
class Table(tables.Table):
foo = tables.TemplateColumn("value={{ value }}")
table = Table([{"foo": "bar"}])
assert table.rows[0]["foo"] == "value=bar"

View File

@ -0,0 +1,26 @@
# coding: utf-8
# pylint: disable=R0912,E0102
from __future__ import unicode_literals
from django.db import models
import django_tables2 as tables
def test_should_turn_url_into_hyperlink():
class TestTable(tables.Table):
url = tables.URLColumn()
table = TestTable([{"url": "http://example.com"}])
assert table.rows[0]["url"] == '<a href="http://example.com">http://example.com</a>'
def test_should_be_used_for_urlfields():
class URLModel(models.Model):
field = models.URLField()
class Table(tables.Table):
class Meta:
model = URLModel
assert type(Table.base_columns["field"]) == tables.URLColumn

View File

@ -1,33 +1,30 @@
# coding: utf-8
from attest import Tests
from django_tables2 import RequestConfig
from django_tables2.utils import build_request
from django.core.paginator import EmptyPage, PageNotAnInteger
from fudge import Fake
import pytest
NOTSET = object() # unique value
requestconfig = Tests()
@requestconfig.context
def faketable():
@pytest.yield_fixture
def table():
yield (Fake("Table")
.has_attr(prefixed_page_field="page",
prefixed_per_page_field="per_page",
prefixed_order_by_field="sort"))
@requestconfig.test
def no_querystring(table):
def test_no_querystring(table):
request = build_request("/")
table = table.has_attr(order_by=NOTSET).expects("paginate")
RequestConfig(request).configure(table)
assert table.order_by is NOTSET
@requestconfig.test
def full_querystring(table):
def test_full_querystring(table):
request = build_request("/?page=1&per_page=5&sort=abc")
table = (table
.expects("paginate").with_args(page=1, per_page=5)
@ -35,8 +32,7 @@ def full_querystring(table):
RequestConfig(request).configure(table)
@requestconfig.test
def partial_querystring(table):
def test_partial_querystring(table):
request = build_request("/?page=1&sort=abc")
table = (table
.expects("paginate").with_args(page=1, per_page=5)
@ -44,8 +40,7 @@ def partial_querystring(table):
RequestConfig(request, paginate={"per_page": 5}).configure(table)
@requestconfig.test
def silent_page_not_an_integer_error(table):
def test_silent_page_not_an_integer_error(table):
request = build_request("/")
paginator = (Fake("Paginator")
.expects("page").with_args(1))
@ -58,8 +53,7 @@ def silent_page_not_an_integer_error(table):
"silent": True}).configure(table)
@requestconfig.test
def silent_empty_page_error(table):
def test_silent_empty_page_error(table):
request = build_request("/")
paginator = (Fake("Paginator")
.has_attr(num_pages=987)
@ -71,6 +65,3 @@ def silent_empty_page_error(table):
RequestConfig(request, paginate={"page": 123,
"silent": True}).configure(table)
config = Tests([requestconfig])

View File

@ -1,16 +1,14 @@
# coding: utf-8
"""Test the core table functionality."""
from __future__ import absolute_import, unicode_literals
from attest import assert_hook, raises, Tests, warns
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
core = Tests()
import pytest
from .utils import warns
class UnorderedTable(tables.Table):
@ -31,8 +29,7 @@ MEMORY_DATA = [
]
@core.test
def declarations():
def test_declarations():
"""Test defining tables by declaration."""
class GeoAreaTable(tables.Table):
name = tables.Column()
@ -59,8 +56,7 @@ def declarations():
assert 'added' in CityTable.base_columns
@core.test
def metaclass_inheritance():
def test_metaclass_inheritance():
class Tweaker(type):
"""Adds an attribute "tweaked" to all classes"""
def __new__(cls, name, bases, attrs):
@ -96,8 +92,7 @@ def metaclass_inheritance():
assert table.tweaked
@core.test
def attrs():
def test_attrs():
class TestTable(tables.Table):
class Meta:
attrs = {}
@ -119,8 +114,7 @@ def attrs():
assert {"c": "d"} == TestTable4([], attrs={"c": "d"}).attrs
@core.test
def attrs_support_computed_values():
def test_attrs_support_computed_values():
counter = itertools.count()
class TestTable(tables.Table):
@ -131,15 +125,13 @@ def attrs_support_computed_values():
assert {"id": "test_table_1"} == TestTable([]).attrs
@core.test
def data_knows_its_name():
def test_data_knows_its_name():
table = tables.Table([{}])
assert table.data.verbose_name == "item"
assert table.data.verbose_name_plural == "items"
@core.test
def datasource_untouched():
def test_datasource_untouched():
"""Ensure that data that is provided to the table (the datasource) is not
modified by table operations.
"""
@ -156,21 +148,20 @@ def datasource_untouched():
assert MEMORY_DATA == original_data
@core.test
def should_support_tuple_data_source():
def test_should_support_tuple_data_source():
class SimpleTable(tables.Table):
name = tables.Column()
table = SimpleTable((
{'name': 'brad'},
{'name': 'stevie'},
{'name': 'davina'},
))
assert len(table.rows) == 2
@core.test_if(not six.PY3) # Haystack isn't compatible with Python 3
def should_support_haystack_data_source():
@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):
@ -178,33 +169,32 @@ def should_support_haystack_data_source():
table = PersonTable(SearchQuerySet().all())
table.as_html()
@core.test
def data_validation():
with raises(ValueError):
table = OrderedTable(None)
class Bad:
def __len__(self):
pass
with 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
@core.test
def ordering():
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
@ -287,8 +277,7 @@ def ordering():
assert len(captured) == 4
@core.test
def ordering_different_types():
def test_ordering_different_types():
from datetime import datetime
data = [
@ -311,25 +300,24 @@ def ordering_different_types():
assert [] == table.rows[0]['beta']
@core.test
def multi_column_ordering():
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"}
stevie = {"first_name": "Stevie", "last_name": "Armstrong"}
davina = {"first_name": "Davina", "last_name": "Adisusila"}
ross = {"first_name": "Ross", "last_name": "Ayers"}
people = [brad, brad2, chris, stevie, ross]
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, ross, stevie] == [r.record for r in table.rows]
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, ross, stevie] == [r.record for r in table.rows]
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):
@ -341,14 +329,13 @@ def multi_column_ordering():
assert brad['name'] == "Bradley Ayers"
table = PersonTable(people, order_by="name")
assert [brad, brad2, chris, ross, stevie] == [r.record for r in table.rows]
assert [brad, brad2, chris, davina, ross] == [r.record for r in table.rows]
table = PersonTable(people, order_by="-name")
assert [stevie, ross, chris, brad2, brad] == [r.record for r in table.rows]
assert [ross, davina, chris, brad2, brad] == [r.record for r in table.rows]
@core.test
def column_count():
def test_column_count():
class SimpleTable(tables.Table):
visible = tables.Column(visible=True)
hidden = tables.Column(visible=False)
@ -357,8 +344,7 @@ def column_count():
assert len(SimpleTable([]).columns) == 1
@core.test
def column_accessor():
def test_column_accessor():
class SimpleTable(UnorderedTable):
col1 = tables.Column(accessor='alpha.upper.isupper')
col2 = tables.Column(accessor='alpha.upper')
@ -368,8 +354,7 @@ def column_accessor():
assert row['col2'] == 'B'
@core.test
def exclude_columns():
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
@ -402,8 +387,7 @@ def exclude_columns():
assert [c.name for c in table.columns] == ["i", "alpha", "added"]
@core.test
def table_exclude_property_should_override_constructor_argument():
def test_table_exclude_property_should_override_constructor_argument():
class SimpleTable(tables.Table):
a = tables.Column()
b = tables.Column()
@ -414,8 +398,7 @@ def table_exclude_property_should_override_constructor_argument():
assert [c.name for c in table.columns] == ['b']
@core.test
def pagination():
def test_pagination():
class BookTable(tables.Table):
name = tables.Column()
@ -445,15 +428,14 @@ def pagination():
assert books.page.has_next() is True
# accessing a non-existant page raises 404
with raises(EmptyPage):
with pytest.raises(EmptyPage):
books.paginate(Paginator, page=9999, per_page=10)
with raises(PageNotAnInteger):
with pytest.raises(PageNotAnInteger):
books.paginate(Paginator, page='abc', per_page=10)
@core.test
def pagination_shouldnt_prevent_multiple_rendering():
def test_pagination_shouldnt_prevent_multiple_rendering():
class SimpleTable(tables.Table):
name = tables.Column()
@ -463,8 +445,7 @@ def pagination_shouldnt_prevent_multiple_rendering():
assert table.as_html() == table.as_html()
@core.test
def empty_text():
def test_empty_text():
class TestTable(tables.Table):
a = tables.Column()
@ -484,8 +465,7 @@ def empty_text():
assert table.empty_text == 'still nothing'
@core.test
def prefix():
def test_prefix():
"""Test that table prefixes affect the names of querystring parameters"""
class TableA(tables.Table):
name = tables.Column()
@ -506,8 +486,7 @@ def prefix():
assert "x" == table.prefix
@core.test
def field_names():
def test_field_names():
class TableA(tables.Table):
class Meta:
order_by_field = "abc"
@ -520,8 +499,7 @@ def field_names():
assert "ghi" == table.per_page_field
@core.test
def field_names_with_prefix():
def test_field_names_with_prefix():
class TableA(tables.Table):
class Meta:
order_by_field = "sort"
@ -552,8 +530,7 @@ def field_names_with_prefix():
assert "1-per_page" == table.prefixed_per_page_field
@core.test
def should_support_a_template_to_be_specified():
def test_should_support_a_template_to_be_specified():
class MetaDeclarationSpecifiedTemplateTable(tables.Table):
name = tables.Column()
@ -583,8 +560,7 @@ def should_support_a_template_to_be_specified():
assert table.template == "django_tables2/table.html"
@core.test
def should_support_rendering_multiple_times():
def test_should_support_rendering_multiple_times():
class MultiRenderTable(tables.Table):
name = tables.Column()
@ -593,8 +569,7 @@ def should_support_rendering_multiple_times():
assert table.as_html() == table.as_html()
@core.test
def column_defaults_are_honored():
def test_column_defaults_are_honored():
class Table(tables.Table):
name = tables.Column(default="abcd")
@ -605,8 +580,7 @@ def column_defaults_are_honored():
assert table.rows[0]['name'] == "abcd"
@core.test
def table_meta_defaults_are_honored():
def test_table_meta_defaults_are_honored():
class Table(tables.Table):
name = tables.Column()
@ -617,8 +591,7 @@ def table_meta_defaults_are_honored():
assert table.rows[0]['name'] == "abcd"
@core.test
def table_defaults_are_honored():
def test_table_defaults_are_honored():
class Table(tables.Table):
name = tables.Column()
@ -630,17 +603,16 @@ def table_defaults_are_honored():
assert table.rows[0]['name'] == "efgh"
@core.test
def list_table_data_supports_ordering():
def test_list_table_data_supports_ordering():
class Table(tables.Table):
name = tables.Column()
data = [
{"name": "Bradley"},
{"name": "Stevie"},
{"name": "Davina"},
]
table = Table(data)
assert table.rows[0]["name"] == "Bradley"
table.order_by = "-name"
assert table.rows[0]["name"] == "Stevie"
assert table.rows[0]["name"] == "Davina"

View File

@ -1,14 +1,12 @@
# coding: utf-8
from .app.models import Person, Occupation
from attest import assert_hook, Tests # pylint: disable=W0611
import itertools
from django_attest import TestContext
import django_tables2 as tables
import six
import pytest
from .app.models import Person, Occupation
import django_tables2 as tables
models = Tests()
models.context(TestContext())
pytestmark = pytest.mark.django_db
class PersonTable(tables.Table):
@ -17,8 +15,7 @@ class PersonTable(tables.Table):
occupation = tables.Column()
@models.test
def boundrows_iteration():
def test_boundrows_iteration():
occupation = Occupation.objects.create(name='Programmer')
Person.objects.create(first_name='Bradley', last_name='Ayers', occupation=occupation)
Person.objects.create(first_name='Chris', last_name='Doble', occupation=occupation)
@ -30,8 +27,7 @@ def boundrows_iteration():
assert expected == actual
@models.test
def model_table():
def test_model_table():
"""
The ``model`` option on a table causes the table to dynamically add columns
based on the fields.
@ -63,8 +59,7 @@ def model_table():
assert ["id", "char", "fk"] == list(ComplexTable.base_columns.keys())
@models.test
def mixins():
def test_mixins():
class TableMixin(tables.Table):
extra = tables.Column()
@ -76,8 +71,7 @@ def mixins():
assert ["extra", "id", "name", "region", "extra2"] == list(OccupationTable.base_columns.keys())
@models.test
def column_verbose_name():
def test_column_verbose_name():
"""
When using queryset data as input for a table, default to using model field
verbose names rather than an autogenerated string based on the column name.
@ -141,15 +135,13 @@ def column_verbose_name():
assert "translation test lazy" == table.columns["trans_test_lazy"].verbose_name
@models.test
def data_verbose_name():
def test_data_verbose_name():
table = tables.Table(Person.objects.all())
assert table.data.verbose_name == "person"
assert table.data.verbose_name_plural == "people"
@models.test
def field_choices_used_to_translated_value():
def test_field_choices_used_to_translated_value():
"""
When a model field uses the ``choices`` option, a table should render the
"pretty" value rather than the database value.
@ -181,8 +173,7 @@ def field_choices_used_to_translated_value():
assert 'Russian' == table.rows[1]['language']
@models.test
def column_mapped_to_nonexistant_field():
def test_column_mapped_to_nonexistant_field():
"""
Issue #9 describes how if a Table has a column that has an accessor that
targets a non-existent field, a FieldDoesNotExist error is raised.
@ -194,8 +185,7 @@ def column_mapped_to_nonexistant_field():
table.as_html() # the bug would cause this to raise FieldDoesNotExist
@models.test
def should_support_rendering_multiple_times():
def test_should_support_rendering_multiple_times():
class MultiRenderTable(tables.Table):
name = tables.Column()
@ -204,8 +194,7 @@ def should_support_rendering_multiple_times():
assert table.as_html() == table.as_html()
@models.test
def ordering():
def test_ordering():
class SimpleTable(tables.Table):
name = tables.Column(order_by=("first_name", "last_name"))
@ -213,8 +202,7 @@ def ordering():
assert table.as_html()
@models.test
def fields_should_implicitly_set_sequence():
def test_fields_should_implicitly_set_sequence():
class PersonTable(tables.Table):
extra = tables.Column()
@ -225,8 +213,7 @@ def fields_should_implicitly_set_sequence():
assert table.columns.names() == ['last_name', 'first_name', 'extra']
@models.test
def model_properties_should_be_useable_for_columns():
def test_model_properties_should_be_useable_for_columns():
class PersonTable(tables.Table):
class Meta:
model = Person
@ -237,8 +224,7 @@ def model_properties_should_be_useable_for_columns():
assert list(table.rows[0]) == ['Bradley Ayers', 'Bradley']
@models.test
def column_with_delete_accessor_shouldnt_delete_records():
def test_column_with_delete_accessor_shouldnt_delete_records():
class PersonTable(tables.Table):
delete = tables.Column()
@ -248,8 +234,7 @@ def column_with_delete_accessor_shouldnt_delete_records():
assert Person.objects.get(first_name='Bradley')
@models.test
def order_by_derived_from_queryset():
def test_order_by_derived_from_queryset():
queryset = Person.objects.order_by("first_name", "last_name", "-occupation__name")
class PersonTable(tables.Table):
@ -265,8 +250,7 @@ def order_by_derived_from_queryset():
assert PersonTable(queryset.all()).order_by == ("occupation", )
@models.test
def queryset_table_data_supports_ordering():
def test_queryset_table_data_supports_ordering():
class Table(tables.Table):
class Meta:
model = Person
@ -281,8 +265,7 @@ def queryset_table_data_supports_ordering():
assert table.rows[0]["first_name"] == "Stevie"
@models.test
def doesnotexist_from_accessor_should_use_default():
def test_doesnotexist_from_accessor_should_use_default():
class Table(tables.Table):
class Meta:
model = Person
@ -296,8 +279,7 @@ def doesnotexist_from_accessor_should_use_default():
assert table.rows[0]["region"] == "abc"
@models.test
def unicode_field_names():
def test_unicode_field_names():
class Table(tables.Table):
class Meta:
model = Person

View File

@ -1,20 +1,17 @@
# coding: utf-8
from attest import assert_hook, raises, Tests
import pytest
import django_tables2 as tables
rows = Tests()
@rows.test
def bound_rows():
def test_bound_rows():
class SimpleTable(tables.Table):
name = tables.Column()
data = [
{'name': 'Bradley'},
{'name': 'Chris'},
{'name': 'Peter'},
{'name': 'Davina'},
]
table = SimpleTable(data)
@ -26,8 +23,7 @@ def bound_rows():
assert records == data
@rows.test
def bound_row():
def test_bound_row():
class SimpleTable(tables.Table):
name = tables.Column()
occupation = tables.Column()
@ -43,13 +39,13 @@ def bound_row():
assert row[1] == record['occupation']
assert row[2] == record['age']
with raises(IndexError):
with pytest.raises(IndexError):
row[3]
# column name indexing into a row
assert row['name'] == record['name']
assert row['name'] == record['name']
assert row['occupation'] == record['occupation']
assert row['age'] == record['age']
assert row['age'] == record['age']
with raises(KeyError):
with pytest.raises(KeyError):
row['gamma']

View File

@ -1,17 +1,18 @@
# coding: utf-8
from __future__ import unicode_literals
from .app.models import Person, Region
from attest import assert_hook, raises, Tests # pylint: disable=W0611
from contextlib import contextmanager
from django_attest import queries, settings, TestContext, translation
import django_tables2 as tables
from django_tables2.config import RequestConfig
from django_tables2.utils import build_request
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:
@ -19,21 +20,8 @@ except ImportError:
import lxml.etree
import lxml.html
import six
def parse(html):
return lxml.etree.fromstring(html)
def attrs(xml):
"""
Helper function that returns a dict of XML attributes, given an element.
"""
return lxml.html.fromstring(xml).attrib
database = contextmanager(TestContext())
templates = Tests()
from .utils import parse, translation
import pytest
class CountryTable(tables.Table):
@ -58,8 +46,7 @@ MEMORY_DATA = [
]
@templates.test
def as_html():
def test_as_html():
table = CountryTable(MEMORY_DATA)
root = parse(table.as_html())
assert len(root.findall('.//thead/tr')) == 1
@ -89,8 +76,7 @@ def as_html():
table.as_html()
@templates.test
def custom_rendering():
def test_custom_rendering():
"""For good measure, render some actual templates."""
countries = CountryTable(MEMORY_DATA)
context = Context({'countries': countries})
@ -110,8 +96,7 @@ def custom_rendering():
assert result == template.render(context)
@templates.test
def render_table_templatetag():
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'))
@ -152,18 +137,17 @@ def render_table_templatetag():
# variable that doesn't exist (issue #8)
template = Template('{% load django_tables2 %}'
'{% render_table this_doesnt_exist %}')
with raises(ValueError):
with settings(DEBUG=True):
template.render(Context())
with pytest.raises(ValueError):
settings.DEBUG = True
template.render(Context())
# Should still be noisy with debug off
with raises(ValueError):
with settings(DEBUG=False):
template.render(Context())
with pytest.raises(ValueError):
settings.DEBUG = False
template.render(Context())
@templates.test
def render_table_should_support_template_argument():
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" %}')
@ -172,26 +156,24 @@ def render_table_should_support_template_argument():
assert template.render(context) == 'dummy template contents\n'
@templates.test
def render_table_supports_queryset():
with database():
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('/')}))
@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
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
@templates.test
def querystring_templatetag():
def test_querystring_templatetag():
template = Template('{% load django_tables2 %}'
'<b>{% querystring "name"="Brad" foo.bar=value %}</b>')
@ -212,15 +194,13 @@ def querystring_templatetag():
assert qs["c"] == ["5"]
@templates.test
def querystring_templatetag_requires_request():
with raises(ImproperlyConfigured):
def test_querystring_templatetag_requires_request():
with pytest.raises(ImproperlyConfigured):
(Template('{% load django_tables2 %}{% querystring "name"="Brad" %}')
.render(Context()))
@templates.test
def querystring_templatetag_supports_without():
def test_querystring_templatetag_supports_without():
context = Context({
"request": build_request('/?a=b&name=dog&c=5'),
"a_var": "a",
@ -240,8 +220,7 @@ def querystring_templatetag_supports_without():
assert set(qs.keys()) == set(["c"])
@templates.test
def title_should_only_apply_to_words_without_uppercase_letters():
def test_title_should_only_apply_to_words_without_uppercase_letters():
expectations = {
"a brown fox": "A Brown Fox",
"a brown foX": "A Brown foX",
@ -255,15 +234,13 @@ def title_should_only_apply_to_words_without_uppercase_letters():
assert template.render(Context({"x": raw})) == expected
@templates.test
def nospaceless_works():
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>&#32;<b>d</b> lic</i>"
@templates.test
def whitespace_is_preserved():
def test_whitespace_is_preserved():
class TestTable(tables.Table):
name = tables.Column(verbose_name=mark_safe("<b>foo</b> <i>bar</i>"))
@ -275,29 +252,35 @@ def whitespace_is_preserved():
assert "<b>foo</b> <i>bar</i>" in lxml.etree.tostring(tree.findall('.//tbody/tr/td')[0], encoding='unicode')
@templates.test
def as_html_db_queries():
with database():
@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()
class TestQueries(TransactionTestCase):
def test_as_html_db_queries(self):
class PersonTable(tables.Table):
class Meta:
model = Person
with queries(count=1):
with self.assertNumQueries(1):
PersonTable(Person.objects.all()).as_html()
@templates.test
def render_table_db_queries():
with database():
def test_render_table_db_queries(self):
Person.objects.create(first_name="brad", last_name="ayers")
Person.objects.create(first_name="stevie", last_name="armstrong")
Person.objects.create(first_name="davina", last_name="adisusila")
class PersonTable(tables.Table):
class Meta:
model = Person
per_page = 1
with queries(count=2):
with self.assertNumQueries(2):
# one query for pagination: .count()
# one query for page records: .all()[start:end]
request = build_request('/')
@ -308,8 +291,8 @@ def render_table_db_queries():
.render(Context({'table': table, 'request': request})))
@templates.test_if(django.VERSION >= (1, 3))
def localization_check():
@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
@ -333,25 +316,27 @@ def localization_check():
html = get_cond_localized_table(False)(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html
with settings(USE_L10N=True, 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
settings.USE_L10N = True
settings.USE_THOUSAND_SEPARATOR = True
# 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 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 = 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
# 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
@templates.test_if(django.VERSION >= (1, 3))
def localization_check_in_meta():
@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")
@ -391,21 +376,23 @@ def localization_check_in_meta():
html = TableNoLocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[None]) in html
with settings(USE_L10N=True, 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
settings.USE_L10N = True
settings.USE_THOUSAND_SEPARATOR = True
# localize
html = TableLocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
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
# unlocalize
html = TableUnlocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html
# localize
html = TableLocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[True]) 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
# 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

144
tests/test_utils.py Normal file
View File

@ -0,0 +1,144 @@
# coding: utf-8
# from attest import assert_hook, raises, Tests
import six
import pytest
from django_tables2.utils import (Accessor, AttributeDict, computed_values,
OrderByTuple, OrderBy, segment)
def test_orderbytuple():
obt = OrderByTuple(('a', 'b', 'c'))
assert obt == (OrderBy('a'), OrderBy('b'), OrderBy('c'))
# indexing
assert obt[0] == OrderBy('a')
assert obt['b'] == OrderBy('b')
with pytest.raises(KeyError):
obt['d']
with pytest.raises(TypeError):
obt[('tuple', )]
# .get
sentinel = object()
assert obt.get('b', sentinel) is obt['b'] # keying
assert obt.get('-', sentinel) is sentinel
assert obt.get(0, sentinel) is obt['a'] # indexing
assert obt.get(3, sentinel) is sentinel
# .opposite
assert OrderByTuple(('a', '-b', 'c')).opposite == ('-a', 'b', '-c')
# in
assert 'a' in obt and '-a' in obt
def test_orderbytuple_sort_key_multiple():
obt = OrderByTuple(('a', '-b'))
items = [
{"a": 1, "b": 2},
{"a": 1, "b": 3},
]
assert sorted(items, key=obt.key) == [
{"a": 1, "b": 3},
{"a": 1, "b": 2},
]
def test_orderbytuple_sort_key_empty_comes_first():
obt = OrderByTuple(('a'))
items = [
{"a": 1},
{"a": ""},
{"a": 2},
]
if six.PY3:
assert sorted(items, key=obt.key) == [
{"a": ""},
{"a": 1},
{"a": 2},
]
else:
assert sorted(items, key=obt.key) == [
{"a": 1},
{"a": 2},
{"a": ""},
]
def test_orderby():
a = OrderBy('a')
assert 'a' == a
assert 'a' == a.bare
assert '-a' == a.opposite
assert True == a.is_ascending
assert False == a.is_descending
b = OrderBy('-b')
assert '-b' == b
assert 'b' == b.bare
assert 'b' == b.opposite
assert True == b.is_descending
assert False == b.is_ascending
def test_accessor():
x = Accessor('0')
assert 'B' == x.resolve('Brad')
x = Accessor('1')
assert 'r' == x.resolve('Brad')
x = Accessor('2.upper')
assert 'A' == x.resolve('Brad')
x = Accessor('2.upper.__len__')
assert 1 == x.resolve('Brad')
x = Accessor('')
assert 'Brad' == x.resolve('Brad')
def test_accessor_wont_honors_alters_data():
class Foo(object):
deleted = False
def delete(self):
self.deleted = True
delete.alters_data = True
foo = Foo()
with pytest.raises(ValueError):
Accessor('delete').resolve(foo)
assert foo.deleted is False
def test_accessor_can_be_quiet():
foo = {}
assert Accessor("bar").resolve(foo, quiet=True) is None
def test_attribute_dict_handles_escaping():
x = AttributeDict({"x": '"\'x&'})
assert x.as_html() == 'x="&quot;&#39;x&amp;"'
def test_compute_values_supports_shallow_structures():
x = computed_values({"foo": lambda: "bar"})
assert x == {"foo": "bar"}
def test_compute_values_supports_shallow_structures():
x = computed_values({"foo": lambda: {"bar": lambda: "baz"}})
assert x == {"foo": {"bar": "baz"}}
def test_segment_should_return_all_candidates():
assert set(segment(("a", "-b", "c"), {
"x": "a",
"y": ("b", "-c"),
"-z": ("b", "-c"),
})) == {
("x", "-y"),
("x", "z"),
}

View File

@ -1,13 +1,10 @@
# coding: utf-8
from .app.models import Region
from attest import assert_hook, Tests
from django_attest import TestContext
import django_tables2 as tables
from django_tables2.utils import build_request
import pytest
views = Tests()
views.context(TestContext())
USING_CBV = hasattr(tables, "SingleTableView")
@ -24,8 +21,9 @@ class SimpleTable(tables.Table):
model = Region
@views.test_if(USING_CBV)
def view_should_support_pagination_options():
@pytest.mark.skipif(not USING_CBV, reason="requires class based views")
@pytest.mark.django_db
def test_view_should_support_pagination_options():
for name in ("Queensland", "New South Wales", "Victoria", "Tasmania"):
Region.objects.create(name=name)
@ -39,8 +37,8 @@ def view_should_support_pagination_options():
assert view.get_table().paginator.num_pages == 4
@views.test_if(USING_CBV)
def should_support_explicit_table_data():
@pytest.mark.skipif(not USING_CBV, reason="requires class based views")
def test_should_support_explicit_table_data():
class SimpleView(DispatchHookMixin, tables.SingleTableView):
table_class = SimpleTable
table_data = [

View File

@ -1,156 +1,50 @@
# coding: utf-8
from attest import assert_hook, raises, Tests
from django_tables2.utils import (Accessor, AttributeDict, computed_values,
OrderByTuple, OrderBy, segment)
import itertools
import six
from contextlib import contextmanager
import lxml.etree
import lxml.html
import warnings
utils = Tests()
def parse(html):
return lxml.etree.fromstring(html)
@utils.test
def orderbytuple():
obt = OrderByTuple(('a', 'b', 'c'))
assert obt == (OrderBy('a'), OrderBy('b'), OrderBy('c'))
# indexing
assert obt[0] == OrderBy('a')
assert obt['b'] == OrderBy('b')
with raises(KeyError):
obt['d']
with raises(TypeError):
obt[('tuple', )]
# .get
sentinel = object()
assert obt.get('b', sentinel) is obt['b'] # keying
assert obt.get('-', sentinel) is sentinel
assert obt.get(0, sentinel) is obt['a'] # indexing
assert obt.get(3, sentinel) is sentinel
# .opposite
assert OrderByTuple(('a', '-b', 'c')).opposite == ('-a', 'b', '-c')
# in
assert 'a' in obt and '-a' in obt
def attrs(xml):
"""
Helper function that returns a dict of XML attributes, given an element.
"""
return lxml.html.fromstring(xml).attrib
@utils.test
def orderbytuple_sort_key_multiple():
obt = OrderByTuple(('a', '-b'))
items = [
{"a": 1, "b": 2},
{"a": 1, "b": 3},
]
assert sorted(items, key=obt.key) == [
{"a": 1, "b": 3},
{"a": 1, "b": 2},
]
@contextmanager
def warns(warning_class):
with warnings.catch_warnings(record=True) as ws:
warnings.simplefilter("always")
yield ws
assert any((issubclass(w.category, DeprecationWarning) for w in ws))
@utils.test
def orderbytuple_sort_key_empty_comes_first():
obt = OrderByTuple(('a'))
items = [
{"a": 1},
{"a": ""},
{"a": 2},
]
if six.PY3:
assert sorted(items, key=obt.key) == [
{"a": ""},
{"a": 1},
{"a": 2},
]
@contextmanager
def translation(language_code, deactivate=False):
"""
Port of django.utils.translation.override from Django 1.4
@param language_code: a language code or ``None``. If ``None``, translation
is disabled and raw translation strings are used
@param deactivate: If ``True``, when leaving the manager revert to the
default behaviour (i.e. ``settings.LANGUAGE_CODE``)
rather than the translation that was active prior to
entering.
"""
from django.utils import translation
original = translation.get_language()
if language_code is not None:
translation.activate(language_code)
else:
assert sorted(items, key=obt.key) == [
{"a": 1},
{"a": 2},
{"a": ""},
]
@utils.test
def orderby():
a = OrderBy('a')
assert 'a' == a
assert 'a' == a.bare
assert '-a' == a.opposite
assert True == a.is_ascending
assert False == a.is_descending
b = OrderBy('-b')
assert '-b' == b
assert 'b' == b.bare
assert 'b' == b.opposite
assert True == b.is_descending
assert False == b.is_ascending
@utils.test
def accessor():
x = Accessor('0')
assert 'B' == x.resolve('Brad')
x = Accessor('1')
assert 'r' == x.resolve('Brad')
x = Accessor('2.upper')
assert 'A' == x.resolve('Brad')
x = Accessor('2.upper.__len__')
assert 1 == x.resolve('Brad')
x = Accessor('')
assert 'Brad' == x.resolve('Brad')
@utils.test
def accessor_wont_honors_alters_data():
class Foo(object):
deleted = False
def delete(self):
self.deleted = True
delete.alters_data = True
foo = Foo()
with raises(ValueError):
Accessor('delete').resolve(foo)
assert foo.deleted is False
@utils.test
def accessor_can_be_quiet():
foo = {}
assert Accessor("bar").resolve(foo, quiet=True) is None
@utils.test
def attribute_dict_handles_escaping():
x = AttributeDict({"x": '"\'x&'})
assert x.as_html() == 'x="&quot;&#39;x&amp;"'
@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"), {
"x": ("a"),
"y": ("b", "-c"),
"-z": ("b", "-c"),
})) == set((
("x", "-y"),
("x", "z"),
))
translation.deactivate_all()
try:
yield
finally:
if deactivate:
translation.deactivate()
else:
translation.activate(original)

149
tox.ini
View File

@ -1,176 +1,95 @@
[pytest]
DJANGO_SETTINGS_MODULE=tests.app.settings
[testenv]
commands =
python -W error -W ignore::PendingDeprecationWarning {envbindir}/coverage run setup.py test
[libs]
common =
http://github.com/bradleyayers/python-progressbar/tarball/master
http://github.com/dag/attest/tarball/master
coverage
django-attest
fudge
lxml
pylint
pytz>0
six
unittest-xml-reporting
for-dj =
django-haystack
for-dj18 =
django-haystack
for-dj17 =
django-haystack
for-dj16 =
django-haystack
for-dj15 =
django-haystack
for-dj14 =
django-haystack
for-dj13 =
django-haystack>=2.0.0,<2.1.0
for-dj12 =
django-haystack>=2.0.0,<2.1.0
[django]
latest = http://github.com/django/django/tarball/master
1.8.x = Django>=1.8,<1.9
1.7.x = Django>=1.7,<1.8
1.6.x = Django>=1.6,<1.7
1.5.x = Django>=1.5,<1.6
1.4.x = Django>=1.4,<1.5
1.3.x = Django>=1.3,<1.4
1.2.x = Django>=1.2,<1.3
setenv = PYTHONPATH={toxinidir}
commands = py.test
[testenv:docs]
changedir = docs
commands = make html
deps =
{[libs]common}
{[django]latest}
-r{toxinidir}/requirements/django-dev.pip
Sphinx
; -- python 3.4 ---------------------------------------------------------------
[testenv:py34-dj]
basepython = python3.4
commands =
python {envbindir}/coverage run setup.py test []
deps =
{[libs]common}
{[libs]for-dj}
{[django]latest}
commands = python -W error {envbindir}/coverage run setup.py test []
deps = -r{toxinidir}/requirements/django-dev.pip
[testenv:py34-dj18]
basepython = python3.4
deps =
{[libs]common}
{[libs]for-dj18}
{[django]1.8.x}
deps = -r{toxinidir}/requirements/django-1.8.x.pip
[testenv:py34-dj17]
basepython = python3.4
deps =
{[libs]common}
{[libs]for-dj17}
{[django]1.7.x}
deps = -r{toxinidir}/requirements/django-1.7.x.pip
[testenv:py34-dj16]
basepython = python3.4
deps =
{[libs]common}
{[libs]for-dj16}
{[django]1.6.x}
deps = -r{toxinidir}/requirements/django-1.6.x.pip
[testenv:py34-dj15]
basepython = python3.4
deps =
{[libs]common}
{[libs]for-dj15}
{[django]1.5.x}
deps = -r{toxinidir}/requirements/django-1.5.x.pip
; -- python 3.3 ---------------------------------------------------------------
[testenv:py33-dj]
basepython = python3.3
commands =
python {envbindir}/coverage run setup.py test []
deps =
{[libs]common}
{[libs]for-dj}
{[django]latest}
commands = python -W error {envbindir}/coverage run setup.py test []
deps = -r{toxinidir}/requirements/django-dev.pip
[testenv:py33-dj18]
basepython = python3.3
deps =
{[libs]common}
{[libs]for-dj18}
{[django]1.8.x}
deps = -r{toxinidir}/requirements/django-1.8.x.pip
[testenv:py33-dj17]
basepython = python3.3
deps =
{[libs]common}
{[libs]for-dj17}
{[django]1.7.x}
deps = -r{toxinidir}/requirements/django-1.7.x.pip
[testenv:py33-dj16]
basepython = python3.3
deps =
{[libs]common}
{[libs]for-dj16}
{[django]1.6.x}
deps = -r{toxinidir}/requirements/django-1.6.x.pip
[testenv:py33-dj15]
basepython = python3.3
deps =
{[libs]common}
{[libs]for-dj15}
{[django]1.5.x}
deps = -r{toxinidir}/requirements/django-1.5.x.pip
; -- python 2.7 ---------------------------------------------------------------
[testenv:py27-dj]
basepython = python2.7
commands =
python {envbindir}/coverage run setup.py test []
deps =
{[libs]common}
{[libs]for-dj}
{[django]latest}
commands = python -W error {envbindir}/coverage run setup.py test []
deps = -r{toxinidir}/requirements/django-dev.pip
[testenv:py27-dj18]
basepython = python2.7
deps = -r{toxinidir}/requirements/django-1.8.x.pip
[testenv:py27-dj17]
basepython = python2.7
deps = -r{toxinidir}/requirements/django-1.7.x.pip
[testenv:py27-dj16]
basepython = python2.7
deps =
{[libs]common}
{[libs]for-dj16}
{[django]1.6.x}
deps = -r{toxinidir}/requirements/django-1.6.x.pip
[testenv:py27-dj15]
basepython = python2.7
deps =
{[libs]common}
{[libs]for-dj15}
{[django]1.5.x}
deps = -r{toxinidir}/requirements/django-1.5.x.pip
[testenv:py27-dj14]
basepython = python2.7
deps =
{[libs]common}
{[libs]for-dj14}
{[django]1.4.x}
deps = -r{toxinidir}/requirements/django-1.4.x.pip
[testenv:py27-dj13]
basepython = python2.7
deps =
{[libs]common}
{[libs]for-dj13}
{[django]1.3.x}
deps = -r{toxinidir}/requirements/django-1.3.x.pip
[testenv:py27-dj12]
basepython = python2.7
commands =
python {envbindir}/coverage run setup.py test []
deps =
{[libs]common}
{[libs]for-dj12}
{[django]1.2.x}
commands = python -W error {envbindir}/coverage run setup.py test []
deps = -r{toxinidir}/requirements/django-1.2.x.pip