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 Python 3.4 support.
- Added Django 1.7 and Django 1.8 support. - Added Django 1.7 and Django 1.8 support.
- Dropped Python 2.6 and 3.2 support. - Dropped Python 2.6 and 3.2 support.
- Convert tests to using py.test.
v0.16.0 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 # Since this method is inherited by every subclass, only provide a
# column if this class was asked directly. # column if this class was asked directly.
if cls is Column: if cls is Column:
# django 1.8 fix, but maintain compatibility if hasattr(field, "get_related_field"):
if 'django.db.models.fields.related.ManyToOneRel' in str(field.__class__): verbose_name = field.get_related_field().verbose_name
return cls(verbose_name=field.get_related_field().verbose_name) else:
return cls(verbose_name=field.verbose_name) verbose_name = field.verbose_name
return cls(verbose_name=verbose_name)
class BoundColumn(object): class BoundColumn(object):
@ -456,8 +457,8 @@ class BoundColumn(object):
# in anything useful. # in anything useful.
name = title(self.name.replace('_', ' ')) name = title(self.name.replace('_', ' '))
# Try to use a tmodel field's verbose_name # Try to use a model field's verbose_name
if hasattr(self.table.data, 'queryset'): if hasattr(self.table.data, 'queryset') and hasattr(self.table.data.queryset, 'model'):
model = self.table.data.queryset.model model = self.table.data.queryset.model
parts = self.accessor.split('.') parts = self.accessor.split('.')
field = None field = None

View File

@ -112,7 +112,7 @@ class BoundRow(object):
try: try:
field = penultimate._meta.get_field(remainder) field = penultimate._meta.get_field(remainder)
display = getattr(penultimate, 'get_%s_display' % remainder, None) display = getattr(penultimate, 'get_%s_display' % remainder, None)
if field.choices and display: if getattr(field, "choices", ()) and display:
value = display() value = display()
remainder = None remainder = None
except FieldDoesNotExist: 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'], install_requires=['Django >=1.2', 'six'],
test_loader='tests:loader',
test_suite='tests.everything',
classifiers=[ classifiers=[
'Environment :: Web Environment', 'Environment :: Web Environment',
'Framework :: Django', '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 __future__ import unicode_literals
from django.db import models from django.db import models
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext, ugettext_lazy
from django.utils.translation import ugettext
import six 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 # coding: utf-8
from attest import Tests
from django_tables2 import RequestConfig from django_tables2 import RequestConfig
from django_tables2.utils import build_request from django_tables2.utils import build_request
from django.core.paginator import EmptyPage, PageNotAnInteger from django.core.paginator import EmptyPage, PageNotAnInteger
from fudge import Fake from fudge import Fake
import pytest
NOTSET = object() # unique value NOTSET = object() # unique value
requestconfig = Tests()
@requestconfig.context @pytest.yield_fixture
def faketable(): def table():
yield (Fake("Table") yield (Fake("Table")
.has_attr(prefixed_page_field="page", .has_attr(prefixed_page_field="page",
prefixed_per_page_field="per_page", prefixed_per_page_field="per_page",
prefixed_order_by_field="sort")) prefixed_order_by_field="sort"))
@requestconfig.test def test_no_querystring(table):
def no_querystring(table):
request = build_request("/") request = build_request("/")
table = table.has_attr(order_by=NOTSET).expects("paginate") table = table.has_attr(order_by=NOTSET).expects("paginate")
RequestConfig(request).configure(table) RequestConfig(request).configure(table)
assert table.order_by is NOTSET assert table.order_by is NOTSET
@requestconfig.test def test_full_querystring(table):
def full_querystring(table):
request = build_request("/?page=1&per_page=5&sort=abc") request = build_request("/?page=1&per_page=5&sort=abc")
table = (table table = (table
.expects("paginate").with_args(page=1, per_page=5) .expects("paginate").with_args(page=1, per_page=5)
@ -35,8 +32,7 @@ def full_querystring(table):
RequestConfig(request).configure(table) RequestConfig(request).configure(table)
@requestconfig.test def test_partial_querystring(table):
def partial_querystring(table):
request = build_request("/?page=1&sort=abc") request = build_request("/?page=1&sort=abc")
table = (table table = (table
.expects("paginate").with_args(page=1, per_page=5) .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(request, paginate={"per_page": 5}).configure(table)
@requestconfig.test def test_silent_page_not_an_integer_error(table):
def silent_page_not_an_integer_error(table):
request = build_request("/") request = build_request("/")
paginator = (Fake("Paginator") paginator = (Fake("Paginator")
.expects("page").with_args(1)) .expects("page").with_args(1))
@ -58,8 +53,7 @@ def silent_page_not_an_integer_error(table):
"silent": True}).configure(table) "silent": True}).configure(table)
@requestconfig.test def test_silent_empty_page_error(table):
def silent_empty_page_error(table):
request = build_request("/") request = build_request("/")
paginator = (Fake("Paginator") paginator = (Fake("Paginator")
.has_attr(num_pages=987) .has_attr(num_pages=987)
@ -71,6 +65,3 @@ def silent_empty_page_error(table):
RequestConfig(request, paginate={"page": 123, RequestConfig(request, paginate={"page": 123,
"silent": True}).configure(table) "silent": True}).configure(table)
config = Tests([requestconfig])

View File

@ -1,16 +1,14 @@
# coding: utf-8 # coding: utf-8
"""Test the core table functionality.""" """Test the core table functionality."""
from __future__ import absolute_import, unicode_literals from __future__ import absolute_import, unicode_literals
from attest import assert_hook, raises, Tests, warns
import copy import copy
from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
import django_tables2 as tables import django_tables2 as tables
from django_tables2.tables import DeclarativeColumnsMetaclass from django_tables2.tables import DeclarativeColumnsMetaclass
import six import six
import itertools import itertools
import pytest
from .utils import warns
core = Tests()
class UnorderedTable(tables.Table): class UnorderedTable(tables.Table):
@ -31,8 +29,7 @@ MEMORY_DATA = [
] ]
@core.test def test_declarations():
def declarations():
"""Test defining tables by declaration.""" """Test defining tables by declaration."""
class GeoAreaTable(tables.Table): class GeoAreaTable(tables.Table):
name = tables.Column() name = tables.Column()
@ -59,8 +56,7 @@ def declarations():
assert 'added' in CityTable.base_columns assert 'added' in CityTable.base_columns
@core.test def test_metaclass_inheritance():
def metaclass_inheritance():
class Tweaker(type): class Tweaker(type):
"""Adds an attribute "tweaked" to all classes""" """Adds an attribute "tweaked" to all classes"""
def __new__(cls, name, bases, attrs): def __new__(cls, name, bases, attrs):
@ -96,8 +92,7 @@ def metaclass_inheritance():
assert table.tweaked assert table.tweaked
@core.test def test_attrs():
def attrs():
class TestTable(tables.Table): class TestTable(tables.Table):
class Meta: class Meta:
attrs = {} attrs = {}
@ -119,8 +114,7 @@ def attrs():
assert {"c": "d"} == TestTable4([], attrs={"c": "d"}).attrs assert {"c": "d"} == TestTable4([], attrs={"c": "d"}).attrs
@core.test def test_attrs_support_computed_values():
def attrs_support_computed_values():
counter = itertools.count() counter = itertools.count()
class TestTable(tables.Table): class TestTable(tables.Table):
@ -131,15 +125,13 @@ def attrs_support_computed_values():
assert {"id": "test_table_1"} == TestTable([]).attrs assert {"id": "test_table_1"} == TestTable([]).attrs
@core.test def test_data_knows_its_name():
def data_knows_its_name():
table = tables.Table([{}]) table = tables.Table([{}])
assert table.data.verbose_name == "item" assert table.data.verbose_name == "item"
assert table.data.verbose_name_plural == "items" assert table.data.verbose_name_plural == "items"
@core.test def test_datasource_untouched():
def datasource_untouched():
"""Ensure that data that is provided to the table (the datasource) is not """Ensure that data that is provided to the table (the datasource) is not
modified by table operations. modified by table operations.
""" """
@ -156,21 +148,20 @@ def datasource_untouched():
assert MEMORY_DATA == original_data assert MEMORY_DATA == original_data
@core.test def test_should_support_tuple_data_source():
def should_support_tuple_data_source():
class SimpleTable(tables.Table): class SimpleTable(tables.Table):
name = tables.Column() name = tables.Column()
table = SimpleTable(( table = SimpleTable((
{'name': 'brad'}, {'name': 'brad'},
{'name': 'stevie'}, {'name': 'davina'},
)) ))
assert len(table.rows) == 2 assert len(table.rows) == 2
@core.test_if(not six.PY3) # Haystack isn't compatible with Python 3 @pytest.mark.skipif(six.PY3, reason="Haystack requires Python 2.x")
def should_support_haystack_data_source(): def test_should_support_haystack_data_source():
from haystack.query import SearchQuerySet from haystack.query import SearchQuerySet
class PersonTable(tables.Table): class PersonTable(tables.Table):
@ -178,33 +169,32 @@ def should_support_haystack_data_source():
table = PersonTable(SearchQuerySet().all()) table = PersonTable(SearchQuerySet().all())
table.as_html() 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 test_data_validation():
def ordering(): 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 # fallback to Table.Meta
assert ('alpha', ) == OrderedTable([], order_by=None).order_by == OrderedTable([]).order_by assert ('alpha', ) == OrderedTable([], order_by=None).order_by == OrderedTable([]).order_by
@ -287,8 +277,7 @@ def ordering():
assert len(captured) == 4 assert len(captured) == 4
@core.test def test_ordering_different_types():
def ordering_different_types():
from datetime import datetime from datetime import datetime
data = [ data = [
@ -311,25 +300,24 @@ def ordering_different_types():
assert [] == table.rows[0]['beta'] assert [] == table.rows[0]['beta']
@core.test def test_multi_column_ordering():
def multi_column_ordering():
brad = {"first_name": "Bradley", "last_name": "Ayers"} brad = {"first_name": "Bradley", "last_name": "Ayers"}
brad2 = {"first_name": "Bradley", "last_name": "Fake"} brad2 = {"first_name": "Bradley", "last_name": "Fake"}
chris = {"first_name": "Chris", "last_name": "Doble"} 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"} ross = {"first_name": "Ross", "last_name": "Ayers"}
people = [brad, brad2, chris, stevie, ross] people = [brad, brad2, chris, davina, ross]
class PersonTable(tables.Table): class PersonTable(tables.Table):
first_name = tables.Column() first_name = tables.Column()
last_name = tables.Column() last_name = tables.Column()
table = PersonTable(people, order_by=("first_name", "last_name")) 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")) 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 # let's try column order_by using multiple keys
class PersonTable(tables.Table): class PersonTable(tables.Table):
@ -341,14 +329,13 @@ def multi_column_ordering():
assert brad['name'] == "Bradley Ayers" assert brad['name'] == "Bradley Ayers"
table = PersonTable(people, order_by="name") 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") 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 test_column_count():
def column_count():
class SimpleTable(tables.Table): class SimpleTable(tables.Table):
visible = tables.Column(visible=True) visible = tables.Column(visible=True)
hidden = tables.Column(visible=False) hidden = tables.Column(visible=False)
@ -357,8 +344,7 @@ def column_count():
assert len(SimpleTable([]).columns) == 1 assert len(SimpleTable([]).columns) == 1
@core.test def test_column_accessor():
def column_accessor():
class SimpleTable(UnorderedTable): class SimpleTable(UnorderedTable):
col1 = tables.Column(accessor='alpha.upper.isupper') col1 = tables.Column(accessor='alpha.upper.isupper')
col2 = tables.Column(accessor='alpha.upper') col2 = tables.Column(accessor='alpha.upper')
@ -368,8 +354,7 @@ def column_accessor():
assert row['col2'] == 'B' assert row['col2'] == 'B'
@core.test def test_exclude_columns():
def exclude_columns():
""" """
Defining ``Table.Meta.exclude`` or providing an ``exclude`` argument when Defining ``Table.Meta.exclude`` or providing an ``exclude`` argument when
instantiating a table should have the same effect -- exclude those columns 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"] assert [c.name for c in table.columns] == ["i", "alpha", "added"]
@core.test def test_table_exclude_property_should_override_constructor_argument():
def table_exclude_property_should_override_constructor_argument():
class SimpleTable(tables.Table): class SimpleTable(tables.Table):
a = tables.Column() a = tables.Column()
b = 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'] assert [c.name for c in table.columns] == ['b']
@core.test def test_pagination():
def pagination():
class BookTable(tables.Table): class BookTable(tables.Table):
name = tables.Column() name = tables.Column()
@ -445,15 +428,14 @@ def pagination():
assert books.page.has_next() is True assert books.page.has_next() is True
# accessing a non-existant page raises 404 # accessing a non-existant page raises 404
with raises(EmptyPage): with pytest.raises(EmptyPage):
books.paginate(Paginator, page=9999, per_page=10) books.paginate(Paginator, page=9999, per_page=10)
with raises(PageNotAnInteger): with pytest.raises(PageNotAnInteger):
books.paginate(Paginator, page='abc', per_page=10) books.paginate(Paginator, page='abc', per_page=10)
@core.test def test_pagination_shouldnt_prevent_multiple_rendering():
def pagination_shouldnt_prevent_multiple_rendering():
class SimpleTable(tables.Table): class SimpleTable(tables.Table):
name = tables.Column() name = tables.Column()
@ -463,8 +445,7 @@ def pagination_shouldnt_prevent_multiple_rendering():
assert table.as_html() == table.as_html() assert table.as_html() == table.as_html()
@core.test def test_empty_text():
def empty_text():
class TestTable(tables.Table): class TestTable(tables.Table):
a = tables.Column() a = tables.Column()
@ -484,8 +465,7 @@ def empty_text():
assert table.empty_text == 'still nothing' assert table.empty_text == 'still nothing'
@core.test def test_prefix():
def prefix():
"""Test that table prefixes affect the names of querystring parameters""" """Test that table prefixes affect the names of querystring parameters"""
class TableA(tables.Table): class TableA(tables.Table):
name = tables.Column() name = tables.Column()
@ -506,8 +486,7 @@ def prefix():
assert "x" == table.prefix assert "x" == table.prefix
@core.test def test_field_names():
def field_names():
class TableA(tables.Table): class TableA(tables.Table):
class Meta: class Meta:
order_by_field = "abc" order_by_field = "abc"
@ -520,8 +499,7 @@ def field_names():
assert "ghi" == table.per_page_field assert "ghi" == table.per_page_field
@core.test def test_field_names_with_prefix():
def field_names_with_prefix():
class TableA(tables.Table): class TableA(tables.Table):
class Meta: class Meta:
order_by_field = "sort" order_by_field = "sort"
@ -552,8 +530,7 @@ def field_names_with_prefix():
assert "1-per_page" == table.prefixed_per_page_field assert "1-per_page" == table.prefixed_per_page_field
@core.test def test_should_support_a_template_to_be_specified():
def should_support_a_template_to_be_specified():
class MetaDeclarationSpecifiedTemplateTable(tables.Table): class MetaDeclarationSpecifiedTemplateTable(tables.Table):
name = tables.Column() name = tables.Column()
@ -583,8 +560,7 @@ def should_support_a_template_to_be_specified():
assert table.template == "django_tables2/table.html" assert table.template == "django_tables2/table.html"
@core.test def test_should_support_rendering_multiple_times():
def should_support_rendering_multiple_times():
class MultiRenderTable(tables.Table): class MultiRenderTable(tables.Table):
name = tables.Column() name = tables.Column()
@ -593,8 +569,7 @@ def should_support_rendering_multiple_times():
assert table.as_html() == table.as_html() assert table.as_html() == table.as_html()
@core.test def test_column_defaults_are_honored():
def column_defaults_are_honored():
class Table(tables.Table): class Table(tables.Table):
name = tables.Column(default="abcd") name = tables.Column(default="abcd")
@ -605,8 +580,7 @@ def column_defaults_are_honored():
assert table.rows[0]['name'] == "abcd" assert table.rows[0]['name'] == "abcd"
@core.test def test_table_meta_defaults_are_honored():
def table_meta_defaults_are_honored():
class Table(tables.Table): class Table(tables.Table):
name = tables.Column() name = tables.Column()
@ -617,8 +591,7 @@ def table_meta_defaults_are_honored():
assert table.rows[0]['name'] == "abcd" assert table.rows[0]['name'] == "abcd"
@core.test def test_table_defaults_are_honored():
def table_defaults_are_honored():
class Table(tables.Table): class Table(tables.Table):
name = tables.Column() name = tables.Column()
@ -630,17 +603,16 @@ def table_defaults_are_honored():
assert table.rows[0]['name'] == "efgh" assert table.rows[0]['name'] == "efgh"
@core.test def test_list_table_data_supports_ordering():
def list_table_data_supports_ordering():
class Table(tables.Table): class Table(tables.Table):
name = tables.Column() name = tables.Column()
data = [ data = [
{"name": "Bradley"}, {"name": "Bradley"},
{"name": "Stevie"}, {"name": "Davina"},
] ]
table = Table(data) table = Table(data)
assert table.rows[0]["name"] == "Bradley" assert table.rows[0]["name"] == "Bradley"
table.order_by = "-name" 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 # 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 six
import pytest
from .app.models import Person, Occupation
import django_tables2 as tables
models = Tests() pytestmark = pytest.mark.django_db
models.context(TestContext())
class PersonTable(tables.Table): class PersonTable(tables.Table):
@ -17,8 +15,7 @@ class PersonTable(tables.Table):
occupation = tables.Column() occupation = tables.Column()
@models.test def test_boundrows_iteration():
def boundrows_iteration():
occupation = Occupation.objects.create(name='Programmer') occupation = Occupation.objects.create(name='Programmer')
Person.objects.create(first_name='Bradley', last_name='Ayers', occupation=occupation) Person.objects.create(first_name='Bradley', last_name='Ayers', occupation=occupation)
Person.objects.create(first_name='Chris', last_name='Doble', occupation=occupation) Person.objects.create(first_name='Chris', last_name='Doble', occupation=occupation)
@ -30,8 +27,7 @@ def boundrows_iteration():
assert expected == actual assert expected == actual
@models.test def test_model_table():
def model_table():
""" """
The ``model`` option on a table causes the table to dynamically add columns The ``model`` option on a table causes the table to dynamically add columns
based on the fields. based on the fields.
@ -63,8 +59,7 @@ def model_table():
assert ["id", "char", "fk"] == list(ComplexTable.base_columns.keys()) assert ["id", "char", "fk"] == list(ComplexTable.base_columns.keys())
@models.test def test_mixins():
def mixins():
class TableMixin(tables.Table): class TableMixin(tables.Table):
extra = tables.Column() extra = tables.Column()
@ -76,8 +71,7 @@ def mixins():
assert ["extra", "id", "name", "region", "extra2"] == list(OccupationTable.base_columns.keys()) assert ["extra", "id", "name", "region", "extra2"] == list(OccupationTable.base_columns.keys())
@models.test def test_column_verbose_name():
def column_verbose_name():
""" """
When using queryset data as input for a table, default to using model field 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. 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 assert "translation test lazy" == table.columns["trans_test_lazy"].verbose_name
@models.test def test_data_verbose_name():
def data_verbose_name():
table = tables.Table(Person.objects.all()) table = tables.Table(Person.objects.all())
assert table.data.verbose_name == "person" assert table.data.verbose_name == "person"
assert table.data.verbose_name_plural == "people" assert table.data.verbose_name_plural == "people"
@models.test def test_field_choices_used_to_translated_value():
def field_choices_used_to_translated_value():
""" """
When a model field uses the ``choices`` option, a table should render the When a model field uses the ``choices`` option, a table should render the
"pretty" value rather than the database value. "pretty" value rather than the database value.
@ -181,8 +173,7 @@ def field_choices_used_to_translated_value():
assert 'Russian' == table.rows[1]['language'] assert 'Russian' == table.rows[1]['language']
@models.test def test_column_mapped_to_nonexistant_field():
def column_mapped_to_nonexistant_field():
""" """
Issue #9 describes how if a Table has a column that has an accessor that 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. 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 table.as_html() # the bug would cause this to raise FieldDoesNotExist
@models.test def test_should_support_rendering_multiple_times():
def should_support_rendering_multiple_times():
class MultiRenderTable(tables.Table): class MultiRenderTable(tables.Table):
name = tables.Column() name = tables.Column()
@ -204,8 +194,7 @@ def should_support_rendering_multiple_times():
assert table.as_html() == table.as_html() assert table.as_html() == table.as_html()
@models.test def test_ordering():
def ordering():
class SimpleTable(tables.Table): class SimpleTable(tables.Table):
name = tables.Column(order_by=("first_name", "last_name")) name = tables.Column(order_by=("first_name", "last_name"))
@ -213,8 +202,7 @@ def ordering():
assert table.as_html() assert table.as_html()
@models.test def test_fields_should_implicitly_set_sequence():
def fields_should_implicitly_set_sequence():
class PersonTable(tables.Table): class PersonTable(tables.Table):
extra = tables.Column() extra = tables.Column()
@ -225,8 +213,7 @@ def fields_should_implicitly_set_sequence():
assert table.columns.names() == ['last_name', 'first_name', 'extra'] assert table.columns.names() == ['last_name', 'first_name', 'extra']
@models.test def test_model_properties_should_be_useable_for_columns():
def model_properties_should_be_useable_for_columns():
class PersonTable(tables.Table): class PersonTable(tables.Table):
class Meta: class Meta:
model = Person model = Person
@ -237,8 +224,7 @@ def model_properties_should_be_useable_for_columns():
assert list(table.rows[0]) == ['Bradley Ayers', 'Bradley'] assert list(table.rows[0]) == ['Bradley Ayers', 'Bradley']
@models.test def test_column_with_delete_accessor_shouldnt_delete_records():
def column_with_delete_accessor_shouldnt_delete_records():
class PersonTable(tables.Table): class PersonTable(tables.Table):
delete = tables.Column() delete = tables.Column()
@ -248,8 +234,7 @@ def column_with_delete_accessor_shouldnt_delete_records():
assert Person.objects.get(first_name='Bradley') assert Person.objects.get(first_name='Bradley')
@models.test def test_order_by_derived_from_queryset():
def order_by_derived_from_queryset():
queryset = Person.objects.order_by("first_name", "last_name", "-occupation__name") queryset = Person.objects.order_by("first_name", "last_name", "-occupation__name")
class PersonTable(tables.Table): class PersonTable(tables.Table):
@ -265,8 +250,7 @@ def order_by_derived_from_queryset():
assert PersonTable(queryset.all()).order_by == ("occupation", ) assert PersonTable(queryset.all()).order_by == ("occupation", )
@models.test def test_queryset_table_data_supports_ordering():
def queryset_table_data_supports_ordering():
class Table(tables.Table): class Table(tables.Table):
class Meta: class Meta:
model = Person model = Person
@ -281,8 +265,7 @@ def queryset_table_data_supports_ordering():
assert table.rows[0]["first_name"] == "Stevie" assert table.rows[0]["first_name"] == "Stevie"
@models.test def test_doesnotexist_from_accessor_should_use_default():
def doesnotexist_from_accessor_should_use_default():
class Table(tables.Table): class Table(tables.Table):
class Meta: class Meta:
model = Person model = Person
@ -296,8 +279,7 @@ def doesnotexist_from_accessor_should_use_default():
assert table.rows[0]["region"] == "abc" assert table.rows[0]["region"] == "abc"
@models.test def test_unicode_field_names():
def unicode_field_names():
class Table(tables.Table): class Table(tables.Table):
class Meta: class Meta:
model = Person model = Person

View File

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

View File

@ -1,17 +1,18 @@
# coding: utf-8 # coding: utf-8
from __future__ import unicode_literals from __future__ import unicode_literals
from .app.models import Person, Region
from attest import assert_hook, raises, Tests # pylint: disable=W0611 from django.test import TransactionTestCase
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
import django import django
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.template import Template, RequestContext, Context from django.template import Template, RequestContext, Context
from django.utils.translation import ugettext_lazy from django.utils.translation import ugettext_lazy
from django.utils.safestring import mark_safe 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: try:
from urlparse import parse_qs from urlparse import parse_qs
except ImportError: except ImportError:
@ -19,21 +20,8 @@ except ImportError:
import lxml.etree import lxml.etree
import lxml.html import lxml.html
import six import six
from .utils import parse, translation
import pytest
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()
class CountryTable(tables.Table): class CountryTable(tables.Table):
@ -58,8 +46,7 @@ MEMORY_DATA = [
] ]
@templates.test def test_as_html():
def as_html():
table = CountryTable(MEMORY_DATA) table = CountryTable(MEMORY_DATA)
root = parse(table.as_html()) root = parse(table.as_html())
assert len(root.findall('.//thead/tr')) == 1 assert len(root.findall('.//thead/tr')) == 1
@ -89,8 +76,7 @@ def as_html():
table.as_html() table.as_html()
@templates.test def test_custom_rendering():
def custom_rendering():
"""For good measure, render some actual templates.""" """For good measure, render some actual templates."""
countries = CountryTable(MEMORY_DATA) countries = CountryTable(MEMORY_DATA)
context = Context({'countries': countries}) context = Context({'countries': countries})
@ -110,8 +96,7 @@ def custom_rendering():
assert result == template.render(context) assert result == template.render(context)
@templates.test def test_render_table_templatetag(settings):
def render_table_templatetag():
# ensure it works with a multi-order-by # ensure it works with a multi-order-by
request = build_request('/') request = build_request('/')
table = CountryTable(MEMORY_DATA, order_by=('name', 'population')) table = CountryTable(MEMORY_DATA, order_by=('name', 'population'))
@ -152,18 +137,17 @@ def render_table_templatetag():
# variable that doesn't exist (issue #8) # variable that doesn't exist (issue #8)
template = Template('{% load django_tables2 %}' template = Template('{% load django_tables2 %}'
'{% render_table this_doesnt_exist %}') '{% render_table this_doesnt_exist %}')
with raises(ValueError): with pytest.raises(ValueError):
with settings(DEBUG=True): settings.DEBUG = True
template.render(Context()) template.render(Context())
# Should still be noisy with debug off # Should still be noisy with debug off
with raises(ValueError): with pytest.raises(ValueError):
with settings(DEBUG=False): settings.DEBUG = False
template.render(Context()) template.render(Context())
@templates.test def test_render_table_should_support_template_argument():
def render_table_should_support_template_argument():
table = CountryTable(MEMORY_DATA, order_by=('name', 'population')) table = CountryTable(MEMORY_DATA, order_by=('name', 'population'))
template = Template('{% load django_tables2 %}' template = Template('{% load django_tables2 %}'
'{% render_table table "dummy.html" %}') '{% render_table table "dummy.html" %}')
@ -172,26 +156,24 @@ def render_table_should_support_template_argument():
assert template.render(context) == 'dummy template contents\n' assert template.render(context) == 'dummy template contents\n'
@templates.test @pytest.mark.django_db
def render_table_supports_queryset(): def test_render_table_supports_queryset():
with database(): for name in ("Mackay", "Brisbane", "Maryborough"):
for name in ("Mackay", "Brisbane", "Maryborough"): Region.objects.create(name=name)
Region.objects.create(name=name) template = Template('{% load django_tables2 %}{% render_table qs %}')
template = Template('{% load django_tables2 %}{% render_table qs %}') html = template.render(Context({'qs': Region.objects.all(),
html = template.render(Context({'qs': Region.objects.all(), 'request': build_request('/')}))
'request': build_request('/')}))
root = parse(html) root = parse(html)
assert [e.text for e in root.findall('.//thead/tr/th/a')] == ["ID", "name", "mayor"] 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')] td = [[td.text for td in tr.findall('td')] for tr in root.findall('.//tbody/tr')]
db = [] db = []
for region in Region.objects.all(): for region in Region.objects.all():
db.append([six.text_type(region.id), region.name, ""]) db.append([six.text_type(region.id), region.name, ""])
assert td == db assert td == db
@templates.test def test_querystring_templatetag():
def querystring_templatetag():
template = Template('{% load django_tables2 %}' template = Template('{% load django_tables2 %}'
'<b>{% querystring "name"="Brad" foo.bar=value %}</b>') '<b>{% querystring "name"="Brad" foo.bar=value %}</b>')
@ -212,15 +194,13 @@ def querystring_templatetag():
assert qs["c"] == ["5"] assert qs["c"] == ["5"]
@templates.test def test_querystring_templatetag_requires_request():
def querystring_templatetag_requires_request(): with pytest.raises(ImproperlyConfigured):
with raises(ImproperlyConfigured):
(Template('{% load django_tables2 %}{% querystring "name"="Brad" %}') (Template('{% load django_tables2 %}{% querystring "name"="Brad" %}')
.render(Context())) .render(Context()))
@templates.test def test_querystring_templatetag_supports_without():
def querystring_templatetag_supports_without():
context = Context({ context = Context({
"request": build_request('/?a=b&name=dog&c=5'), "request": build_request('/?a=b&name=dog&c=5'),
"a_var": "a", "a_var": "a",
@ -240,8 +220,7 @@ def querystring_templatetag_supports_without():
assert set(qs.keys()) == set(["c"]) assert set(qs.keys()) == set(["c"])
@templates.test def test_title_should_only_apply_to_words_without_uppercase_letters():
def title_should_only_apply_to_words_without_uppercase_letters():
expectations = { expectations = {
"a brown fox": "A Brown Fox", "a brown fox": "A Brown Fox",
"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 assert template.render(Context({"x": raw})) == expected
@templates.test def test_nospaceless_works():
def nospaceless_works():
template = Template("{% load django_tables2 %}" template = Template("{% load django_tables2 %}"
"{% spaceless %}<b>a</b> <i>b {% nospaceless %}<b>c</b> <b>d</b> {% endnospaceless %}lic</i>{% endspaceless %}") "{% 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>" assert template.render(Context()) == "<b>a</b><i>b <b>c</b>&#32;<b>d</b> lic</i>"
@templates.test def test_whitespace_is_preserved():
def whitespace_is_preserved():
class TestTable(tables.Table): class TestTable(tables.Table):
name = tables.Column(verbose_name=mark_safe("<b>foo</b> <i>bar</i>")) 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') assert "<b>foo</b> <i>bar</i>" in lxml.etree.tostring(tree.findall('.//tbody/tr/td')[0], encoding='unicode')
@templates.test @pytest.mark.django_db
def as_html_db_queries(): def test_as_html_db_queries(transactional_db):
with database(): 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 PersonTable(tables.Table):
class Meta: class Meta:
model = Person model = Person
with queries(count=1): with self.assertNumQueries(1):
PersonTable(Person.objects.all()).as_html() PersonTable(Person.objects.all()).as_html()
def test_render_table_db_queries(self):
@templates.test
def render_table_db_queries():
with database():
Person.objects.create(first_name="brad", last_name="ayers") 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 PersonTable(tables.Table):
class Meta: class Meta:
model = Person model = Person
per_page = 1 per_page = 1
with queries(count=2): with self.assertNumQueries(2):
# one query for pagination: .count() # one query for pagination: .count()
# one query for page records: .all()[start:end] # one query for page records: .all()[start:end]
request = build_request('/') request = build_request('/')
@ -308,8 +291,8 @@ def render_table_db_queries():
.render(Context({'table': table, 'request': request}))) .render(Context({'table': table, 'request': request})))
@templates.test_if(django.VERSION >= (1, 3)) @pytest.mark.skipif(django.VERSION < (1, 3), reason="requires Django >= 1.3")
def localization_check(): def test_localization_check(settings):
def get_cond_localized_table(localizeit=None): def get_cond_localized_table(localizeit=None):
''' '''
helper function for defining Table class conditionally 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() html = get_cond_localized_table(False)(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html
with settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): settings.USE_L10N = True
with translation("pl"): settings.USE_THOUSAND_SEPARATOR = True
# with default polish locales and enabled thousand separator
# 1234.5 is formatted as "1 234,5" with nbsp
html = get_cond_localized_table(True)(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
# with localize = False there should be no formatting with translation("pl"):
html = get_cond_localized_table(False)(simple_test_data).as_html() # with default polish locales and enabled thousand separator
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html # 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 # with localize = False there should be no formatting
# there should be the same formatting as with localize = True html = get_cond_localized_table(False)(simple_test_data).as_html()
html = get_cond_localized_table(None)(simple_test_data).as_html() assert '<td class="name">{0}</td>'.format(expected_reults[False]) in 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
@templates.test_if(django.VERSION >= (1, 3)) @pytest.mark.skipif(django.VERSION < (1, 3), reason="requires Django >= 1.3")
def localization_check_in_meta(): def test_localization_check_in_meta(settings):
class TableNoLocalize(tables.Table): class TableNoLocalize(tables.Table):
name = tables.Column(verbose_name="my column") name = tables.Column(verbose_name="my column")
@ -391,21 +376,23 @@ def localization_check_in_meta():
html = TableNoLocalize(simple_test_data).as_html() html = TableNoLocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[None]) in html assert '<td class="name">{0}</td>'.format(expected_reults[None]) in html
with settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): settings.USE_L10N = True
with translation("pl"): settings.USE_THOUSAND_SEPARATOR = True
# the same as in localization_check.
# with localization and polish locale we get formatted output
html = TableNoLocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
# localize with translation("pl"):
html = TableLocalize(simple_test_data).as_html() # the same as in localization_check.
assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html # 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 # localize
html = TableUnlocalize(simple_test_data).as_html() html = TableLocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in html assert '<td class="name">{0}</td>'.format(expected_reults[True]) in html
# test unlocalize higher precedence # unlocalize
html = TableLocalizePrecedence(simple_test_data).as_html() html = TableUnlocalize(simple_test_data).as_html()
assert '<td class="name">{0}</td>'.format(expected_reults[False]) in 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 # coding: utf-8
from .app.models import Region from .app.models import Region
from attest import assert_hook, Tests
from django_attest import TestContext
import django_tables2 as tables import django_tables2 as tables
from django_tables2.utils import build_request from django_tables2.utils import build_request
import pytest
views = Tests()
views.context(TestContext())
USING_CBV = hasattr(tables, "SingleTableView") USING_CBV = hasattr(tables, "SingleTableView")
@ -24,8 +21,9 @@ class SimpleTable(tables.Table):
model = Region model = Region
@views.test_if(USING_CBV) @pytest.mark.skipif(not USING_CBV, reason="requires class based views")
def view_should_support_pagination_options(): @pytest.mark.django_db
def test_view_should_support_pagination_options():
for name in ("Queensland", "New South Wales", "Victoria", "Tasmania"): for name in ("Queensland", "New South Wales", "Victoria", "Tasmania"):
Region.objects.create(name=name) Region.objects.create(name=name)
@ -39,8 +37,8 @@ def view_should_support_pagination_options():
assert view.get_table().paginator.num_pages == 4 assert view.get_table().paginator.num_pages == 4
@views.test_if(USING_CBV) @pytest.mark.skipif(not USING_CBV, reason="requires class based views")
def should_support_explicit_table_data(): def test_should_support_explicit_table_data():
class SimpleView(DispatchHookMixin, tables.SingleTableView): class SimpleView(DispatchHookMixin, tables.SingleTableView):
table_class = SimpleTable table_class = SimpleTable
table_data = [ table_data = [

View File

@ -1,156 +1,50 @@
# coding: utf-8 from contextlib import contextmanager
from attest import assert_hook, raises, Tests import lxml.etree
from django_tables2.utils import (Accessor, AttributeDict, computed_values, import lxml.html
OrderByTuple, OrderBy, segment) import warnings
import itertools
import six
utils = Tests() def parse(html):
return lxml.etree.fromstring(html)
@utils.test def attrs(xml):
def orderbytuple(): """
obt = OrderByTuple(('a', 'b', 'c')) Helper function that returns a dict of XML attributes, given an element.
assert obt == (OrderBy('a'), OrderBy('b'), OrderBy('c')) """
return lxml.html.fromstring(xml).attrib
# 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
@utils.test @contextmanager
def orderbytuple_sort_key_multiple(): def warns(warning_class):
obt = OrderByTuple(('a', '-b')) with warnings.catch_warnings(record=True) as ws:
items = [ warnings.simplefilter("always")
{"a": 1, "b": 2}, yield ws
{"a": 1, "b": 3}, assert any((issubclass(w.category, DeprecationWarning) for w in ws))
]
assert sorted(items, key=obt.key) == [
{"a": 1, "b": 3},
{"a": 1, "b": 2},
]
@utils.test @contextmanager
def orderbytuple_sort_key_empty_comes_first(): def translation(language_code, deactivate=False):
obt = OrderByTuple(('a')) """
items = [ Port of django.utils.translation.override from Django 1.4
{"a": 1},
{"a": ""}, @param language_code: a language code or ``None``. If ``None``, translation
{"a": 2}, is disabled and raw translation strings are used
] @param deactivate: If ``True``, when leaving the manager revert to the
if six.PY3: default behaviour (i.e. ``settings.LANGUAGE_CODE``)
assert sorted(items, key=obt.key) == [ rather than the translation that was active prior to
{"a": ""}, entering.
{"a": 1}, """
{"a": 2}, from django.utils import translation
] original = translation.get_language()
if language_code is not None:
translation.activate(language_code)
else: else:
assert sorted(items, key=obt.key) == [ translation.deactivate_all()
{"a": 1}, try:
{"a": 2}, yield
{"a": ""}, finally:
] if deactivate:
translation.deactivate()
@utils.test else:
def orderby(): translation.activate(original)
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"),
))

149
tox.ini
View File

@ -1,176 +1,95 @@
[pytest]
DJANGO_SETTINGS_MODULE=tests.app.settings
[testenv] [testenv]
commands = setenv = PYTHONPATH={toxinidir}
python -W error -W ignore::PendingDeprecationWarning {envbindir}/coverage run setup.py test commands = 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
[testenv:docs] [testenv:docs]
changedir = docs changedir = docs
commands = make html commands = make html
deps = deps =
{[libs]common} -r{toxinidir}/requirements/django-dev.pip
{[django]latest}
Sphinx Sphinx
; -- python 3.4 --------------------------------------------------------------- ; -- python 3.4 ---------------------------------------------------------------
[testenv:py34-dj] [testenv:py34-dj]
basepython = python3.4 basepython = python3.4
commands = commands = python -W error {envbindir}/coverage run setup.py test []
python {envbindir}/coverage run setup.py test [] deps = -r{toxinidir}/requirements/django-dev.pip
deps =
{[libs]common}
{[libs]for-dj}
{[django]latest}
[testenv:py34-dj18] [testenv:py34-dj18]
basepython = python3.4 basepython = python3.4
deps = deps = -r{toxinidir}/requirements/django-1.8.x.pip
{[libs]common}
{[libs]for-dj18}
{[django]1.8.x}
[testenv:py34-dj17] [testenv:py34-dj17]
basepython = python3.4 basepython = python3.4
deps = deps = -r{toxinidir}/requirements/django-1.7.x.pip
{[libs]common}
{[libs]for-dj17}
{[django]1.7.x}
[testenv:py34-dj16] [testenv:py34-dj16]
basepython = python3.4 basepython = python3.4
deps = deps = -r{toxinidir}/requirements/django-1.6.x.pip
{[libs]common}
{[libs]for-dj16}
{[django]1.6.x}
[testenv:py34-dj15] [testenv:py34-dj15]
basepython = python3.4 basepython = python3.4
deps = deps = -r{toxinidir}/requirements/django-1.5.x.pip
{[libs]common}
{[libs]for-dj15}
{[django]1.5.x}
; -- python 3.3 --------------------------------------------------------------- ; -- python 3.3 ---------------------------------------------------------------
[testenv:py33-dj] [testenv:py33-dj]
basepython = python3.3 basepython = python3.3
commands = commands = python -W error {envbindir}/coverage run setup.py test []
python {envbindir}/coverage run setup.py test [] deps = -r{toxinidir}/requirements/django-dev.pip
deps =
{[libs]common}
{[libs]for-dj}
{[django]latest}
[testenv:py33-dj18] [testenv:py33-dj18]
basepython = python3.3 basepython = python3.3
deps = deps = -r{toxinidir}/requirements/django-1.8.x.pip
{[libs]common}
{[libs]for-dj18}
{[django]1.8.x}
[testenv:py33-dj17] [testenv:py33-dj17]
basepython = python3.3 basepython = python3.3
deps = deps = -r{toxinidir}/requirements/django-1.7.x.pip
{[libs]common}
{[libs]for-dj17}
{[django]1.7.x}
[testenv:py33-dj16] [testenv:py33-dj16]
basepython = python3.3 basepython = python3.3
deps = deps = -r{toxinidir}/requirements/django-1.6.x.pip
{[libs]common}
{[libs]for-dj16}
{[django]1.6.x}
[testenv:py33-dj15] [testenv:py33-dj15]
basepython = python3.3 basepython = python3.3
deps = deps = -r{toxinidir}/requirements/django-1.5.x.pip
{[libs]common}
{[libs]for-dj15}
{[django]1.5.x}
; -- python 2.7 --------------------------------------------------------------- ; -- python 2.7 ---------------------------------------------------------------
[testenv:py27-dj] [testenv:py27-dj]
basepython = python2.7 basepython = python2.7
commands = commands = python -W error {envbindir}/coverage run setup.py test []
python {envbindir}/coverage run setup.py test [] deps = -r{toxinidir}/requirements/django-dev.pip
deps =
{[libs]common} [testenv:py27-dj18]
{[libs]for-dj} basepython = python2.7
{[django]latest} 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] [testenv:py27-dj16]
basepython = python2.7 basepython = python2.7
deps = deps = -r{toxinidir}/requirements/django-1.6.x.pip
{[libs]common}
{[libs]for-dj16}
{[django]1.6.x}
[testenv:py27-dj15] [testenv:py27-dj15]
basepython = python2.7 basepython = python2.7
deps = deps = -r{toxinidir}/requirements/django-1.5.x.pip
{[libs]common}
{[libs]for-dj15}
{[django]1.5.x}
[testenv:py27-dj14] [testenv:py27-dj14]
basepython = python2.7 basepython = python2.7
deps = deps = -r{toxinidir}/requirements/django-1.4.x.pip
{[libs]common}
{[libs]for-dj14}
{[django]1.4.x}
[testenv:py27-dj13] [testenv:py27-dj13]
basepython = python2.7 basepython = python2.7
deps = deps = -r{toxinidir}/requirements/django-1.3.x.pip
{[libs]common}
{[libs]for-dj13}
{[django]1.3.x}
[testenv:py27-dj12] [testenv:py27-dj12]
basepython = python2.7 basepython = python2.7
commands = commands = python -W error {envbindir}/coverage run setup.py test []
python {envbindir}/coverage run setup.py test [] deps = -r{toxinidir}/requirements/django-1.2.x.pip
deps =
{[libs]common}
{[libs]for-dj12}
{[django]1.2.x}