* render_FOO methods now only receive the arguments they accept (no more **kwargs) v0.4.0.beta4
authorBradley Ayers <bradley.ayers@enigmainteractive.com>
Tue, 5 Apr 2011 07:44:35 +0000 (17:44 +1000)
committerBradley Ayers <bradley.ayers@enigmainteractive.com>
Tue, 5 Apr 2011 07:48:39 +0000 (17:48 +1000)
* The default ordering of a table is now Table.Meta.order_by, rather than to not sort at all.
* Fixed a rendering bug with Django 1.3 (related to indexing into querysets)
* Model tests now work.

django_tables/rows.py
django_tables/tables.py
docs/conf.py
docs/index.rst
setup.py
tests/__init__.py
tests/core.py
tests/models.py
tests/templates.py
tests/testapp/models.py

index 1b662b99d80a94b0512bbc6d36a1c0d47fe38b4a..d14d0b8044a620555c795623242c05848a83cde5 100644 (file)
@@ -1,7 +1,9 @@
 # -*- coding: utf-8 -*-
+from itertools import imap, ifilter
+import inspect
 from django.utils.safestring import EscapeUnicode, SafeData
+from django.utils.functional import curry
 from .proxies import TemplateSafeLazyProxy
-import itertools
 
 
 class BoundRow(object):
@@ -120,20 +122,17 @@ class BoundRow(object):
         }
         render_FOO = 'render_' + bound_column.name
         render = getattr(self.table, render_FOO, bound_column.column.render)
-        try:
-            return render(**kwargs)
-        except TypeError as e:
-            # Let's be helpful and provide a decent error message, since
-            # render() underwent backwards incompatible changes.
-            if e.message.startswith('render() got an unexpected keyword'):
-                if hasattr(self.table, render_FOO):
-                    cls = self.table.__class__.__name__
-                    meth = render_FOO
-                else:
-                    cls = kwargs['column'].__class__.__name__
-                    meth = 'render'
-                msg = 'Did you forget to add **kwargs to %s.%s() ?' % (cls, meth)
-                raise TypeError(e.message + '. ' + msg)
+
+        # just give a list of all available methods
+        available = ifilter(curry(hasattr, inspect), ('getfullargspec', 'getargspec'))
+        spec = getattr(inspect, next(available))
+        # only provide the arguments that the func is interested in
+        kw = {}
+        for name in spec(render).args:
+            if name == 'self':
+                continue
+            kw[name] = kwargs[name]
+        return render(**kw)
 
     def __contains__(self, item):
         """Check by both row object and column name."""
@@ -158,14 +157,6 @@ class BoundRows(object):
     def __init__(self, table):
         self.table = table
 
-    def all(self):
-        """
-        Return an iterable for all :class:`.BoundRow` objects in the table.
-
-        """
-        for record in self.table.data:
-            yield BoundRow(self.table, record)
-
     def page(self):
         """
         If the table is paginated, return an iterable of :class:`.BoundRow`
@@ -179,7 +170,8 @@ class BoundRows(object):
 
     def __iter__(self):
         """Convience method for :meth:`.BoundRows.all`"""
-        return self.all()
+        for record in self.table.data:
+            yield BoundRow(self.table, record)
 
     def __len__(self):
         """Returns the number of rows in the table."""
@@ -191,8 +183,8 @@ class BoundRows(object):
     def __getitem__(self, key):
         """Allows normal list slicing syntax to be used."""
         if isinstance(key, slice):
-            return itertools.imap(lambda record: BoundRow(self.table, record),
-                                  self.table.data[key])
+            return imap(lambda record: BoundRow(self.table, record),
+                        self.table.data[key])
         elif isinstance(key, int):
             return BoundRow(self.table, self.table.data[key])
         else:
index 0b3efa06fd35dc3fdaf52b218b7db76a402d642f..d3a3bd15f8dd8831d28eb9930875a71ccc0e8b1c 100644 (file)
@@ -71,7 +71,17 @@ class TableData(object):
             translated.append(prefix + column.accessor)
         return OrderByTuple(translated)
 
+    def __iter__(self):
+        """
+        for ... in ... default to using this. There's a bug in Django 1.3
+        with indexing into querysets, so this side-steps that problem (as well
+        as just being a better way to iterate).
+
+        """
+        return self.list.__iter__() if hasattr(self, 'list') else self.queryset.__iter__()
+
     def __getitem__(self, index):
+        """Forwards indexing accesses to underlying data"""
         return (self.list if hasattr(self, 'list') else self.queryset)[index]
 
 
@@ -137,19 +147,32 @@ class TableOptions(object):
 
 
 class Table(StrAndUnicode):
-    """A collection of columns, plus their associated data rows.
+    """
+    A collection of columns, plus their associated data rows.
 
-    :type data: :class:`list` or :class:`QuerySet`
-    :param data:
-        The :term:`table data`.
+    :type data:  ``list`` or ``QuerySet``
+    :param data: The :term:`table data`.
 
-    :param: :class:`tuple`-like or :class:`basestring`
-    :param order_by:
-        The description of how the table should be ordered. This allows the
-        :attr:`.Table.Meta.order_by` option to be overridden.
+    :type order_by: ``Table.DoNotOrder``, ``None``, ``tuple`` or ``basestring``
+    :param order_by: sort the table based on these columns prior to display.
+        (default :attr:`.Table.Meta.order_by`)
 
-    .. note::
-        Unlike a :class:`Form`, tables are always bound to data.
+    The ``order_by`` argument is optional and allows the table's
+    ``Meta.order_by`` option to be overridden. If the ``bool(order_by)``
+    evaluates to ``False``, the table's ``Meta.order_by`` will be used. If you
+    want to disable a default ordering, you must pass in the value
+    ``Table.DoNotOrder``.
+
+    Example:
+
+    .. code-block:: python
+
+        def obj_list(request):
+            ...
+            # We don't want a default sort
+            order_by = request.GET.get('sort', SimpleTable.DoNotOrder)
+            table = SimpleTable(data, order_by=order_by)
+            ...
 
     """
     __metaclass__ = DeclarativeColumnsMetaclass
@@ -157,18 +180,22 @@ class Table(StrAndUnicode):
     # this value is not the same as None. it means 'use the default sort
     # order', which may (or may not) be inherited from the table options.
     # None means 'do not sort the data', ignoring the default.
-    DefaultOrder = type('DefaultSortType', (), {})()
+    DoNotOrder = type('DoNotOrder', (), {})
     TableDataClass = TableData
 
-    def __init__(self, data, order_by=DefaultOrder):
+    def __init__(self, data, order_by=None):
         self._rows = BoundRows(self)  # bound rows
         self._columns = BoundColumns(self)  # bound columns
         self._data = self.TableDataClass(data=data, table=self)
 
         # None is a valid order, so we must use DefaultOrder as a flag
         # to fall back to the table sort order.
-        self.order_by = (self._meta.order_by if order_by is Table.DefaultOrder
-                                             else order_by)
+        if not order_by:
+            self.order_by = self._meta.order_by
+        elif order_by is Table.DoNotOrder:
+            self.order_by = None
+        else:
+            self.order_by = order_by
 
         # Make a copy so that modifying this will not touch the class
         # definition. Note that this is different from forms, where the
index 67eef8ac761b4b30fb610aef058765bd36d476cf..bfa6dd5d8a91ad972f4a26c2cd07acf8f8b89a25 100644 (file)
@@ -52,7 +52,7 @@ project = u'django-tables'
 # The short X.Y version.
 version = '0.4.0'
 # The full version, including alpha/beta/rc tags.
-release = '0.4.0.beta3'
+release = '0.4.0.beta4'
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
index 3e38c2646546163ee271bf2508d7485687aa92bc..ac3622b81172ea4e74331f85ce0dd2f42a280e69 100644 (file)
@@ -241,8 +241,13 @@ copy & paste the method to every table you want to modify – which violates
 DRY).
 
 For convenience, a bunch of commonly used/useful values are passed to
-``render_FOO`` functions, when writing the signature, accept the arguments
-you're interested in, and collect the rest in a ``**kwargs`` argument.
+``render_FOO`` functions, when writing the signature, simply accept the
+arguments you're interested in, and the function will recieve them
+
+.. note:: Due to the implementation, a "catch-extra" arguments (e.g. ``*args``
+    or ``**kwargs``) will not recieve any arguments. This is because
+    ``inspect.getargsspec()`` is used to check what arguments a ``render_FOO``
+    method expect, and only to supply those.
 
 :param value: the value for the cell retrieved from the :term:`table data`
 :param record: the entire record for the row from :term:`table data`
index 1430d1586735859521d33ff97ebcdd6b718b753a..ecd501f4d39d206b59e1995a4c6551657ef919c3 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.beta3',
+    version='0.4.0.beta4',
     description='Table framework for Django',
 
     author='Bradley Ayers',
index fdb9e0edf38c5b70791635a8c46f5e1a4b786756..d592d868211675bc851599bda28bd007e9f8d1c0 100644 (file)
@@ -16,8 +16,6 @@ settings.configure(
         }
     },
     INSTALLED_APPS = [
-        #'django.contrib.contenttypes',
-        #'django.contrib.auth',
         'tests.testapp',
         'django_tables',
     ]
index 8341bdb3621fce5cec87d96fac5fe9b2fdf80724..8320e9b2fc4fa49de2c62038f6b4936116bbebdb 100644 (file)
@@ -103,24 +103,37 @@ def sorting(context):
 
     # order_by is inherited from the options if not explitly set
     table = MySortedTable([])
-    assert ('alpha',) == table.order_by
+    assert ('alpha', ) == table.order_by
 
     # ...but can be overloaded at __init___
     table = MySortedTable([], order_by='beta')
-    assert ('beta',) == table.order_by
+    assert ('beta', ) == table.order_by
 
     # ...or rewritten later
     table = MySortedTable(context.memory_data)
     table.order_by = 'beta'
-    assert ('beta',) == table.order_by
+    assert ('beta', ) == table.order_by
     assert 3 == table.rows[0]['i']
 
-    # ...or reset to None (unsorted), ignoring the table default
+    # Explicitly pass in None, should default to table's Meta.order_by
     table = MySortedTable(context.memory_data, order_by=None)
+    assert ('alpha', ) == table.order_by
+    assert 1 == table.rows[0]['i']
+
+    # ...or reset to Table.DoNotOrder (unsorted), ignoring the table default
+    table = MySortedTable(context.memory_data, order_by=MySortedTable.DoNotOrder)
     assert () == table.order_by
     assert 2 == table.rows[0]['i']
 
 
+@core.test
+def boundrows_iteration(context):
+    records = []
+    for row in context.table.rows:
+        records.append(row.record)
+    Assert(records) == context.memory_data
+
+
 @core.test
 def row_subscripting(context):
     row = context.table.rows[0]
@@ -181,7 +194,7 @@ def pagination():
     books.paginate(Paginator, page=1, per_page=10)
     # rows is now paginated
     assert len(list(books.rows.page())) == 10
-    assert len(list(books.rows.all())) == 100
+    assert len(list(books.rows)) == 100
     # new attributes
     assert books.paginator.num_pages == 10
     assert books.page.has_previous() == False
index cfeb176419e898ab88566692ff306d0c33498b5b..6075f6fc8848b4d10027fa5a7f2d6e901fdd53c0 100644 (file)
@@ -1,34 +1,41 @@
 from django.conf import settings
+from django.test.simple import DjangoTestSuiteRunner
+from django.test.client import RequestFactory
+from django.template import Template, Context
 from django.core.paginator import *
 import django_tables as tables
+import itertools
 from django_attest import TestContext
-from attest import Tests
-from .testapp.models import Person
+from attest import Tests, Assert
+from .testapp.models import Person, Occupation
 
 
 models = Tests()
 models.context(TestContext())
 
+runner = DjangoTestSuiteRunner()
+runner.setup_databases()
+
 
 @models.context
 def samples():
     class PersonTable(tables.Table):
         first_name = tables.Column()
         last_name = tables.Column()
+        occupation = tables.Column()
 
-    # we're going to test against User, so let's create a few
-    Person.objects.create(first_name='Bradley', last_name='Ayers')
-    Person.objects.create(first_name='Chris',   last_name='Doble')
+    # Test data
+    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)
 
     yield PersonTable
 
 
 @models.test
-def simple(client, UserTable):
-    queryset = Person.objects.all()
-    table = PersonTable(queryset)
-
-    for index, row in enumerate(table.rows):
-        person = queryset[index]
-        Assert(person.username) == row['first_name']
-        Assert(person.email) == row['last_name']
+def boundrows_iteration(client, PersonTable):
+    table = PersonTable(Person.objects.all())
+    records = [row.record for row in table.rows]
+    expecteds = Person.objects.all()
+    for expected, actual in itertools.izip(expecteds, records):
+        Assert(expected) == actual
index a591ffaeae084df155cd390e46084326e5594ab2..f6f1700eb17c4ab40b2e59832fb54d031c6368f7 100644 (file)
@@ -65,6 +65,7 @@ def custom_rendering(context):
               'None 31 Austria None 8 43 ')
     Assert(result) == template.render(context)
 
+
 @templates.test
 def templatetag(context):
     # ensure it works with a multi-order-by
index 48d828ef2e0a7a9a16f1cf970d7567ac5c29b581..f08923b409bfb9f27ae064540837069ba799df8c 100644 (file)
@@ -4,3 +4,14 @@ from django.db import models
 class Person(models.Model):
     first_name = models.CharField(max_length=200)
     last_name = models.CharField(max_length=200)
+    occupation = models.ForeignKey('Occupation', related_name='people')
+
+    def __unicode__(self):
+        return self.first_name
+
+
+class Occupation(models.Model):
+    name = models.CharField(max_length=200)
+
+    def __unicode__(self):
+        return self.name