-"""Test ModelTable specific functionality.\r
-\r
-Sets up a temporary Django project using a memory SQLite database.\r
-"""\r
-\r
-from django.conf import settings\r
-import django_tables as tables\r
-\r
-def setup_module(module):\r
- settings.configure(**{\r
- 'DATABASE_ENGINE': 'sqlite3',\r
- 'DATABASE_NAME': ':memory:',\r
- 'INSTALLED_APPS': ('tests.testapp',)\r
- })\r
-\r
- from django.db import models\r
- from django.core.management import call_command\r
-\r
- class City(models.Model):\r
- name = models.TextField()\r
- population = models.IntegerField()\r
- class Meta:\r
- app_label = 'testapp'\r
- module.City = City\r
-\r
- class Country(models.Model):\r
- name = models.TextField()\r
- population = models.IntegerField()\r
- capital = models.ForeignKey(City, blank=True, null=True)\r
- tld = models.TextField(verbose_name='Domain Extension', max_length=2)\r
- system = models.TextField(blank=True, null=True)\r
- null = models.TextField(blank=True, null=True) # tests expect this to be always null!\r
- class Meta:\r
- app_label = 'testapp'\r
- module.Country = Country\r
-\r
- # create the tables\r
- call_command('syncdb', verbosity=1, interactive=False)\r
-\r
- # create a couple of objects\r
- Country(name="Austria", tld="au", population=8, system="republic").save()\r
- Country(name="Germany", tld="de", population=81).save()\r
- Country(name="France", tld="fr", population=64, system="republic").save()\r
- Country(name="Netherlands", tld="nl", population=16, system="monarchy").save()\r
-\r
-def test_declaration():\r
- """Test declaration, declared columns and default model field columns.\r
- """\r
-\r
- class CountryTable(tables.ModelTable):\r
- class Meta:\r
- model = Country\r
-\r
- assert len(CountryTable.base_columns) == 7\r
- assert 'name' in CountryTable.base_columns\r
- assert not hasattr(CountryTable, 'name')\r
-\r
- # Override one model column, add another custom one, exclude one\r
- class CountryTable(tables.ModelTable):\r
- capital = tables.TextColumn(verbose_name='Name of capital')\r
- projected = tables.Column(verbose_name="Projected Population")\r
- class Meta:\r
- model = Country\r
- exclude = ['tld']\r
-\r
- assert len(CountryTable.base_columns) == 7\r
- assert 'projected' in CountryTable.base_columns\r
- assert 'capital' in CountryTable.base_columns\r
- assert not 'tld' in CountryTable.base_columns\r
-\r
- # Inheritance (with a different model) + field restrictions\r
- class CityTable(CountryTable):\r
- class Meta:\r
- model = City\r
- columns = ['id', 'name']\r
- exclude = ['capital']\r
-\r
- print CityTable.base_columns\r
- assert len(CityTable.base_columns) == 4\r
- assert 'id' in CityTable.base_columns\r
- assert 'name' in CityTable.base_columns\r
- assert 'projected' in CityTable.base_columns # declared in parent\r
- assert not 'population' in CityTable.base_columns # not in Meta:columns\r
- assert 'capital' in CityTable.base_columns # in exclude, but only works on model fields (is that the right behaviour?)\r
-\r
-def test_basic():\r
- """Some tests here are copied from ``test_basic.py`` but need to be\r
- rerun with a ModelTable, as the implementation is different."""\r
-\r
- class CountryTable(tables.ModelTable):\r
- null = tables.Column(default="foo")\r
- tld = tables.Column(name="domain")\r
- class Meta:\r
- model = Country\r
- exclude = ('id',)\r
- countries = CountryTable()\r
-\r
- def test_country_table(table):\r
- for r in table.rows:\r
- # "normal" fields exist\r
- assert 'name' in r\r
- # unknown fields are removed/not accessible\r
- assert not 'does-not-exist' in r\r
- # ...so are excluded fields\r
- assert not 'id' in r\r
- # missing data is available with default values\r
- assert 'null' in r\r
- assert r['null'] == "foo" # note: different from prev. line!\r
-\r
- # all that still works when name overrides are used\r
- assert not 'tld' in r\r
- assert 'domain' in r\r
- assert len(r['domain']) == 2 # valid country tld\r
- test_country_table(countries)\r
-\r
- # repeat the avove tests with a table that is not associated with a\r
- # model, and all columns being created manually.\r
- class CountryTable(tables.ModelTable):\r
- name = tables.Column()\r
- population = tables.Column()\r
- capital = tables.Column()\r
- system = tables.Column()\r
- null = tables.Column(default="foo")\r
- tld = tables.Column(name="domain")\r
- countries = CountryTable(Country)\r
- test_country_table(countries)\r
-\r
- # make sure the row and column caches work for model tables as well\r
- assert id(list(countries.columns)[0]) == id(list(countries.columns)[0])\r
- # TODO: row cache currently not used\r
- #assert id(list(countries.rows)[0]) == id(list(countries.rows)[0])\r
-\r
-def test_sort():\r
- class CountryTable(tables.ModelTable):\r
- tld = tables.Column(name="domain")\r
- system = tables.Column(default="republic")\r
- custom1 = tables.Column()\r
- custom2 = tables.Column(sortable=True)\r
- class Meta:\r
- model = Country\r
- countries = CountryTable()\r
-\r
- def test_order(order, result):\r
- countries.order_by = order\r
- assert [r['id'] for r in countries.rows] == result\r
-\r
- # test various orderings\r
- test_order(('population',), [1,4,3,2])\r
- test_order(('-population',), [2,3,4,1])\r
- test_order(('name',), [1,3,2,4])\r
- # test sorting with a "rewritten" column name\r
- countries.order_by = 'domain,tld' # "tld" would be invalid...\r
- countries.order_by == ('domain',) # ...and is therefore removed\r
- test_order(('-domain',), [4,3,2,1])\r
- # test multiple order instructions; note: one row is missing a "system"\r
- # value, but has a default set; however, that has no effect on sorting.\r
- test_order(('system', '-population'), [2,4,3,1])\r
- # using a simple string (for convinience as well as querystring passing\r
- test_order('-population', [2,3,4,1])\r
- test_order('system,-population', [2,4,3,1])\r
-\r
- # test invalid order instructions...\r
- countries.order_by = 'invalid_field,population'\r
- assert countries.order_by == ('population',)\r
- # ...in case of ModelTables, this primarily means that only\r
- # model-based colunns are currently sortable at all.\r
- countries.order_by = ('custom1', 'custom2')\r
- assert countries.order_by == ()\r
-\r
-def test_pagination():\r
- pass\r
-\r
-# TODO: pagination\r
-# TODO: support function column sources both for modeltables (methods on model) and static tables (functions in dict)\r
-# TODO: manual base columns change -> update() call (add as example in docstr here) -> rebuild snapshot: is row cache, column cache etc. reset?\r
-# TODO: support relationship spanning columns (we could generate select_related() automatically)
\ No newline at end of file
+"""Test ModelTable specific functionality.
+
+Sets up a temporary Django project using a memory SQLite database.
+"""
+
+from nose.tools import assert_raises, assert_equal
+from django.conf import settings
+from django.core.paginator import *
+import django_tables as tables
+
+
+def setup_module(module):
+ settings.configure(**{
+ 'DATABASE_ENGINE': 'sqlite3',
+ 'DATABASE_NAME': ':memory:',
+ 'INSTALLED_APPS': ('tests.testapp',)
+ })
+
+ from django.db import models
+ from django.core.management import call_command
+
+ class City(models.Model):
+ name = models.TextField()
+ population = models.IntegerField(null=True)
+ class Meta:
+ app_label = 'testapp'
+ module.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) # - " -
+ def example_domain(self):
+ return 'example.%s' % self.tld
+ class Meta:
+ app_label = 'testapp'
+ module.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
\ No newline at end of file