diff --git a/README.rst b/README.rst index 6f9857e..649a548 100644 --- a/README.rst +++ b/README.rst @@ -83,6 +83,7 @@ v1.0.0 - Added Python 3.4 support. - Added Django 1.7 and Django 1.8 support. - Dropped Python 2.6 and 3.2 support. +- Convert tests to using py.test. v0.16.0 ------- diff --git a/django_tables2/columns/base.py b/django_tables2/columns/base.py index 9d29174..e59b41d 100644 --- a/django_tables2/columns/base.py +++ b/django_tables2/columns/base.py @@ -242,10 +242,11 @@ class Column(object): # pylint: disable=R0902 # Since this method is inherited by every subclass, only provide a # column if this class was asked directly. if cls is Column: - # django 1.8 fix, but maintain compatibility - if 'django.db.models.fields.related.ManyToOneRel' in str(field.__class__): - return cls(verbose_name=field.get_related_field().verbose_name) - return cls(verbose_name=field.verbose_name) + if hasattr(field, "get_related_field"): + verbose_name = field.get_related_field().verbose_name + else: + verbose_name = field.verbose_name + return cls(verbose_name=verbose_name) class BoundColumn(object): @@ -456,8 +457,8 @@ class BoundColumn(object): # in anything useful. name = title(self.name.replace('_', ' ')) - # Try to use a tmodel field's verbose_name - if hasattr(self.table.data, 'queryset'): + # Try to use a model field's verbose_name + if hasattr(self.table.data, 'queryset') and hasattr(self.table.data.queryset, 'model'): model = self.table.data.queryset.model parts = self.accessor.split('.') field = None diff --git a/django_tables2/rows.py b/django_tables2/rows.py index d1125e2..bd2dd0f 100644 --- a/django_tables2/rows.py +++ b/django_tables2/rows.py @@ -112,7 +112,7 @@ class BoundRow(object): try: field = penultimate._meta.get_field(remainder) display = getattr(penultimate, 'get_%s_display' % remainder, None) - if field.choices and display: + if getattr(field, "choices", ()) and display: value = display() remainder = None except FieldDoesNotExist: diff --git a/requirements/common.pip b/requirements/common.pip new file mode 100644 index 0000000..ceb312a --- /dev/null +++ b/requirements/common.pip @@ -0,0 +1,8 @@ +django-haystack>=2.0.0,<2.1.0 +fudge +lxml +pylint +pytz>0 +six +pytest +pytest-django diff --git a/requirements/django-1.2.x.pip b/requirements/django-1.2.x.pip new file mode 100644 index 0000000..9817b15 --- /dev/null +++ b/requirements/django-1.2.x.pip @@ -0,0 +1,2 @@ +-r common.pip +Django>=1.2,<1.3 diff --git a/requirements/django-1.3.x.pip b/requirements/django-1.3.x.pip new file mode 100644 index 0000000..5bac7cb --- /dev/null +++ b/requirements/django-1.3.x.pip @@ -0,0 +1,2 @@ +-r common.pip +Django>=1.3,<1.4 diff --git a/requirements/django-1.4.x.pip b/requirements/django-1.4.x.pip new file mode 100644 index 0000000..9a05ed5 --- /dev/null +++ b/requirements/django-1.4.x.pip @@ -0,0 +1,2 @@ +-r common.pip +Django>=1.4,<1.5 diff --git a/requirements/django-1.5.x.pip b/requirements/django-1.5.x.pip new file mode 100644 index 0000000..658a0d6 --- /dev/null +++ b/requirements/django-1.5.x.pip @@ -0,0 +1,2 @@ +-r common.pip +Django>=1.5,<1.6 diff --git a/requirements/django-1.6.x.pip b/requirements/django-1.6.x.pip new file mode 100644 index 0000000..b1edda1 --- /dev/null +++ b/requirements/django-1.6.x.pip @@ -0,0 +1,2 @@ +-r common.pip +Django>=1.6,<1.7 diff --git a/requirements/django-1.7.x.pip b/requirements/django-1.7.x.pip new file mode 100644 index 0000000..70897e2 --- /dev/null +++ b/requirements/django-1.7.x.pip @@ -0,0 +1,2 @@ +-r common.pip +Django>=1.7,<1.8 diff --git a/requirements/django-1.8.x.pip b/requirements/django-1.8.x.pip new file mode 100644 index 0000000..af4e25c --- /dev/null +++ b/requirements/django-1.8.x.pip @@ -0,0 +1,2 @@ +-r common.pip +Django>=1.8,<1.9 diff --git a/requirements/django-dev.pip b/requirements/django-dev.pip new file mode 100644 index 0000000..66d17b5 --- /dev/null +++ b/requirements/django-dev.pip @@ -0,0 +1,2 @@ +-r common.pip +http://github.com/django/django/tarball/master diff --git a/setup.py b/setup.py index 73d8701..c109467 100755 --- a/setup.py +++ b/setup.py @@ -22,9 +22,6 @@ setup( install_requires=['Django >=1.2', 'six'], - test_loader='tests:loader', - test_suite='tests.everything', - classifiers=[ 'Environment :: Web Environment', 'Framework :: Django', diff --git a/tests/__init__.py b/tests/__init__.py index 1482687..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -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) diff --git a/tests/app/models.py b/tests/app/models.py index da26a4b..335f225 100644 --- a/tests/app/models.py +++ b/tests/app/models.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals from django.db import models from django.utils.safestring import mark_safe -from django.utils.translation import ugettext_lazy -from django.utils.translation import ugettext +from django.utils.translation import ugettext, ugettext_lazy import six diff --git a/tests/columns.py b/tests/columns.py deleted file mode 100644 index f468358..0000000 --- a/tests/columns.py +++ /dev/null @@ -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"] == '' - - -@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("Safe")) - - 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": "", "pk": 1}]) - assert table.rows[0]["name"] == '<brad>' - - -@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"] == 'http://example.com' - - -@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"] == 'test@example.com' - - -@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]) diff --git a/tests/columns/__init__.py b/tests/columns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/columns/test_booleancolumn.py b/tests/columns/test_booleancolumn.py new file mode 100644 index 0000000..f3dc9f9 --- /dev/null +++ b/tests/columns/test_booleancolumn.py @@ -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"] == '' + + +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"} diff --git a/tests/columns/test_checkboxcolumn.py b/tests/columns/test_checkboxcolumn.py new file mode 100644 index 0000000..a1d0e9c --- /dev/null +++ b/tests/columns/test_checkboxcolumn.py @@ -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"} diff --git a/tests/columns/test_datecolumn.py b/tests/columns/test_datecolumn.py new file mode 100644 index 0000000..bf3f019 --- /dev/null +++ b/tests/columns/test_datecolumn.py @@ -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 diff --git a/tests/columns/test_datetimecolumn.py b/tests/columns/test_datetimecolumn.py new file mode 100644 index 0000000..ff41e7d --- /dev/null +++ b/tests/columns/test_datetimecolumn.py @@ -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 diff --git a/tests/columns/test_emailcolumn.py b/tests/columns/test_emailcolumn.py new file mode 100644 index 0000000..426ed8e --- /dev/null +++ b/tests/columns/test_emailcolumn.py @@ -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"] == 'test@example.com' + + +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 diff --git a/tests/columns/test_filecolumn.py b/tests/columns/test_filecolumn.py new file mode 100644 index 0000000..56272f2 --- /dev/null +++ b/tests/columns/test_filecolumn.py @@ -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" diff --git a/tests/columns/test_general.py b/tests/columns/test_general.py new file mode 100644 index 0000000..b5a2443 --- /dev/null +++ b/tests/columns/test_general.py @@ -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("Safe")) + + 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] diff --git a/tests/columns/test_linkcolumn.py b/tests/columns/test_linkcolumn.py new file mode 100644 index 0000000..c3ccd1c --- /dev/null +++ b/tests/columns/test_linkcolumn.py @@ -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": "", "pk": 1}]) + assert table.rows[0]["name"] == '<brad>' + + +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' diff --git a/tests/columns/test_templatecolumn.py b/tests/columns/test_templatecolumn.py new file mode 100644 index 0000000..a346279 --- /dev/null +++ b/tests/columns/test_templatecolumn.py @@ -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" diff --git a/tests/columns/test_urlcolumn.py b/tests/columns/test_urlcolumn.py new file mode 100644 index 0000000..4ff4cb3 --- /dev/null +++ b/tests/columns/test_urlcolumn.py @@ -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"] == 'http://example.com' + + +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 diff --git a/tests/config.py b/tests/test_config.py similarity index 83% rename from tests/config.py rename to tests/test_config.py index 450c26b..3599a2a 100644 --- a/tests/config.py +++ b/tests/test_config.py @@ -1,33 +1,30 @@ # coding: utf-8 -from attest import Tests from django_tables2 import RequestConfig from django_tables2.utils import build_request from django.core.paginator import EmptyPage, PageNotAnInteger from fudge import Fake +import pytest NOTSET = object() # unique value -requestconfig = Tests() -@requestconfig.context -def faketable(): +@pytest.yield_fixture +def table(): yield (Fake("Table") .has_attr(prefixed_page_field="page", prefixed_per_page_field="per_page", prefixed_order_by_field="sort")) -@requestconfig.test -def no_querystring(table): +def test_no_querystring(table): request = build_request("/") table = table.has_attr(order_by=NOTSET).expects("paginate") RequestConfig(request).configure(table) assert table.order_by is NOTSET -@requestconfig.test -def full_querystring(table): +def test_full_querystring(table): request = build_request("/?page=1&per_page=5&sort=abc") table = (table .expects("paginate").with_args(page=1, per_page=5) @@ -35,8 +32,7 @@ def full_querystring(table): RequestConfig(request).configure(table) -@requestconfig.test -def partial_querystring(table): +def test_partial_querystring(table): request = build_request("/?page=1&sort=abc") table = (table .expects("paginate").with_args(page=1, per_page=5) @@ -44,8 +40,7 @@ def partial_querystring(table): RequestConfig(request, paginate={"per_page": 5}).configure(table) -@requestconfig.test -def silent_page_not_an_integer_error(table): +def test_silent_page_not_an_integer_error(table): request = build_request("/") paginator = (Fake("Paginator") .expects("page").with_args(1)) @@ -58,8 +53,7 @@ def silent_page_not_an_integer_error(table): "silent": True}).configure(table) -@requestconfig.test -def silent_empty_page_error(table): +def test_silent_empty_page_error(table): request = build_request("/") paginator = (Fake("Paginator") .has_attr(num_pages=987) @@ -71,6 +65,3 @@ def silent_empty_page_error(table): RequestConfig(request, paginate={"page": 123, "silent": True}).configure(table) - - -config = Tests([requestconfig]) diff --git a/tests/core.py b/tests/test_core.py similarity index 89% rename from tests/core.py rename to tests/test_core.py index 66351e2..9a33bbb 100644 --- a/tests/core.py +++ b/tests/test_core.py @@ -1,16 +1,14 @@ # coding: utf-8 """Test the core table functionality.""" from __future__ import absolute_import, unicode_literals -from attest import assert_hook, raises, Tests, warns import copy from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator import django_tables2 as tables from django_tables2.tables import DeclarativeColumnsMetaclass import six import itertools - - -core = Tests() +import pytest +from .utils import warns class UnorderedTable(tables.Table): @@ -31,8 +29,7 @@ MEMORY_DATA = [ ] -@core.test -def declarations(): +def test_declarations(): """Test defining tables by declaration.""" class GeoAreaTable(tables.Table): name = tables.Column() @@ -59,8 +56,7 @@ def declarations(): assert 'added' in CityTable.base_columns -@core.test -def metaclass_inheritance(): +def test_metaclass_inheritance(): class Tweaker(type): """Adds an attribute "tweaked" to all classes""" def __new__(cls, name, bases, attrs): @@ -96,8 +92,7 @@ def metaclass_inheritance(): assert table.tweaked -@core.test -def attrs(): +def test_attrs(): class TestTable(tables.Table): class Meta: attrs = {} @@ -119,8 +114,7 @@ def attrs(): assert {"c": "d"} == TestTable4([], attrs={"c": "d"}).attrs -@core.test -def attrs_support_computed_values(): +def test_attrs_support_computed_values(): counter = itertools.count() class TestTable(tables.Table): @@ -131,15 +125,13 @@ def attrs_support_computed_values(): assert {"id": "test_table_1"} == TestTable([]).attrs -@core.test -def data_knows_its_name(): +def test_data_knows_its_name(): table = tables.Table([{}]) assert table.data.verbose_name == "item" assert table.data.verbose_name_plural == "items" -@core.test -def datasource_untouched(): +def test_datasource_untouched(): """Ensure that data that is provided to the table (the datasource) is not modified by table operations. """ @@ -156,21 +148,20 @@ def datasource_untouched(): assert MEMORY_DATA == original_data -@core.test -def should_support_tuple_data_source(): +def test_should_support_tuple_data_source(): class SimpleTable(tables.Table): name = tables.Column() table = SimpleTable(( {'name': 'brad'}, - {'name': 'stevie'}, + {'name': 'davina'}, )) assert len(table.rows) == 2 -@core.test_if(not six.PY3) # Haystack isn't compatible with Python 3 -def should_support_haystack_data_source(): +@pytest.mark.skipif(six.PY3, reason="Haystack requires Python 2.x") +def test_should_support_haystack_data_source(): from haystack.query import SearchQuerySet class PersonTable(tables.Table): @@ -178,33 +169,32 @@ def should_support_haystack_data_source(): table = PersonTable(SearchQuerySet().all()) table.as_html() - - -@core.test -def data_validation(): - with raises(ValueError): - table = OrderedTable(None) - - class Bad: - def __len__(self): - pass - - with raises(ValueError): - table = OrderedTable(Bad()) - class Ok: - def __len__(self): - return 1 - def __getitem__(self, pos): - if pos != 0: - raise IndexError() - return {'a': 1} - - table = OrderedTable(Ok()) - assert len(table.rows) == 1 -@core.test -def ordering(): +def test_data_validation(): + with pytest.raises(ValueError): + table = OrderedTable(None) + + class Bad: + def __len__(self): + pass + + with pytest.raises(ValueError): + table = OrderedTable(Bad()) + + class Ok: + def __len__(self): + return 1 + def __getitem__(self, pos): + if pos != 0: + raise IndexError() + return {'a': 1} + + table = OrderedTable(Ok()) + assert len(table.rows) == 1 + + +def test_ordering(): # fallback to Table.Meta assert ('alpha', ) == OrderedTable([], order_by=None).order_by == OrderedTable([]).order_by @@ -287,8 +277,7 @@ def ordering(): assert len(captured) == 4 -@core.test -def ordering_different_types(): +def test_ordering_different_types(): from datetime import datetime data = [ @@ -311,25 +300,24 @@ def ordering_different_types(): assert [] == table.rows[0]['beta'] -@core.test -def multi_column_ordering(): +def test_multi_column_ordering(): brad = {"first_name": "Bradley", "last_name": "Ayers"} brad2 = {"first_name": "Bradley", "last_name": "Fake"} chris = {"first_name": "Chris", "last_name": "Doble"} - stevie = {"first_name": "Stevie", "last_name": "Armstrong"} + davina = {"first_name": "Davina", "last_name": "Adisusila"} ross = {"first_name": "Ross", "last_name": "Ayers"} - people = [brad, brad2, chris, stevie, ross] + people = [brad, brad2, chris, davina, ross] class PersonTable(tables.Table): first_name = tables.Column() last_name = tables.Column() table = PersonTable(people, order_by=("first_name", "last_name")) - assert [brad, brad2, chris, ross, stevie] == [r.record for r in table.rows] + assert [brad, brad2, chris, davina, ross] == [r.record for r in table.rows] table = PersonTable(people, order_by=("first_name", "-last_name")) - assert [brad2, brad, chris, ross, stevie] == [r.record for r in table.rows] + assert [brad2, brad, chris, davina, ross] == [r.record for r in table.rows] # let's try column order_by using multiple keys class PersonTable(tables.Table): @@ -341,14 +329,13 @@ def multi_column_ordering(): assert brad['name'] == "Bradley Ayers" table = PersonTable(people, order_by="name") - assert [brad, brad2, chris, ross, stevie] == [r.record for r in table.rows] + assert [brad, brad2, chris, davina, ross] == [r.record for r in table.rows] table = PersonTable(people, order_by="-name") - assert [stevie, ross, chris, brad2, brad] == [r.record for r in table.rows] + assert [ross, davina, chris, brad2, brad] == [r.record for r in table.rows] -@core.test -def column_count(): +def test_column_count(): class SimpleTable(tables.Table): visible = tables.Column(visible=True) hidden = tables.Column(visible=False) @@ -357,8 +344,7 @@ def column_count(): assert len(SimpleTable([]).columns) == 1 -@core.test -def column_accessor(): +def test_column_accessor(): class SimpleTable(UnorderedTable): col1 = tables.Column(accessor='alpha.upper.isupper') col2 = tables.Column(accessor='alpha.upper') @@ -368,8 +354,7 @@ def column_accessor(): assert row['col2'] == 'B' -@core.test -def exclude_columns(): +def test_exclude_columns(): """ Defining ``Table.Meta.exclude`` or providing an ``exclude`` argument when instantiating a table should have the same effect -- exclude those columns @@ -402,8 +387,7 @@ def exclude_columns(): assert [c.name for c in table.columns] == ["i", "alpha", "added"] -@core.test -def table_exclude_property_should_override_constructor_argument(): +def test_table_exclude_property_should_override_constructor_argument(): class SimpleTable(tables.Table): a = tables.Column() b = tables.Column() @@ -414,8 +398,7 @@ def table_exclude_property_should_override_constructor_argument(): assert [c.name for c in table.columns] == ['b'] -@core.test -def pagination(): +def test_pagination(): class BookTable(tables.Table): name = tables.Column() @@ -445,15 +428,14 @@ def pagination(): assert books.page.has_next() is True # accessing a non-existant page raises 404 - with raises(EmptyPage): + with pytest.raises(EmptyPage): books.paginate(Paginator, page=9999, per_page=10) - with raises(PageNotAnInteger): + with pytest.raises(PageNotAnInteger): books.paginate(Paginator, page='abc', per_page=10) -@core.test -def pagination_shouldnt_prevent_multiple_rendering(): +def test_pagination_shouldnt_prevent_multiple_rendering(): class SimpleTable(tables.Table): name = tables.Column() @@ -463,8 +445,7 @@ def pagination_shouldnt_prevent_multiple_rendering(): assert table.as_html() == table.as_html() -@core.test -def empty_text(): +def test_empty_text(): class TestTable(tables.Table): a = tables.Column() @@ -484,8 +465,7 @@ def empty_text(): assert table.empty_text == 'still nothing' -@core.test -def prefix(): +def test_prefix(): """Test that table prefixes affect the names of querystring parameters""" class TableA(tables.Table): name = tables.Column() @@ -506,8 +486,7 @@ def prefix(): assert "x" == table.prefix -@core.test -def field_names(): +def test_field_names(): class TableA(tables.Table): class Meta: order_by_field = "abc" @@ -520,8 +499,7 @@ def field_names(): assert "ghi" == table.per_page_field -@core.test -def field_names_with_prefix(): +def test_field_names_with_prefix(): class TableA(tables.Table): class Meta: order_by_field = "sort" @@ -552,8 +530,7 @@ def field_names_with_prefix(): assert "1-per_page" == table.prefixed_per_page_field -@core.test -def should_support_a_template_to_be_specified(): +def test_should_support_a_template_to_be_specified(): class MetaDeclarationSpecifiedTemplateTable(tables.Table): name = tables.Column() @@ -583,8 +560,7 @@ def should_support_a_template_to_be_specified(): assert table.template == "django_tables2/table.html" -@core.test -def should_support_rendering_multiple_times(): +def test_should_support_rendering_multiple_times(): class MultiRenderTable(tables.Table): name = tables.Column() @@ -593,8 +569,7 @@ def should_support_rendering_multiple_times(): assert table.as_html() == table.as_html() -@core.test -def column_defaults_are_honored(): +def test_column_defaults_are_honored(): class Table(tables.Table): name = tables.Column(default="abcd") @@ -605,8 +580,7 @@ def column_defaults_are_honored(): assert table.rows[0]['name'] == "abcd" -@core.test -def table_meta_defaults_are_honored(): +def test_table_meta_defaults_are_honored(): class Table(tables.Table): name = tables.Column() @@ -617,8 +591,7 @@ def table_meta_defaults_are_honored(): assert table.rows[0]['name'] == "abcd" -@core.test -def table_defaults_are_honored(): +def test_table_defaults_are_honored(): class Table(tables.Table): name = tables.Column() @@ -630,17 +603,16 @@ def table_defaults_are_honored(): assert table.rows[0]['name'] == "efgh" -@core.test -def list_table_data_supports_ordering(): +def test_list_table_data_supports_ordering(): class Table(tables.Table): name = tables.Column() data = [ {"name": "Bradley"}, - {"name": "Stevie"}, + {"name": "Davina"}, ] table = Table(data) assert table.rows[0]["name"] == "Bradley" table.order_by = "-name" - assert table.rows[0]["name"] == "Stevie" + assert table.rows[0]["name"] == "Davina" diff --git a/tests/models.py b/tests/test_models.py similarity index 90% rename from tests/models.py rename to tests/test_models.py index fcb3b7c..049e71a 100644 --- a/tests/models.py +++ b/tests/test_models.py @@ -1,14 +1,12 @@ # coding: utf-8 -from .app.models import Person, Occupation -from attest import assert_hook, Tests # pylint: disable=W0611 -import itertools -from django_attest import TestContext -import django_tables2 as tables import six +import pytest + +from .app.models import Person, Occupation +import django_tables2 as tables -models = Tests() -models.context(TestContext()) +pytestmark = pytest.mark.django_db class PersonTable(tables.Table): @@ -17,8 +15,7 @@ class PersonTable(tables.Table): occupation = tables.Column() -@models.test -def boundrows_iteration(): +def test_boundrows_iteration(): occupation = Occupation.objects.create(name='Programmer') Person.objects.create(first_name='Bradley', last_name='Ayers', occupation=occupation) Person.objects.create(first_name='Chris', last_name='Doble', occupation=occupation) @@ -30,8 +27,7 @@ def boundrows_iteration(): assert expected == actual -@models.test -def model_table(): +def test_model_table(): """ The ``model`` option on a table causes the table to dynamically add columns based on the fields. @@ -63,8 +59,7 @@ def model_table(): assert ["id", "char", "fk"] == list(ComplexTable.base_columns.keys()) -@models.test -def mixins(): +def test_mixins(): class TableMixin(tables.Table): extra = tables.Column() @@ -76,8 +71,7 @@ def mixins(): assert ["extra", "id", "name", "region", "extra2"] == list(OccupationTable.base_columns.keys()) -@models.test -def column_verbose_name(): +def test_column_verbose_name(): """ When using queryset data as input for a table, default to using model field verbose names rather than an autogenerated string based on the column name. @@ -141,15 +135,13 @@ def column_verbose_name(): assert "translation test lazy" == table.columns["trans_test_lazy"].verbose_name -@models.test -def data_verbose_name(): +def test_data_verbose_name(): table = tables.Table(Person.objects.all()) assert table.data.verbose_name == "person" assert table.data.verbose_name_plural == "people" -@models.test -def field_choices_used_to_translated_value(): +def test_field_choices_used_to_translated_value(): """ When a model field uses the ``choices`` option, a table should render the "pretty" value rather than the database value. @@ -181,8 +173,7 @@ def field_choices_used_to_translated_value(): assert 'Russian' == table.rows[1]['language'] -@models.test -def column_mapped_to_nonexistant_field(): +def test_column_mapped_to_nonexistant_field(): """ Issue #9 describes how if a Table has a column that has an accessor that targets a non-existent field, a FieldDoesNotExist error is raised. @@ -194,8 +185,7 @@ def column_mapped_to_nonexistant_field(): table.as_html() # the bug would cause this to raise FieldDoesNotExist -@models.test -def should_support_rendering_multiple_times(): +def test_should_support_rendering_multiple_times(): class MultiRenderTable(tables.Table): name = tables.Column() @@ -204,8 +194,7 @@ def should_support_rendering_multiple_times(): assert table.as_html() == table.as_html() -@models.test -def ordering(): +def test_ordering(): class SimpleTable(tables.Table): name = tables.Column(order_by=("first_name", "last_name")) @@ -213,8 +202,7 @@ def ordering(): assert table.as_html() -@models.test -def fields_should_implicitly_set_sequence(): +def test_fields_should_implicitly_set_sequence(): class PersonTable(tables.Table): extra = tables.Column() @@ -225,8 +213,7 @@ def fields_should_implicitly_set_sequence(): assert table.columns.names() == ['last_name', 'first_name', 'extra'] -@models.test -def model_properties_should_be_useable_for_columns(): +def test_model_properties_should_be_useable_for_columns(): class PersonTable(tables.Table): class Meta: model = Person @@ -237,8 +224,7 @@ def model_properties_should_be_useable_for_columns(): assert list(table.rows[0]) == ['Bradley Ayers', 'Bradley'] -@models.test -def column_with_delete_accessor_shouldnt_delete_records(): +def test_column_with_delete_accessor_shouldnt_delete_records(): class PersonTable(tables.Table): delete = tables.Column() @@ -248,8 +234,7 @@ def column_with_delete_accessor_shouldnt_delete_records(): assert Person.objects.get(first_name='Bradley') -@models.test -def order_by_derived_from_queryset(): +def test_order_by_derived_from_queryset(): queryset = Person.objects.order_by("first_name", "last_name", "-occupation__name") class PersonTable(tables.Table): @@ -265,8 +250,7 @@ def order_by_derived_from_queryset(): assert PersonTable(queryset.all()).order_by == ("occupation", ) -@models.test -def queryset_table_data_supports_ordering(): +def test_queryset_table_data_supports_ordering(): class Table(tables.Table): class Meta: model = Person @@ -281,8 +265,7 @@ def queryset_table_data_supports_ordering(): assert table.rows[0]["first_name"] == "Stevie" -@models.test -def doesnotexist_from_accessor_should_use_default(): +def test_doesnotexist_from_accessor_should_use_default(): class Table(tables.Table): class Meta: model = Person @@ -296,8 +279,7 @@ def doesnotexist_from_accessor_should_use_default(): assert table.rows[0]["region"] == "abc" -@models.test -def unicode_field_names(): +def test_unicode_field_names(): class Table(tables.Table): class Meta: model = Person diff --git a/tests/rows.py b/tests/test_rows.py similarity index 75% rename from tests/rows.py rename to tests/test_rows.py index 0e29a25..1ab0b65 100644 --- a/tests/rows.py +++ b/tests/test_rows.py @@ -1,20 +1,17 @@ # coding: utf-8 -from attest import assert_hook, raises, Tests +import pytest + import django_tables2 as tables -rows = Tests() - - -@rows.test -def bound_rows(): +def test_bound_rows(): class SimpleTable(tables.Table): name = tables.Column() data = [ {'name': 'Bradley'}, {'name': 'Chris'}, - {'name': 'Peter'}, + {'name': 'Davina'}, ] table = SimpleTable(data) @@ -26,8 +23,7 @@ def bound_rows(): assert records == data -@rows.test -def bound_row(): +def test_bound_row(): class SimpleTable(tables.Table): name = tables.Column() occupation = tables.Column() @@ -43,13 +39,13 @@ def bound_row(): assert row[1] == record['occupation'] assert row[2] == record['age'] - with raises(IndexError): + with pytest.raises(IndexError): row[3] # column name indexing into a row - assert row['name'] == record['name'] + assert row['name'] == record['name'] assert row['occupation'] == record['occupation'] - assert row['age'] == record['age'] + assert row['age'] == record['age'] - with raises(KeyError): + with pytest.raises(KeyError): row['gamma'] diff --git a/tests/templates.py b/tests/test_templates.py similarity index 72% rename from tests/templates.py rename to tests/test_templates.py index 6af0b3b..a65a1b0 100644 --- a/tests/templates.py +++ b/tests/test_templates.py @@ -1,17 +1,18 @@ # coding: utf-8 from __future__ import unicode_literals -from .app.models import Person, Region -from attest import assert_hook, raises, Tests # pylint: disable=W0611 -from contextlib import contextmanager -from django_attest import queries, settings, TestContext, translation -import django_tables2 as tables -from django_tables2.config import RequestConfig -from django_tables2.utils import build_request + +from django.test import TransactionTestCase import django from django.core.exceptions import ImproperlyConfigured from django.template import Template, RequestContext, Context from django.utils.translation import ugettext_lazy from django.utils.safestring import mark_safe + +from .app.models import Person, Region +import django_tables2 as tables +from django_tables2.config import RequestConfig +from django_tables2.utils import build_request + try: from urlparse import parse_qs except ImportError: @@ -19,21 +20,8 @@ except ImportError: import lxml.etree import lxml.html import six - - -def parse(html): - return lxml.etree.fromstring(html) - - -def attrs(xml): - """ - Helper function that returns a dict of XML attributes, given an element. - """ - return lxml.html.fromstring(xml).attrib - - -database = contextmanager(TestContext()) -templates = Tests() +from .utils import parse, translation +import pytest class CountryTable(tables.Table): @@ -58,8 +46,7 @@ MEMORY_DATA = [ ] -@templates.test -def as_html(): +def test_as_html(): table = CountryTable(MEMORY_DATA) root = parse(table.as_html()) assert len(root.findall('.//thead/tr')) == 1 @@ -89,8 +76,7 @@ def as_html(): table.as_html() -@templates.test -def custom_rendering(): +def test_custom_rendering(): """For good measure, render some actual templates.""" countries = CountryTable(MEMORY_DATA) context = Context({'countries': countries}) @@ -110,8 +96,7 @@ def custom_rendering(): assert result == template.render(context) -@templates.test -def render_table_templatetag(): +def test_render_table_templatetag(settings): # ensure it works with a multi-order-by request = build_request('/') table = CountryTable(MEMORY_DATA, order_by=('name', 'population')) @@ -152,18 +137,17 @@ def render_table_templatetag(): # variable that doesn't exist (issue #8) template = Template('{% load django_tables2 %}' '{% render_table this_doesnt_exist %}') - with raises(ValueError): - with settings(DEBUG=True): - template.render(Context()) + with pytest.raises(ValueError): + settings.DEBUG = True + template.render(Context()) # Should still be noisy with debug off - with raises(ValueError): - with settings(DEBUG=False): - template.render(Context()) + with pytest.raises(ValueError): + settings.DEBUG = False + template.render(Context()) -@templates.test -def render_table_should_support_template_argument(): +def test_render_table_should_support_template_argument(): table = CountryTable(MEMORY_DATA, order_by=('name', 'population')) template = Template('{% load django_tables2 %}' '{% render_table table "dummy.html" %}') @@ -172,26 +156,24 @@ def render_table_should_support_template_argument(): assert template.render(context) == 'dummy template contents\n' -@templates.test -def render_table_supports_queryset(): - with database(): - for name in ("Mackay", "Brisbane", "Maryborough"): - Region.objects.create(name=name) - template = Template('{% load django_tables2 %}{% render_table qs %}') - html = template.render(Context({'qs': Region.objects.all(), - 'request': build_request('/')})) +@pytest.mark.django_db +def test_render_table_supports_queryset(): + for name in ("Mackay", "Brisbane", "Maryborough"): + Region.objects.create(name=name) + template = Template('{% load django_tables2 %}{% render_table qs %}') + html = template.render(Context({'qs': Region.objects.all(), + 'request': build_request('/')})) - root = parse(html) - assert [e.text for e in root.findall('.//thead/tr/th/a')] == ["ID", "name", "mayor"] - td = [[td.text for td in tr.findall('td')] for tr in root.findall('.//tbody/tr')] - db = [] - for region in Region.objects.all(): - db.append([six.text_type(region.id), region.name, "—"]) - assert td == db + root = parse(html) + assert [e.text for e in root.findall('.//thead/tr/th/a')] == ["ID", "name", "mayor"] + td = [[td.text for td in tr.findall('td')] for tr in root.findall('.//tbody/tr')] + db = [] + for region in Region.objects.all(): + db.append([six.text_type(region.id), region.name, "—"]) + assert td == db -@templates.test -def querystring_templatetag(): +def test_querystring_templatetag(): template = Template('{% load django_tables2 %}' '{% querystring "name"="Brad" foo.bar=value %}') @@ -212,15 +194,13 @@ def querystring_templatetag(): assert qs["c"] == ["5"] -@templates.test -def querystring_templatetag_requires_request(): - with raises(ImproperlyConfigured): +def test_querystring_templatetag_requires_request(): + with pytest.raises(ImproperlyConfigured): (Template('{% load django_tables2 %}{% querystring "name"="Brad" %}') .render(Context())) -@templates.test -def querystring_templatetag_supports_without(): +def test_querystring_templatetag_supports_without(): context = Context({ "request": build_request('/?a=b&name=dog&c=5'), "a_var": "a", @@ -240,8 +220,7 @@ def querystring_templatetag_supports_without(): assert set(qs.keys()) == set(["c"]) -@templates.test -def title_should_only_apply_to_words_without_uppercase_letters(): +def test_title_should_only_apply_to_words_without_uppercase_letters(): expectations = { "a brown fox": "A Brown Fox", "a brown foX": "A Brown foX", @@ -255,15 +234,13 @@ def title_should_only_apply_to_words_without_uppercase_letters(): assert template.render(Context({"x": raw})) == expected -@templates.test -def nospaceless_works(): +def test_nospaceless_works(): template = Template("{% load django_tables2 %}" "{% spaceless %}a b {% nospaceless %}c d {% endnospaceless %}lic{% endspaceless %}") assert template.render(Context()) == "ab c d lic" -@templates.test -def whitespace_is_preserved(): +def test_whitespace_is_preserved(): class TestTable(tables.Table): name = tables.Column(verbose_name=mark_safe("foo bar")) @@ -275,29 +252,35 @@ def whitespace_is_preserved(): assert "foo bar" in lxml.etree.tostring(tree.findall('.//tbody/tr/td')[0], encoding='unicode') -@templates.test -def as_html_db_queries(): - with database(): +@pytest.mark.django_db +def test_as_html_db_queries(transactional_db): + class PersonTable(tables.Table): + class Meta: + model = Person + + # with queries(count=1): + # PersonTable(Person.objects.all()).as_html() + + +class TestQueries(TransactionTestCase): + def test_as_html_db_queries(self): class PersonTable(tables.Table): class Meta: model = Person - with queries(count=1): + with self.assertNumQueries(1): PersonTable(Person.objects.all()).as_html() - -@templates.test -def render_table_db_queries(): - with database(): + def test_render_table_db_queries(self): Person.objects.create(first_name="brad", last_name="ayers") - Person.objects.create(first_name="stevie", last_name="armstrong") + Person.objects.create(first_name="davina", last_name="adisusila") class PersonTable(tables.Table): class Meta: model = Person per_page = 1 - with queries(count=2): + with self.assertNumQueries(2): # one query for pagination: .count() # one query for page records: .all()[start:end] request = build_request('/') @@ -308,8 +291,8 @@ def render_table_db_queries(): .render(Context({'table': table, 'request': request}))) -@templates.test_if(django.VERSION >= (1, 3)) -def localization_check(): +@pytest.mark.skipif(django.VERSION < (1, 3), reason="requires Django >= 1.3") +def test_localization_check(settings): def get_cond_localized_table(localizeit=None): ''' helper function for defining Table class conditionally @@ -333,25 +316,27 @@ def localization_check(): html = get_cond_localized_table(False)(simple_test_data).as_html() assert '{0}'.format(expected_reults[False]) in html - with settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): - with translation("pl"): - # with default polish locales and enabled thousand separator - # 1234.5 is formatted as "1 234,5" with nbsp - html = get_cond_localized_table(True)(simple_test_data).as_html() - assert '{0}'.format(expected_reults[True]) in html + settings.USE_L10N = True + settings.USE_THOUSAND_SEPARATOR = True - # with localize = False there should be no formatting - html = get_cond_localized_table(False)(simple_test_data).as_html() - assert '{0}'.format(expected_reults[False]) in html + with translation("pl"): + # with default polish locales and enabled thousand separator + # 1234.5 is formatted as "1 234,5" with nbsp + html = get_cond_localized_table(True)(simple_test_data).as_html() + assert '{0}'.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 '{0}'.format(expected_reults[True]) in html + # with localize = False there should be no formatting + html = get_cond_localized_table(False)(simple_test_data).as_html() + assert '{0}'.format(expected_reults[False]) in html + + # with localize = None and USE_L10N = True + # there should be the same formatting as with localize = True + html = get_cond_localized_table(None)(simple_test_data).as_html() + assert '{0}'.format(expected_reults[True]) in html -@templates.test_if(django.VERSION >= (1, 3)) -def localization_check_in_meta(): +@pytest.mark.skipif(django.VERSION < (1, 3), reason="requires Django >= 1.3") +def test_localization_check_in_meta(settings): class TableNoLocalize(tables.Table): name = tables.Column(verbose_name="my column") @@ -391,21 +376,23 @@ def localization_check_in_meta(): html = TableNoLocalize(simple_test_data).as_html() assert '{0}'.format(expected_reults[None]) in html - with settings(USE_L10N=True, USE_THOUSAND_SEPARATOR=True): - with translation("pl"): - # the same as in localization_check. - # with localization and polish locale we get formatted output - html = TableNoLocalize(simple_test_data).as_html() - assert '{0}'.format(expected_reults[True]) in html + settings.USE_L10N = True + settings.USE_THOUSAND_SEPARATOR = True - # localize - html = TableLocalize(simple_test_data).as_html() - assert '{0}'.format(expected_reults[True]) in html + with translation("pl"): + # the same as in localization_check. + # with localization and polish locale we get formatted output + html = TableNoLocalize(simple_test_data).as_html() + assert '{0}'.format(expected_reults[True]) in html - # unlocalize - html = TableUnlocalize(simple_test_data).as_html() - assert '{0}'.format(expected_reults[False]) in html + # localize + html = TableLocalize(simple_test_data).as_html() + assert '{0}'.format(expected_reults[True]) in html - # test unlocalize higher precedence - html = TableLocalizePrecedence(simple_test_data).as_html() - assert '{0}'.format(expected_reults[False]) in html + # unlocalize + html = TableUnlocalize(simple_test_data).as_html() + assert '{0}'.format(expected_reults[False]) in html + + # test unlocalize higher precedence + html = TableLocalizePrecedence(simple_test_data).as_html() + assert '{0}'.format(expected_reults[False]) in html diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..bf7ef55 --- /dev/null +++ b/tests/test_utils.py @@ -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=""'x&"' + + +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"), + } diff --git a/tests/views.py b/tests/test_views.py similarity index 83% rename from tests/views.py rename to tests/test_views.py index 00340f1..d141547 100644 --- a/tests/views.py +++ b/tests/test_views.py @@ -1,13 +1,10 @@ # coding: utf-8 from .app.models import Region -from attest import assert_hook, Tests -from django_attest import TestContext import django_tables2 as tables from django_tables2.utils import build_request +import pytest -views = Tests() -views.context(TestContext()) USING_CBV = hasattr(tables, "SingleTableView") @@ -24,8 +21,9 @@ class SimpleTable(tables.Table): model = Region -@views.test_if(USING_CBV) -def view_should_support_pagination_options(): +@pytest.mark.skipif(not USING_CBV, reason="requires class based views") +@pytest.mark.django_db +def test_view_should_support_pagination_options(): for name in ("Queensland", "New South Wales", "Victoria", "Tasmania"): Region.objects.create(name=name) @@ -39,8 +37,8 @@ def view_should_support_pagination_options(): assert view.get_table().paginator.num_pages == 4 -@views.test_if(USING_CBV) -def should_support_explicit_table_data(): +@pytest.mark.skipif(not USING_CBV, reason="requires class based views") +def test_should_support_explicit_table_data(): class SimpleView(DispatchHookMixin, tables.SingleTableView): table_class = SimpleTable table_data = [ diff --git a/tests/utils.py b/tests/utils.py index 9e182a3..990d868 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -1,156 +1,50 @@ -# coding: utf-8 -from attest import assert_hook, raises, Tests -from django_tables2.utils import (Accessor, AttributeDict, computed_values, - OrderByTuple, OrderBy, segment) -import itertools -import six +from contextlib import contextmanager +import lxml.etree +import lxml.html +import warnings -utils = Tests() +def parse(html): + return lxml.etree.fromstring(html) -@utils.test -def orderbytuple(): - obt = OrderByTuple(('a', 'b', 'c')) - assert obt == (OrderBy('a'), OrderBy('b'), OrderBy('c')) - - # indexing - assert obt[0] == OrderBy('a') - assert obt['b'] == OrderBy('b') - with raises(KeyError): - obt['d'] - with raises(TypeError): - obt[('tuple', )] - - # .get - sentinel = object() - assert obt.get('b', sentinel) is obt['b'] # keying - assert obt.get('-', sentinel) is sentinel - assert obt.get(0, sentinel) is obt['a'] # indexing - assert obt.get(3, sentinel) is sentinel - - # .opposite - assert OrderByTuple(('a', '-b', 'c')).opposite == ('-a', 'b', '-c') - - # in - assert 'a' in obt and '-a' in obt +def attrs(xml): + """ + Helper function that returns a dict of XML attributes, given an element. + """ + return lxml.html.fromstring(xml).attrib -@utils.test -def orderbytuple_sort_key_multiple(): - obt = OrderByTuple(('a', '-b')) - items = [ - {"a": 1, "b": 2}, - {"a": 1, "b": 3}, - ] - assert sorted(items, key=obt.key) == [ - {"a": 1, "b": 3}, - {"a": 1, "b": 2}, - ] +@contextmanager +def warns(warning_class): + with warnings.catch_warnings(record=True) as ws: + warnings.simplefilter("always") + yield ws + assert any((issubclass(w.category, DeprecationWarning) for w in ws)) -@utils.test -def orderbytuple_sort_key_empty_comes_first(): - obt = OrderByTuple(('a')) - items = [ - {"a": 1}, - {"a": ""}, - {"a": 2}, - ] - if six.PY3: - assert sorted(items, key=obt.key) == [ - {"a": ""}, - {"a": 1}, - {"a": 2}, - ] +@contextmanager +def translation(language_code, deactivate=False): + """ + Port of django.utils.translation.override from Django 1.4 + + @param language_code: a language code or ``None``. If ``None``, translation + is disabled and raw translation strings are used + @param deactivate: If ``True``, when leaving the manager revert to the + default behaviour (i.e. ``settings.LANGUAGE_CODE``) + rather than the translation that was active prior to + entering. + """ + from django.utils import translation + original = translation.get_language() + if language_code is not None: + translation.activate(language_code) else: - assert sorted(items, key=obt.key) == [ - {"a": 1}, - {"a": 2}, - {"a": ""}, - ] - -@utils.test -def orderby(): - a = OrderBy('a') - assert 'a' == a - assert 'a' == a.bare - assert '-a' == a.opposite - assert True == a.is_ascending - assert False == a.is_descending - - b = OrderBy('-b') - assert '-b' == b - assert 'b' == b.bare - assert 'b' == b.opposite - assert True == b.is_descending - assert False == b.is_ascending - - -@utils.test -def accessor(): - x = Accessor('0') - assert 'B' == x.resolve('Brad') - - x = Accessor('1') - assert 'r' == x.resolve('Brad') - - x = Accessor('2.upper') - assert 'A' == x.resolve('Brad') - - x = Accessor('2.upper.__len__') - assert 1 == x.resolve('Brad') - - x = Accessor('') - assert 'Brad' == x.resolve('Brad') - - -@utils.test -def accessor_wont_honors_alters_data(): - class Foo(object): - deleted = False - - def delete(self): - self.deleted = True - delete.alters_data = True - - foo = Foo() - with raises(ValueError): - Accessor('delete').resolve(foo) - assert foo.deleted is False - - -@utils.test -def accessor_can_be_quiet(): - foo = {} - assert Accessor("bar").resolve(foo, quiet=True) is None - - -@utils.test -def attribute_dict_handles_escaping(): - x = AttributeDict({"x": '"\'x&'}) - assert x.as_html() == 'x=""'x&"' - - -@utils.test -def compute_values_supports_shallow_structures(): - x = computed_values({"foo": lambda: "bar"}) - assert x == {"foo": "bar"} - - -@utils.test -def compute_values_supports_shallow_structures(): - x = computed_values({"foo": lambda: {"bar": lambda: "baz"}}) - assert x == {"foo": {"bar": "baz"}} - - -@utils.test -def segment_should_return_all_candidates(): - assert set(segment(("a", "-b", "c"), { - "x": ("a"), - "y": ("b", "-c"), - "-z": ("b", "-c"), - })) == set(( - ("x", "-y"), - ("x", "z"), - )) + translation.deactivate_all() + try: + yield + finally: + if deactivate: + translation.deactivate() + else: + translation.activate(original) diff --git a/tox.ini b/tox.ini index 9d51f0f..b7016c6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,176 +1,95 @@ +[pytest] +DJANGO_SETTINGS_MODULE=tests.app.settings + [testenv] -commands = - python -W error -W ignore::PendingDeprecationWarning {envbindir}/coverage run setup.py test - -[libs] -common = - http://github.com/bradleyayers/python-progressbar/tarball/master - http://github.com/dag/attest/tarball/master - coverage - django-attest - fudge - lxml - pylint - pytz>0 - six - unittest-xml-reporting -for-dj = - django-haystack -for-dj18 = - django-haystack -for-dj17 = - django-haystack -for-dj16 = - django-haystack -for-dj15 = - django-haystack -for-dj14 = - django-haystack -for-dj13 = - django-haystack>=2.0.0,<2.1.0 -for-dj12 = - django-haystack>=2.0.0,<2.1.0 - -[django] -latest = http://github.com/django/django/tarball/master -1.8.x = Django>=1.8,<1.9 -1.7.x = Django>=1.7,<1.8 -1.6.x = Django>=1.6,<1.7 -1.5.x = Django>=1.5,<1.6 -1.4.x = Django>=1.4,<1.5 -1.3.x = Django>=1.3,<1.4 -1.2.x = Django>=1.2,<1.3 +setenv = PYTHONPATH={toxinidir} +commands = py.test [testenv:docs] changedir = docs commands = make html deps = - {[libs]common} - {[django]latest} + -r{toxinidir}/requirements/django-dev.pip Sphinx ; -- python 3.4 --------------------------------------------------------------- [testenv:py34-dj] basepython = python3.4 -commands = - python {envbindir}/coverage run setup.py test [] -deps = - {[libs]common} - {[libs]for-dj} - {[django]latest} +commands = python -W error {envbindir}/coverage run setup.py test [] +deps = -r{toxinidir}/requirements/django-dev.pip [testenv:py34-dj18] basepython = python3.4 -deps = - {[libs]common} - {[libs]for-dj18} - {[django]1.8.x} +deps = -r{toxinidir}/requirements/django-1.8.x.pip [testenv:py34-dj17] basepython = python3.4 -deps = - {[libs]common} - {[libs]for-dj17} - {[django]1.7.x} +deps = -r{toxinidir}/requirements/django-1.7.x.pip [testenv:py34-dj16] basepython = python3.4 -deps = - {[libs]common} - {[libs]for-dj16} - {[django]1.6.x} +deps = -r{toxinidir}/requirements/django-1.6.x.pip [testenv:py34-dj15] basepython = python3.4 -deps = - {[libs]common} - {[libs]for-dj15} - {[django]1.5.x} +deps = -r{toxinidir}/requirements/django-1.5.x.pip ; -- python 3.3 --------------------------------------------------------------- [testenv:py33-dj] basepython = python3.3 -commands = - python {envbindir}/coverage run setup.py test [] -deps = - {[libs]common} - {[libs]for-dj} - {[django]latest} +commands = python -W error {envbindir}/coverage run setup.py test [] +deps = -r{toxinidir}/requirements/django-dev.pip [testenv:py33-dj18] basepython = python3.3 -deps = - {[libs]common} - {[libs]for-dj18} - {[django]1.8.x} +deps = -r{toxinidir}/requirements/django-1.8.x.pip [testenv:py33-dj17] basepython = python3.3 -deps = - {[libs]common} - {[libs]for-dj17} - {[django]1.7.x} +deps = -r{toxinidir}/requirements/django-1.7.x.pip [testenv:py33-dj16] basepython = python3.3 -deps = - {[libs]common} - {[libs]for-dj16} - {[django]1.6.x} +deps = -r{toxinidir}/requirements/django-1.6.x.pip [testenv:py33-dj15] basepython = python3.3 -deps = - {[libs]common} - {[libs]for-dj15} - {[django]1.5.x} +deps = -r{toxinidir}/requirements/django-1.5.x.pip ; -- python 2.7 --------------------------------------------------------------- [testenv:py27-dj] basepython = python2.7 -commands = - python {envbindir}/coverage run setup.py test [] -deps = - {[libs]common} - {[libs]for-dj} - {[django]latest} +commands = python -W error {envbindir}/coverage run setup.py test [] +deps = -r{toxinidir}/requirements/django-dev.pip + +[testenv:py27-dj18] +basepython = python2.7 +deps = -r{toxinidir}/requirements/django-1.8.x.pip + +[testenv:py27-dj17] +basepython = python2.7 +deps = -r{toxinidir}/requirements/django-1.7.x.pip [testenv:py27-dj16] basepython = python2.7 -deps = - {[libs]common} - {[libs]for-dj16} - {[django]1.6.x} +deps = -r{toxinidir}/requirements/django-1.6.x.pip [testenv:py27-dj15] basepython = python2.7 -deps = - {[libs]common} - {[libs]for-dj15} - {[django]1.5.x} +deps = -r{toxinidir}/requirements/django-1.5.x.pip [testenv:py27-dj14] basepython = python2.7 -deps = - {[libs]common} - {[libs]for-dj14} - {[django]1.4.x} +deps = -r{toxinidir}/requirements/django-1.4.x.pip [testenv:py27-dj13] basepython = python2.7 -deps = - {[libs]common} - {[libs]for-dj13} - {[django]1.3.x} +deps = -r{toxinidir}/requirements/django-1.3.x.pip [testenv:py27-dj12] basepython = python2.7 -commands = - python {envbindir}/coverage run setup.py test [] -deps = - {[libs]common} - {[libs]for-dj12} - {[django]1.2.x} +commands = python -W error {envbindir}/coverage run setup.py test [] +deps = -r{toxinidir}/requirements/django-1.2.x.pip