* Moved tests out of the app, and integrated them with setup.py
authorBradley Ayers <bradley.ayers@enigmainteractive.com>
Thu, 24 Mar 2011 03:07:49 +0000 (13:07 +1000)
committerBradley Ayers <bradley.ayers@enigmainteractive.com>
Thu, 24 Mar 2011 03:07:49 +0000 (13:07 +1000)
* Added more tests for QuerySet data sources
* Clarified documentation
* Renamed BoundRow.data to BoundRow.record

14 files changed:
.gitignore
django_tables/rows.py
django_tables/tables.py
django_tables/templatetags/django_tables.py
django_tables/tests/__init__.py [deleted file]
django_tables/tests/memory.py [deleted file]
django_tables/tests/models.py [deleted file]
docs/conf.py
docs/index.rst
setup.py
tests/__init__.py [new file with mode: 0644]
tests/core.py [moved from django_tables/tests/core.py with 99% similarity]
tests/models.py [new file with mode: 0644]
tests/templates.py [moved from django_tables/tests/templates.py with 100% similarity]

index 5ce80034bfff63a29fa6f8af9e7abbd8211fe3b1..6eabac012a9c3e2a01aa739b0a606d17e00dc21e 100644 (file)
@@ -1,6 +1,7 @@
 *.pyc
 /*.komodoproject
 /*.egg-info/
+/*.egg
 /MANIFEST
 /dist/
 /build/
index 9544bd31f73e0675195305244dfb61fea54ceacc..a6f9ed9479f6fb8ef83eecd5795fc2613b28fe01 100644 (file)
@@ -54,17 +54,30 @@ class BoundRow(object):
         KeyError: 'c'
 
     """
-    def __init__(self, table, data):
+    def __init__(self, table, record):
         """Initialise a new :class:`BoundRow` object where:
 
         * *table* is the :class:`Table` in which this row exists.
-        * *data* is a chunk of data that describes the information for this
-          row. A "chunk" of data might be a :class:`Model` object, a ``dict``,
-          or perhaps something else.
+        * *record* is a single record from the data source that is posed to
+          populate the row. A record could be a :class:`Model` object, a
+          ``dict``, or something else.
 
         """
-        self.table = table
-        self.data = data
+        self._table = table
+        self._record = record
+
+    @property
+    def table(self):
+        """The associated :term:`table`."""
+        return self._table
+
+    @property
+    def record(self):
+        """The data record from the data source which is used to populate this
+        row with data.
+
+        """
+        return self._record
 
     def __iter__(self):
         """Iterate over the rendered values for cells in the row.
@@ -73,8 +86,10 @@ class BoundRow(object):
         each cell.
 
         """
-        for value in self.values:
-            yield value
+        for column in self.table.columns:
+            # this uses __getitem__, using the name (rather than the accessor)
+            # is correct – it's what __getitem__ expects.
+            yield self[column.name]
 
     def __getitem__(self, name):
         """Returns the final rendered value for a cell in the row, given the
@@ -96,13 +111,6 @@ class BoundRow(object):
         else:
             return item in self
 
-    @property
-    def values(self):
-        for column in self.table.columns:
-            # this uses __getitem__, using the name (rather than the accessor)
-            # is correct – it's what __getitem__ expects.
-            yield self[column.name]
-
 
 class Rows(object):
     """Container for spawning BoundRows.
index 09062b85cd536eab4e82ebd10ca51049c4ab3bf3..2897e50132fc4f27614e2a59d0bed4e04d0c92f3 100644 (file)
@@ -112,7 +112,7 @@ class TableData(object):
             Apply column formatter after retrieving the value from the data.
 
         """
-        value = Accessor(bound_column.accessor).resolve(bound_row.data)
+        value = Accessor(bound_column.accessor).resolve(bound_row.record)
         # try and use default value if we've only got 'None'
         if value is None and bound_column.default is not None:
             value = bound_column.default()
index db15886367dce31f62d7c9591805b8f4729573eb..b914f4e4045592bb25aebf7de422cec50a723e40 100644 (file)
@@ -1,6 +1,6 @@
 """
-Allows setting/changing/removing of chosen url query string parameters,
-while maintaining any existing others.
+Allows setting/changing/removing of chosen url query string parameters, while
+maintaining any existing others.
 
 Expects the current request to be available in the context as ``request``.
 
@@ -9,8 +9,8 @@ Examples:
     {% set_url_param page=next_page %}
     {% set_url_param page="" %}
     {% set_url_param filter="books" page=1 %}
-"""
 
+"""
 import urllib
 import tokenize
 import StringIO
@@ -18,6 +18,7 @@ from django import template
 from django.template.loader import get_template
 from django.utils.safestring import mark_safe
 
+
 register = template.Library()
 
 
@@ -59,7 +60,8 @@ class SetUrlParamNode(template.Node):
         return '?' + urllib.urlencode(params, doseq=True)
 
 
-def do_set_url_param(parser, token):
+@register.tag
+def set_url_param(parser, token):
     bits = token.contents.split()
     qschanges = {}
     for i in bits[1:]:
@@ -81,8 +83,6 @@ def do_set_url_param(parser, token):
                    "Argument syntax wrong: should be key=value")
     return SetUrlParamNode(qschanges)
 
-register.tag('set_url_param', do_set_url_param)
-
 
 class RenderTableNode(template.Node):
     def __init__(self, table_var_name):
@@ -96,12 +96,11 @@ class RenderTableNode(template.Node):
         return get_template('django_tables/table.html').render(context)
 
 
-def do_render_table(parser, token):
+@register.tag
+def render_table(parser, token):
     try:
         _, table_var_name = token.contents.split()
     except ValueError:
         raise template.TemplateSyntaxError,\
           "%r tag requires a single argument" % token.contents.split()[0]
     return RenderTableNode(table_var_name)
-
-register.tag('render_table', do_render_table)
diff --git a/django_tables/tests/__init__.py b/django_tables/tests/__init__.py
deleted file mode 100644 (file)
index 5572945..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-from attest import Tests
-from .core import core
-from .templates import templates
-from .models import models
-#from .memory import memory
-
-
-tests = Tests([core, templates, models])
-
-def suite():
-    return tests.test_suite()
-
-if __name__ == '__main__':
-    tests.main()
diff --git a/django_tables/tests/memory.py b/django_tables/tests/memory.py
deleted file mode 100644 (file)
index 20d52ac..0000000
+++ /dev/null
@@ -1,349 +0,0 @@
-"""Test the memory table functionality.
-
-TODO: A bunch of those tests probably fit better into test_basic, since
-they aren't really MemoryTable specific.
-"""
-
-from math import sqrt
-from attest import Tests
-from django.core.paginator import Paginator
-import django_tables as tables
-
-memory = Tests()
-
-@memory.test
-def basics():
-    class StuffTable(tables.Table):
-        name = tables.Column()
-        answer = tables.Column(default=42)
-        c = tables.Column(name="count", default=1)
-        email = tables.Column(data="@")
-
-    stuff = StuffTable([
-        {'id': 1, 'name': 'Foo Bar', '@': 'foo@bar.org'},
-    ])
-
-    # access without order_by works
-    stuff.data
-    stuff.rows
-
-    # make sure BoundColumnn.name always gives us the right thing, whether
-    # the column explicitely defines a name or not.
-    stuff.columns['count'].name == 'count'
-    stuff.columns['answer'].name == 'answer'
-
-    for r in stuff.rows:
-        # unknown fields are removed/not-accessible
-        assert 'name' in r
-        assert not 'id' in r
-        # missing data is available as default
-        assert 'answer' in r
-        assert r['answer'] == 42   # note: different from prev. line!
-
-        # all that still works when name overrides are used
-        assert not 'c' in r
-        assert 'count' in r
-        assert r['count'] == 1
-
-        # columns with data= option work fine
-        assert r['email'] == 'foo@bar.org'
-
-    # [bug] splicing the table gives us valid, working rows
-    assert list(stuff[0]) == list(stuff.rows[0])
-    assert stuff[0]['name'] == 'Foo Bar'
-
-    # changing an instance's base_columns does not change the class
-    assert id(stuff.base_columns) != id(StuffTable.base_columns)
-    stuff.base_columns['test'] = tables.Column()
-    assert not 'test' in StuffTable.base_columns
-
-    # optionally, exceptions can be raised when input is invalid
-    tables.options.IGNORE_INVALID_OPTIONS = False
-    try:
-        assert_raises(ValueError, setattr, stuff, 'order_by', '-name,made-up-column')
-        assert_raises(ValueError, setattr, stuff, 'order_by', ('made-up-column',))
-        # when a column name is overwritten, the original won't work anymore
-        assert_raises(ValueError, setattr, stuff, 'order_by', 'c')
-        # reset for future tests
-    finally:
-        tables.options.IGNORE_INVALID_OPTIONS = True
-
-
-class TestRender:
-    """Test use of the render_* methods.
-    """
-
-    def test(self):
-        class TestTable(tables.MemoryTable):
-            private_name = tables.Column(name='public_name')
-            def render_public_name(self, data):
-                # We are given the actual data dict and have direct access
-                # to additional values for which no field is defined.
-                return "%s:%s" % (data['private_name'], data['additional'])
-
-        table = TestTable([{'private_name': 'FOO', 'additional': 'BAR'}])
-        assert table.rows[0]['public_name'] == 'FOO:BAR'
-
-    def test_not_sorted(self):
-        """The render methods are not considered when sorting.
-        """
-        class TestTable(tables.MemoryTable):
-            foo = tables.Column()
-            def render_foo(self, data):
-                return -data['foo']  # try to cause a reverse sort
-        table = TestTable([{'foo': 1}, {'foo': 2}], order_by='asc')
-        # Result is properly sorted, and the render function has never been called
-        assert [r['foo'] for r in table.rows] == [-1, -2]
-
-
-def test_caches():
-    """Ensure the various caches are effective.
-    """
-
-    class BookTable(tables.MemoryTable):
-        name = tables.Column()
-        answer = tables.Column(default=42)
-    books = BookTable([
-        {'name': 'Foo: Bar'},
-    ])
-
-    assert id(list(books.columns)[0]) == id(list(books.columns)[0])
-    # TODO: row cache currently not used
-    #assert id(list(books.rows)[0]) == id(list(books.rows)[0])
-
-    # test that caches are reset after an update()
-    old_column_cache = id(list(books.columns)[0])
-    old_row_cache = id(list(books.rows)[0])
-    books.update()
-    assert id(list(books.columns)[0]) != old_column_cache
-    assert id(list(books.rows)[0]) != old_row_cache
-
-def test_meta_sortable():
-    """Specific tests for sortable table meta option."""
-
-    def mktable(default_sortable):
-        class BookTable(tables.MemoryTable):
-            id = tables.Column(sortable=True)
-            name = tables.Column(sortable=False)
-            author = tables.Column()
-            class Meta:
-                sortable = default_sortable
-        return BookTable([])
-
-    global_table = mktable(None)
-    for default_sortable, results in (
-        (None,      (True, False, True)),    # last bool is global default
-        (True,      (True, False, True)),    # last bool is table default
-        (False,     (True, False, False)),   # last bool is table default
-    ):
-        books = mktable(default_sortable)
-        assert [c.sortable for c in books.columns] == list(results)
-
-        # it also works if the meta option is manually changed after
-        # class and instance creation
-        global_table._meta.sortable = default_sortable
-        assert [c.sortable for c in global_table.columns] == list(results)
-
-
-def test_sort():
-    class BookTable(tables.MemoryTable):
-        id = tables.Column(direction='desc')
-        name = tables.Column()
-        pages = tables.Column(name='num_pages')  # test rewritten names
-        language = tables.Column(default='en')   # default affects sorting
-        rating = tables.Column(data='*')         # test data field option
-
-    books = BookTable([
-        {'id': 1, 'pages':  60, 'name': 'Z: The Book', '*': 5},    # language: en
-        {'id': 2, 'pages': 100, 'language': 'de', 'name': 'A: The Book', '*': 2},
-        {'id': 3, 'pages':  80, 'language': 'de', 'name': 'A: The Book, Vol. 2', '*': 4},
-        {'id': 4, 'pages': 110, 'language': 'fr', 'name': 'A: The Book, French Edition'},   # rating (with data option) is missing
-    ])
-
-    # None is normalized to an empty order by tuple, ensuring iterability;
-    # it also supports all the cool methods that we offer for order_by.
-    # This is true for the default case...
-    assert books.order_by == ()
-    iter(books.order_by)
-    assert hasattr(books.order_by, 'toggle')
-    # ...as well as when explicitly set to None.
-    books.order_by = None
-    assert books.order_by == ()
-    iter(books.order_by)
-    assert hasattr(books.order_by, 'toggle')
-
-    # test various orderings
-    def test_order(order, result):
-        books.order_by = order
-        assert [b['id'] for b in books.rows] == result
-    test_order(('num_pages',), [1,3,2,4])
-    test_order(('-num_pages',), [4,2,3,1])
-    test_order(('name',), [2,4,3,1])
-    test_order(('language', 'num_pages'), [3,2,1,4])
-    # using a simple string (for convinience as well as querystring passing
-    test_order('-num_pages', [4,2,3,1])
-    test_order('language,num_pages', [3,2,1,4])
-    # if overwritten, the declared fieldname has no effect
-    test_order('pages,name', [2,4,3,1])   # == ('name',)
-    # sort by column with "data" option
-    test_order('rating', [4,2,3,1])
-
-    # test the column with a default ``direction`` set to descending
-    test_order('id', [4,3,2,1])
-    test_order('-id', [1,2,3,4])
-    # changing the direction afterwards is fine too
-    books.base_columns['id'].direction = 'asc'
-    test_order('id', [1,2,3,4])
-    test_order('-id', [4,3,2,1])
-    # a invalid direction string raises an exception
-    assert_raises(ValueError, setattr, books.base_columns['id'], 'direction', 'blub')
-
-    # [bug] test alternative order formats if passed to constructor
-    BookTable([], 'language,-num_pages')
-
-    # test invalid order instructions
-    books.order_by = 'xyz'
-    assert not books.order_by
-    books.base_columns['language'].sortable = False
-    books.order_by = 'language'
-    assert not books.order_by
-    test_order(('language', 'num_pages'), [1,3,2,4])  # as if: 'num_pages'
-
-    # [bug] order_by did not run through setter when passed to init
-    books = BookTable([], order_by='name')
-    assert books.order_by == ('name',)
-
-    # test table.order_by extensions
-    books.order_by = ''
-    assert books.order_by.polarize(False) == ()
-    assert books.order_by.polarize(True) == ()
-    assert books.order_by.toggle() == ()
-    assert books.order_by.polarize(False, ['id']) == ('id',)
-    assert books.order_by.polarize(True, ['id']) == ('-id',)
-    assert books.order_by.toggle(['id']) == ('id',)
-    books.order_by = 'id,-name'
-    assert books.order_by.polarize(False, ['name']) == ('id', 'name')
-    assert books.order_by.polarize(True, ['name']) == ('id', '-name')
-    assert books.order_by.toggle(['name']) == ('id', 'name')
-    # ``in`` operator works
-    books.order_by = 'name'
-    assert 'name' in books.order_by
-    books.order_by = '-name'
-    assert 'name' in books.order_by
-    assert not 'language' in books.order_by
-
-
-def test_callable():
-    """Data fields and the ``default`` option can be callables.
-    """
-
-    class MathTable(tables.MemoryTable):
-        lhs = tables.Column()
-        rhs = tables.Column()
-        op = tables.Column(default='+')
-        sum = tables.Column(default=lambda d: calc(d['op'], d['lhs'], d['rhs']))
-
-    math = MathTable([
-        {'lhs': 1, 'rhs': lambda x: x['lhs']*3},              # 1+3
-        {'lhs': 9, 'rhs': lambda x: x['lhs'], 'op': '/'},     # 9/9
-        {'lhs': lambda x: x['rhs']+3, 'rhs': 4, 'op': '-'},   # 7-4
-    ])
-
-    # function is called when queried
-    def calc(op, lhs, rhs):
-        if op == '+': return lhs+rhs
-        elif op == '/': return lhs/rhs
-        elif op == '-': return lhs-rhs
-    assert [calc(row['op'], row['lhs'], row['rhs']) for row in math] == [4,1,3]
-
-    # field function is called while sorting
-    math.order_by = ('-rhs',)
-    assert [row['rhs'] for row in math] == [9,4,3]
-
-    # default function is called while sorting
-    math.order_by = ('sum',)
-    assert [row['sum'] for row in math] == [1,3,4]
-
-
-# TODO: all the column stuff might warrant it's own test file
-def test_columns():
-    """Test Table.columns container functionality.
-    """
-
-    class BookTable(tables.MemoryTable):
-        id = tables.Column(sortable=False, visible=False)
-        name = tables.Column(sortable=True)
-        pages = tables.Column(sortable=True)
-        language = tables.Column(sortable=False)
-    books = BookTable([])
-
-    assert list(books.columns.sortable()) == [c for c in books.columns if c.sortable]
-
-    # .columns iterator only yields visible columns
-    assert len(list(books.columns)) == 3
-    # visiblity of columns can be changed at instance-time
-    books.columns['id'].visible = True
-    assert len(list(books.columns)) == 4
-
-
-def test_column_order():
-    """Test the order functionality of bound columns.
-    """
-
-    class BookTable(tables.MemoryTable):
-        id = tables.Column()
-        name = tables.Column()
-        pages = tables.Column()
-        language = tables.Column()
-    books = BookTable([])
-
-    # the basic name property is a no-brainer
-    books.order_by = ''
-    assert [c.name for c in books.columns] == ['id','name','pages','language']
-
-    # name_reversed will always reverse, no matter what
-    for test in ['', 'name', '-name']:
-        books.order_by = test
-        assert [c.name_reversed for c in books.columns] == ['-id','-name','-pages','-language']
-
-    # name_toggled will always toggle
-    books.order_by = ''
-    assert [c.name_toggled for c in books.columns] == ['id','name','pages','language']
-    books.order_by = 'id'
-    assert [c.name_toggled for c in books.columns] == ['-id','name','pages','language']
-    books.order_by = '-name'
-    assert [c.name_toggled for c in books.columns] == ['id','name','pages','language']
-    # other columns in an order_by will be dismissed
-    books.order_by = '-id,name'
-    assert [c.name_toggled for c in books.columns] == ['id','-name','pages','language']
-
-    # with multi-column order, this is slightly more complex
-    books.order_by =  ''
-    assert [str(c.order_by) for c in books.columns] == ['id','name','pages','language']
-    assert [str(c.order_by_reversed) for c in books.columns] == ['-id','-name','-pages','-language']
-    assert [str(c.order_by_toggled) for c in books.columns] == ['id','name','pages','language']
-    books.order_by =  'id'
-    assert [str(c.order_by) for c in books.columns] == ['id','id,name','id,pages','id,language']
-    assert [str(c.order_by_reversed) for c in books.columns] == ['-id','id,-name','id,-pages','id,-language']
-    assert [str(c.order_by_toggled) for c in books.columns] == ['-id','id,name','id,pages','id,language']
-    books.order_by =  '-pages,id'
-    assert [str(c.order_by) for c in books.columns] == ['-pages,id','-pages,id,name','pages,id','-pages,id,language']
-    assert [str(c.order_by_reversed) for c in books.columns] == ['-pages,-id','-pages,id,-name','-pages,id','-pages,id,-language']
-    assert [str(c.order_by_toggled) for c in books.columns] == ['-pages,-id','-pages,id,name','pages,id','-pages,id,language']
-
-    # querying whether a column is ordered is possible
-    books.order_by = ''
-    assert [c.is_ordered for c in books.columns] == [False, False, False, False]
-    books.order_by = 'name'
-    assert [c.is_ordered for c in books.columns] == [False, True, False, False]
-    assert [c.is_ordered_reverse for c in books.columns] == [False, False, False, False]
-    assert [c.is_ordered_straight for c in books.columns] == [False, True, False, False]
-    books.order_by = '-pages'
-    assert [c.is_ordered for c in books.columns] == [False, False, True, False]
-    assert [c.is_ordered_reverse for c in books.columns] == [False, False, True, False]
-    assert [c.is_ordered_straight for c in books.columns] == [False, False, False, False]
-    # and even works with multi-column ordering
-    books.order_by = 'id,-pages'
-    assert [c.is_ordered for c in books.columns] == [True, False, True, False]
-    assert [c.is_ordered_reverse for c in books.columns] == [False, False, True, False]
-    assert [c.is_ordered_straight for c in books.columns] == [True, False, False, False]
diff --git a/django_tables/tests/models.py b/django_tables/tests/models.py
deleted file mode 100644 (file)
index 4bc010b..0000000
+++ /dev/null
@@ -1,392 +0,0 @@
-from django.contrib.auth.models import User
-from django.conf import settings
-from django.core.paginator import *
-import django_tables as tables
-from django_attest import TestContext
-from attest import Tests
-
-
-models = Tests()
-
-
-models.context(TestContext())
-
-
-@models.context
-def samples():
-    class Context(object):
-        class UserTable(tables.Table):
-            username = tables.Column()
-            first_name = tables.Column()
-            last_name = tables.Column()
-            email = tables.Column()
-            password = tables.Column()
-            is_staff = tables.Column()
-            is_active = tables.Column()
-            is_superuser = tables.Column()
-            last_login = tables.Column()
-            date_joined = tables.Column()
-
-    # we're going to test against User, so let's create a few
-    User.objects.create_user('fake-user-1', 'fake-1@example.com', 'password')
-    User.objects.create_user('fake-user-2', 'fake-2@example.com', 'password')
-    User.objects.create_user('fake-user-3', 'fake-3@example.com', 'password')
-    User.objects.create_user('fake-user-4', 'fake-4@example.com', 'password')
-
-    yield Context
-
-
-@models.test
-def simple(dj, samples):
-    table = samples.UserTable(User.objects.all())
-
-
-'''
-class City(models.Model):
-    name = models.TextField()
-    population = models.IntegerField(null=True)
-
-    class Meta:
-        app_label = 'django_tables'
-django_tables_models.City = City
-
-
-class Country(models.Model):
-    name = models.TextField()
-    population = models.IntegerField()
-    capital = models.ForeignKey(City, blank=True, null=True)
-    tld = models.TextField(verbose_name='Domain Extension', max_length=2)
-    system = models.TextField(blank=True, null=True)
-    null = models.TextField(blank=True, null=True)   # tests expect this to be always null!
-    null2 = models.TextField(blank=True, null=True)  #  - " -
-
-    class Meta:
-        app_label = 'django_tables'
-
-    def example_domain(self):
-        return 'example.%s' % self.tld
-django_tables_models.Country = Country
-
-# create the tables
-call_command('syncdb', verbosity=1, interactive=False)
-
-# create a couple of objects
-berlin=City(name="Berlin"); berlin.save()
-amsterdam=City(name="Amsterdam"); amsterdam.save()
-Country(name="Austria", tld="au", population=8, system="republic").save()
-Country(name="Germany", tld="de", population=81, capital=berlin).save()
-Country(name="France", tld="fr", population=64, system="republic").save()
-Country(name="Netherlands", tld="nl", population=16, system="monarchy", capital=amsterdam).save()
-
-
-class TestDeclaration:
-    """Test declaration, declared columns and default model field columns.
-    """
-
-    def test_autogen_basic(self):
-        class CountryTable(tables.ModelTable):
-            class Meta:
-                model = Country
-
-        assert len(CountryTable.base_columns) == 8
-        assert 'name' in CountryTable.base_columns
-        assert not hasattr(CountryTable, 'name')
-
-        # Override one model column, add another custom one, exclude one
-        class CountryTable(tables.ModelTable):
-            capital = tables.TextColumn(verbose_name='Name of capital')
-            projected = tables.Column(verbose_name="Projected Population")
-            class Meta:
-                model = Country
-                exclude = ['tld']
-
-        assert len(CountryTable.base_columns) == 8
-        assert 'projected' in CountryTable.base_columns
-        assert 'capital' in CountryTable.base_columns
-        assert not 'tld' in CountryTable.base_columns
-
-        # Inheritance (with a different model) + field restrictions
-        class CityTable(CountryTable):
-            class Meta:
-                model = City
-                columns = ['id', 'name']
-                exclude = ['capital']
-
-        print CityTable.base_columns
-        assert len(CityTable.base_columns) == 4
-        assert 'id' in CityTable.base_columns
-        assert 'name' in CityTable.base_columns
-        assert 'projected' in CityTable.base_columns # declared in parent
-        assert not 'population' in CityTable.base_columns  # not in Meta:columns
-        assert 'capital' in CityTable.base_columns  # in exclude, but only works on model fields (is that the right behaviour?)
-
-    def test_columns_custom_order(self):
-        """Using the columns meta option, you can also modify the ordering.
-        """
-        class CountryTable(tables.ModelTable):
-            foo = tables.Column()
-            class Meta:
-                model = Country
-                columns = ('system', 'population', 'foo', 'tld',)
-
-        assert [c.name for c in CountryTable().columns] == ['system', 'population', 'foo', 'tld']
-
-    def test_columns_verbose_name(self):
-        """Tests that the model field's verbose_name is used for the column
-        """
-        class CountryTable(tables.ModelTable):
-            class Meta:
-                model = Country
-                columns = ('tld',)
-
-        assert [c.column.verbose_name for c in CountryTable().columns] == ['Domain Extension']
-
-
-def test_basic():
-    """Some tests here are copied from ``test_basic.py`` but need to be
-    rerun with a ModelTable, as the implementation is different."""
-
-    class CountryTable(tables.ModelTable):
-        null = tables.Column(default="foo")
-        tld = tables.Column(name="domain")
-        class Meta:
-            model = Country
-            exclude = ('id',)
-    countries = CountryTable()
-
-    def test_country_table(table):
-        for r in table.rows:
-            # "normal" fields exist
-            assert 'name' in r
-            # unknown fields are removed/not accessible
-            assert not 'does-not-exist' in r
-            # ...so are excluded fields
-            assert not 'id' in r
-            # [bug] access to data that might be available, but does not
-            # have a corresponding column is denied.
-            assert_raises(Exception, "r['id']")
-            # missing data is available with default values
-            assert 'null' in r
-            assert r['null'] == "foo"   # note: different from prev. line!
-            # if everything else fails (no default), we get None back
-            assert r['null2'] is None
-
-            # all that still works when name overrides are used
-            assert not 'tld' in r
-            assert 'domain' in r
-            assert len(r['domain']) == 2   # valid country tld
-    test_country_table(countries)
-
-    # repeat the avove tests with a table that is not associated with a
-    # model, and all columns being created manually.
-    class CountryTable(tables.ModelTable):
-        name = tables.Column()
-        population = tables.Column()
-        capital = tables.Column()
-        system = tables.Column()
-        null = tables.Column(default="foo")
-        null2 = tables.Column()
-        tld = tables.Column(name="domain")
-    countries = CountryTable(Country)
-    test_country_table(countries)
-
-
-def test_invalid_accessor():
-    """Test that a column being backed by a non-existent model property
-    is handled correctly.
-
-    Regression-Test: There used to be a NameError here.
-    """
-    class CountryTable(tables.ModelTable):
-        name = tables.Column(data='something-i-made-up')
-    countries = CountryTable(Country)
-    assert_raises(ValueError, countries[0].__getitem__, 'name')
-
-
-def test_caches():
-    """Make sure the caches work for model tables as well (parts are
-    reimplemented).
-    """
-    class CountryTable(tables.ModelTable):
-        class Meta:
-            model = Country
-            exclude = ('id',)
-    countries = CountryTable()
-
-    assert id(list(countries.columns)[0]) == id(list(countries.columns)[0])
-    # TODO: row cache currently not used
-    #assert id(list(countries.rows)[0]) == id(list(countries.rows)[0])
-
-    # test that caches are reset after an update()
-    old_column_cache = id(list(countries.columns)[0])
-    old_row_cache = id(list(countries.rows)[0])
-    countries.update()
-    assert id(list(countries.columns)[0]) != old_column_cache
-    assert id(list(countries.rows)[0]) != old_row_cache
-
-def test_sort():
-    class CountryTable(tables.ModelTable):
-        tld = tables.Column(name="domain")
-        population = tables.Column()
-        system = tables.Column(default="republic")
-        custom1 = tables.Column()
-        custom2 = tables.Column(sortable=True)
-        class Meta:
-            model = Country
-    countries = CountryTable()
-
-    def test_order(order, result, table=countries):
-        table.order_by = order
-        assert [r['id'] for r in table.rows] == result
-
-    # test various orderings
-    test_order(('population',), [1,4,3,2])
-    test_order(('-population',), [2,3,4,1])
-    test_order(('name',), [1,3,2,4])
-    # test sorting with a "rewritten" column name
-    countries.order_by = 'domain,tld'      # "tld" would be invalid...
-    countries.order_by == ('domain',)      # ...and is therefore removed
-    test_order(('-domain',), [4,3,2,1])
-    # test multiple order instructions; note: one row is missing a "system"
-    # value, but has a default set; however, that has no effect on sorting.
-    test_order(('system', '-population'), [2,4,3,1])
-    # using a simple string (for convinience as well as querystring passing)
-    test_order('-population', [2,3,4,1])
-    test_order('system,-population', [2,4,3,1])
-
-    # test column with a default ``direction`` set to descending
-    class CityTable(tables.ModelTable):
-        name = tables.Column(direction='desc')
-        class Meta:
-            model = City
-    cities = CityTable()
-    test_order('name', [1,2], table=cities)   # Berlin to Amsterdam
-    test_order('-name', [2,1], table=cities)  # Amsterdam to Berlin
-
-    # test invalid order instructions...
-    countries.order_by = 'invalid_field,population'
-    assert countries.order_by == ('population',)
-    # ...in case of ModelTables, this primarily means that only
-    # model-based colunns are currently sortable at all.
-    countries.order_by = ('custom1', 'custom2')
-    assert countries.order_by == ()
-
-def test_default_sort():
-    class SortedCountryTable(tables.ModelTable):
-        class Meta:
-            model = Country
-            order_by = '-name'
-
-    # the order_by option is provided by TableOptions
-    assert_equal('-name', SortedCountryTable()._meta.order_by)
-
-    # the default order can be inherited from the table
-    assert_equal(('-name',), SortedCountryTable().order_by)
-    assert_equal(4, SortedCountryTable().rows[0]['id'])
-
-    # and explicitly set (or reset) via __init__
-    assert_equal(2, SortedCountryTable(order_by='system').rows[0]['id'])
-    assert_equal(1, SortedCountryTable(order_by=None).rows[0]['id'])
-
-def test_callable():
-    """Some of the callable code is reimplemented for modeltables, so
-    test some specifics again.
-    """
-
-    class CountryTable(tables.ModelTable):
-        null = tables.Column(default=lambda s: s['example_domain'])
-        example_domain = tables.Column()
-        class Meta:
-            model = Country
-    countries = CountryTable(Country)
-
-    # model method is called
-    assert [row['example_domain'] for row in countries] == \
-                    ['example.'+row['tld'] for row in countries]
-
-    # column default method is called
-    assert [row['example_domain'] for row in countries] == \
-                    [row['null'] for row in countries]
-
-
-def test_relationships():
-    """Test relationship spanning."""
-
-    class CountryTable(tables.ModelTable):
-        # add relationship spanning columns (using different approaches)
-        capital_name = tables.Column(data='capital__name')
-        capital__population = tables.Column(name="capital_population")
-        invalid = tables.Column(data="capital__invalid")
-        class Meta:
-            model = Country
-    countries = CountryTable(Country.objects.select_related('capital'))
-
-    # ordering and field access works
-    countries.order_by = 'capital_name'
-    assert [row['capital_name'] for row in countries.rows] == \
-        [None, None, 'Amsterdam', 'Berlin']
-
-    countries.order_by = 'capital_population'
-    assert [row['capital_population'] for row in countries.rows] == \
-        [None, None, None, None]
-
-    # ordering by a column with an invalid relationship fails silently
-    countries.order_by = 'invalid'
-    assert countries.order_by == ()
-
-
-def test_pagination():
-    """Pretty much the same as static table pagination, but make sure we
-    provide the capability, at least for paginators that use it, to not
-    have the complete queryset loaded (by use of a count() query).
-
-    Note: This test changes the available cities, make sure it is last,
-    or that tests that follow are written appropriately.
-    """
-    from django.db import connection
-
-    class CityTable(tables.ModelTable):
-        class Meta:
-            model = City
-            columns = ['name']
-    cities = CityTable()
-
-    # add some sample data
-    City.objects.all().delete()
-    for i in range(1,101):
-        City.objects.create(name="City %d"%i)
-
-    # for query logging
-    settings.DEBUG = True
-
-    # external paginator
-    start_querycount = len(connection.queries)
-    paginator = Paginator(cities.rows, 10)
-    assert paginator.num_pages == 10
-    page = paginator.page(1)
-    assert len(page.object_list) == 10
-    assert page.has_previous() == False
-    assert page.has_next() == True
-    # Make sure the queryset is not loaded completely - there must be two
-    # queries, one a count(). This check is far from foolproof...
-    assert len(connection.queries)-start_querycount == 2
-
-    # using a queryset paginator is possible as well (although unnecessary)
-    paginator = QuerySetPaginator(cities.rows, 10)
-    assert paginator.num_pages == 10
-
-    # integrated paginator
-    start_querycount = len(connection.queries)
-    cities.paginate(Paginator, 10, page=1)
-    # rows is now paginated
-    assert len(list(cities.rows.page())) == 10
-    assert len(list(cities.rows.all())) == 100
-    # new attributes
-    assert cities.paginator.num_pages == 10
-    assert cities.page.has_previous() == False
-    assert cities.page.has_next() == True
-    assert len(connection.queries)-start_querycount == 2
-
-    # reset
-    settings.DEBUG = False
-'''
index 2ebada62b35975a6686da5bfabc7d14cab3851bf..93ad7644b2ba3d0b5cfa5b7a2983dd0e7f1ef80b 100644 (file)
@@ -50,9 +50,9 @@ project = u'django-tables'
 # built documents.
 #
 # The short X.Y version.
-version = '0.4.0.alpha2.dev'
+version = '0.4.0.alpha2'
 # The full version, including alpha/beta/rc tags.
-release = '0.4.0.alpha2.dev'
+release = '0.4.0.alpha2'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
index 03d3b1f6b6c1c914c2bf5d2213fe0e00da4102f7..7cbe39649e08e42df5716e46fa4de2dde5e5d8db 100644 (file)
@@ -440,4 +440,14 @@ API Reference
 -------------------------
 
 .. autoclass:: django_tables.rows.BoundRow
-    :members: __init__, values, __getitem__, __contains__, __iter__
+    :members: __init__, __getitem__, __contains__, __iter__, record, table
+
+
+Glossary
+========
+
+.. glossary::
+
+    table
+        The traditional concept of a table. i.e. a grid of rows and columns
+        containing data.
index f1cd2885b60a27acd4d919f2fffb57b3b730fe96..bd4e54b2cb37e7dfd196989562fa2b5d23d2df6f 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -4,7 +4,7 @@ from setuptools import setup, find_packages
 
 setup(
     name='django-tables',
-    version='0.4.0.alpha2.dev',
+    version='0.4.0.alpha2',
     description='Table framework for Django',
 
     author='Bradley Ayers',
@@ -16,6 +16,10 @@ setup(
     include_package_data=True,  # declarations in MANIFEST.in
 
     install_requires=['Django >=1.1'],
+    tests_require=['Django >=1.1', 'Attest >=0.4', 'django_attest'],
+
+    test_loader='attest:FancyReporter.test_loader',
+    test_suite='tests.everything',
 
     classifiers=[
         'Environment :: Web Environment',
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644 (file)
index 0000000..602f2c4
--- /dev/null
@@ -0,0 +1,28 @@
+from attest import AssertImportHook, Tests
+
+# Django's django.utils.module_loading.module_has_submodule is busted
+AssertImportHook.disable()
+
+
+from django.conf import settings
+
+# It's important to configure prior to importing the tests, as some of them
+# import Django's DB stuff.
+settings.configure(
+    DATABASES = {
+        'default': {
+            'ENGINE': 'django.db.backends.sqlite3',
+        }
+    },
+    INSTALLED_APPS = [
+        'django_tables'
+    ]
+)
+
+
+from .core import core
+from .templates import templates
+from .models import models
+
+
+everything = Tests([core, templates, models])
similarity index 99%
rename from django_tables/tests/core.py
rename to tests/core.py
index f22d7b2fcee8927e2867f0704bbed22f684a1f6d..93fbd2da9a360154403dbe0b71065b575764a2aa 100644 (file)
@@ -1,5 +1,4 @@
 """Test the core table functionality."""
-
 import copy
 from attest import Tests, Assert
 from django.http import Http404
diff --git a/tests/models.py b/tests/models.py
new file mode 100644 (file)
index 0000000..7a4c9d4
--- /dev/null
@@ -0,0 +1,45 @@
+from django.contrib.auth.models import User
+from django.conf import settings
+from django.core.paginator import *
+import django_tables as tables
+from django_attest import TestContext
+from attest import Tests
+
+
+models = Tests()
+models.context(TestContext())
+
+
+@models.context
+def samples():
+    class Context(object):
+        class UserTable(tables.Table):
+            username = tables.Column()
+            first_name = tables.Column()
+            last_name = tables.Column()
+            email = tables.Column()
+            password = tables.Column()
+            is_staff = tables.Column()
+            is_active = tables.Column()
+            is_superuser = tables.Column()
+            last_login = tables.Column()
+            date_joined = tables.Column()
+
+    # we're going to test against User, so let's create a few
+    User.objects.create_user('fake-user-1', 'fake-1@example.com', 'password')
+    User.objects.create_user('fake-user-2', 'fake-2@example.com', 'password')
+    User.objects.create_user('fake-user-3', 'fake-3@example.com', 'password')
+    User.objects.create_user('fake-user-4', 'fake-4@example.com', 'password')
+
+    yield Context
+
+
+@models.test
+def simple(dj, samples):
+    users = User.objects.all()
+    table = samples.UserTable(users)
+
+    for index, row in enumerate(table.rows):
+        user = users[index]
+        Assert(user.username) == row['username']
+        Assert(user.email) == row['email']