From b4d157013e984f5d29b1877fdecbc0482fd8fb9f Mon Sep 17 00:00:00 2001 From: Bradley Ayers Date: Fri, 25 Feb 2011 17:36:14 +1000 Subject: [PATCH] added a test to reveal problem with using querysets as data sources --- django_tables/tables.py | 46 ++++----- django_tables/tests/__init__.py | 5 +- django_tables/tests/models.py | 119 +++++++++++++++--------- django_tables/tests/testapp/__init__.py | 0 django_tables/tests/testapp/models.py | 1 - docs/index.rst | 8 +- 6 files changed, 104 insertions(+), 75 deletions(-) delete mode 100644 django_tables/tests/testapp/__init__.py delete mode 100644 django_tables/tests/testapp/models.py diff --git a/django_tables/tables.py b/django_tables/tables.py index df26d8f..5b4f8d3 100644 --- a/django_tables/tables.py +++ b/django_tables/tables.py @@ -16,8 +16,9 @@ __all__ = ('Table',) QUERYSET_ACCESSOR_SEPARATOR = '__' class TableData(object): - """Exposes a consistent API for a table data. It currently supports a query - set and a list of dicts. + """Exposes a consistent API for a table data. It currently supports a + :class:`QuerySet` or a ``list`` of ``dict``s. + """ def __init__(self, data, table): from django.db.models.query import QuerySet @@ -68,6 +69,7 @@ class TableData(object): """Populates self._data with missing values based on the default value for each column. It will create new items in the dataset (not modify existing ones). + """ for i, item in enumerate(data): # add data that is missing from the source. we do this now @@ -99,8 +101,9 @@ class TableData(object): def data_for_cell(self, bound_column, bound_row, apply_formatter=True): """Calculate the value of a cell given a bound row and bound column. - *formatting* – Apply column formatter after retrieving the value from - the data. + :param formatting: + Apply column formatter after retrieving the value from the data. + """ value = Accessor(bound_column.accessor).resolve(bound_row.data) # try and use default value if we've only got 'None' @@ -116,28 +119,13 @@ class TableData(object): class DeclarativeColumnsMetaclass(type): """Metaclass that converts Column attributes on the class to a dictionary - called 'base_columns', taking into account parent class 'base_columns' as - well. + called ``base_columns``, taking into account parent class ``base_columns`` + as well. + """ def __new__(cls, name, bases, attrs, parent_cols_from=None): - """The ``parent_cols_from`` argument determines from which attribute - we read the columns of a base class that this table might be - subclassing. This is useful for ``ModelTable`` (and possibly other - derivatives) which might want to differ between the declared columns - and others. - - Note that if the attribute specified in ``parent_cols_from`` is not - found, we fall back to the default (``base_columns``), instead of - skipping over that base. This makes a table like the following work: - - class MyNewTable(tables.ModelTable, MyNonModelTable): - pass - - ``MyNewTable`` will be built by the ModelTable metaclass, which will - call this base with a modified ``parent_cols_from`` argument - specific to ModelTables. Since ``MyNonModelTable`` is not a - ModelTable, and thus does not provide that attribute, the columns - from that base class would otherwise be ignored. + """Ughhh document this :) + """ # extract declared columns columns = [(name, attrs.pop(name)) for name, column in attrs.items() @@ -188,8 +176,13 @@ class Table(StrAndUnicode): def __init__(self, data, order_by=DefaultOrder): """Create a new table instance with the iterable ``data``. - If ``order_by`` is specified, the data will be sorted accordingly. - Otherwise, the sort order can be specified in the table options. + :param order_by: + If specified, it must be a sequence containing the names of columns + in the order that they should be ordered (much the same as + :method:`QuerySet.order_by`) + + If not specified, the table will fall back to the + :attr:`Meta.order_by` setting. Note that unlike a ``Form``, tables are always bound to data. Also unlike a form, the ``columns`` attribute is read-only and returns @@ -200,6 +193,7 @@ class Table(StrAndUnicode): the perfect fit for this. Instead, ``base_colums`` is copied to table instances, so modifying that will not touch the class-wide column list. + """ self._rows = Rows(self) # bound rows self._columns = Columns(self) # bound columns diff --git a/django_tables/tests/__init__.py b/django_tables/tests/__init__.py index 2cedec1..5572945 100644 --- a/django_tables/tests/__init__.py +++ b/django_tables/tests/__init__.py @@ -1,10 +1,11 @@ from attest import Tests from .core import core from .templates import templates +from .models import models #from .memory import memory -#from .models import models -tests = Tests([core, templates]) + +tests = Tests([core, templates, models]) def suite(): return tests.test_suite() diff --git a/django_tables/tests/models.py b/django_tables/tests/models.py index c0bfaa5..4148822 100644 --- a/django_tables/tests/models.py +++ b/django_tables/tests/models.py @@ -1,58 +1,87 @@ -"""Test ModelTable specific functionality. - -Sets up a temporary Django project using a memory SQLite database. -""" - +from django.contrib.auth.models import User from django.conf import settings from django.core.paginator import * import django_tables as tables +import django_tables.models as django_tables_models from attest import Tests +# 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') + + models = Tests() -''' -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 +@models.context +def transaction(): + print 't1' + yield + print 't2' + +@models.context +def context(): + 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() + + print 'c1' + yield Context + print 'c2' + +@models.test +def simple(context): + table = context.UserTable(User.objects.all()) - 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 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: diff --git a/django_tables/tests/testapp/__init__.py b/django_tables/tests/testapp/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/django_tables/tests/testapp/models.py b/django_tables/tests/testapp/models.py deleted file mode 100644 index 7bedc38..0000000 --- a/django_tables/tests/testapp/models.py +++ /dev/null @@ -1 +0,0 @@ -"""Empty demo app our tests can assign models to.""" diff --git a/docs/index.rst b/docs/index.rst index 40cdf4a..e79be21 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -380,9 +380,15 @@ which can be iterated over: API Reference ============= -:class:`Column` Objects: +:class:`Table` Objects: ------------------------ +.. autoclass:: django_tables.tables.Table + :members: __init__, data, order_by, rows, columns, as_html, paginate + + +:class:`Column` Objects: +------------------------ .. autoclass:: django_tables.columns.Column :members: __init__, default, render -- 2.26.2