+++ /dev/null
-from django.utils.functional import Promise
-
-
-class AbstractProxy(object):
- """Delegates all operations (except ``.__subject__``) to another object"""
- __slots__ = ()
-
- #def __call__(self, *args, **kw):
- # return self.__subject__(*args, **kw)
-
- def __getattribute__(self, attr, oga=object.__getattribute__):
- subject = oga(self,'__subject__')
- if attr=='__subject__':
- return subject
- return getattr(subject,attr)
-
- def __setattr__(self, attr, val, osa=object.__setattr__):
- if attr == '__subject__':
- osa(self, attr, val)
- else:
- setattr(self.__subject__, attr, val)
-
- def __delattr__(self, attr, oda=object.__delattr__):
- if attr=='__subject__':
- oda(self,attr)
- else:
- delattr(self.__subject__, attr)
-
- def __nonzero__(self):
- return bool(self.__subject__)
-
- def __getitem__(self, arg):
- return self.__subject__[arg]
-
- def __setitem__(self, arg, val):
- self.__subject__[arg] = val
-
- def __delitem__(self, arg):
- del self.__subject__[arg]
-
- def __getslice__(self, i, j):
- return self.__subject__[i:j]
-
-
- def __setslice__(self, i, j, val):
- self.__subject__[i:j] = val
-
- def __delslice__(self, i, j):
- del self.__subject__[i:j]
-
- def __contains__(self, ob):
- return ob in self.__subject__
-
- for name in 'repr str hash len abs complex int long float iter oct hex'.split():
- exec "def __%s__(self): return %s(self.__subject__)" % (name, name)
-
- for name in 'cmp', 'coerce', 'divmod':
- exec "def __%s__(self,ob): return %s(self.__subject__,ob)" % (name, name)
-
- for name, op in [
- ('lt','<'), ('gt','>'), ('le','<='), ('ge','>='),
- ('eq','=='), ('ne','!=')
- ]:
- exec "def __%s__(self,ob): return self.__subject__ %s ob" % (name, op)
-
- for name, op in [('neg','-'), ('pos','+'), ('invert','~')]:
- exec "def __%s__(self): return %s self.__subject__" % (name, op)
-
- for name, op in [
- ('or','|'), ('and','&'), ('xor','^'), ('lshift','<<'), ('rshift','>>'),
- ('add','+'), ('sub','-'), ('mul','*'), ('div','/'), ('mod','%'),
- ('truediv','/'), ('floordiv','//')
- ]:
- exec (
- "def __%(name)s__(self,ob):\n"
- " return self.__subject__ %(op)s ob\n"
- "\n"
- "def __r%(name)s__(self,ob):\n"
- " return ob %(op)s self.__subject__\n"
- "\n"
- "def __i%(name)s__(self,ob):\n"
- " self.__subject__ %(op)s=ob\n"
- " return self\n"
- ) % locals()
-
- del name, op
-
- # Oddball signatures
-
- def __rdivmod__(self,ob):
- return divmod(ob, self.__subject__)
-
- def __pow__(self, *args):
- return pow(self.__subject__, *args)
-
- def __ipow__(self, ob):
- self.__subject__ **= ob
- return self
-
- def __rpow__(self, ob):
- return pow(ob, self.__subject__)
-
-
-class ObjectProxy(AbstractProxy):
- """Proxy for a specific object"""
-
- __slots__ = "__subject__"
-
- def __init__(self, subject):
- self.__subject__ = subject
-
-
-class CallbackProxy(AbstractProxy):
- """Proxy for a dynamically-chosen object"""
-
- __slots__ = '__callback__'
-
- def __init__(self, func):
- set_callback(self, func)
-
-set_callback = CallbackProxy.__callback__.__set__
-get_callback = CallbackProxy.__callback__.__get__
-CallbackProxy.__subject__ = property(lambda self, gc=get_callback: gc(self)())
-
-
-class LazyProxy(CallbackProxy):
- """Proxy for a lazily-obtained object, that is cached on first use"""
- __slots__ = "__cache__"
-
-get_cache = LazyProxy.__cache__.__get__
-set_cache = LazyProxy.__cache__.__set__
-
-def __subject__(self, get_cache=get_cache, set_cache=set_cache):
- try:
- return get_cache(self)
- except AttributeError:
- set_cache(self, get_callback(self)())
- return get_cache(self)
-
-LazyProxy.__subject__ = property(__subject__, set_cache)
-del __subject__
-
-
-class TemplateSafeLazyProxy(LazyProxy):
- """
- A version of LazyProxy suitable for use in Django templates.
-
- It's important that an ``alters_data`` attribute returns :const:`False`.
-
- """
- def __getattribute__(self, attr, *args, **kwargs):
- if attr == 'alters_data':
- return False
- return LazyProxy.__getattribute__(self, attr, *args, **kwargs)
import inspect
from django.utils.safestring import EscapeUnicode, SafeData
from django.utils.functional import curry
-from .proxies import TemplateSafeLazyProxy
class BoundRow(object):
return raw if raw is not None else bound_column.default
kwargs = {
- 'value': TemplateSafeLazyProxy(value),
- 'record': self.record,
- 'column': bound_column.column,
- 'bound_column': bound_column,
- 'bound_row': self,
- 'table': self._table,
+ 'value': value, # already a function
+ 'record': lambda: self.record,
+ 'column': lambda: bound_column.column,
+ 'bound_column': lambda: bound_column,
+ 'bound_row': lambda: self,
+ 'table': lambda: self._table,
}
render_FOO = 'render_' + bound_column.name
render = getattr(self.table, render_FOO, bound_column.column.render)
# just give a list of all available methods
- available = ifilter(curry(hasattr, inspect), ('getfullargspec', 'getargspec'))
- spec = getattr(inspect, next(available))
+ funcs = ifilter(curry(hasattr, inspect), ('getfullargspec', 'getargspec'))
+ spec = getattr(inspect, next(funcs))
# 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]
+ kw[name] = kwargs[name]()
return render(**kw)
def __contains__(self, item):
def __init__(self, table):
self.table = table
- def page(self):
- """
- If the table is paginated, return an iterable of :class:`.BoundRow`
- objects that appear on the current page.
-
- :rtype: iterable of :class:`.BoundRow` objects, or :const:`None`.
- """
- if not hasattr(self.table, 'page'):
- return None
- return iter(self.table.page.object_list)
-
def __iter__(self):
"""Convience method for :meth:`.BoundRows.all`"""
for record in self.table.data:
order_by = (order_by, )
self.order_by = OrderByTuple(order_by)
self.attrs = AttributeDict(getattr(options, 'attrs', {}))
+ self.empty_text = getattr(options, 'empty_text', None)
class Table(StrAndUnicode):
:type data: ``list`` or ``QuerySet``
:param data: The :term:`table data`.
- :type order_by: ``Table.DoNotOrder``, ``None``, ``tuple`` or ``basestring``
+ :type order_by: ``None``, ``tuple`` or ``string``
:param order_by: sort the table based on these columns prior to display.
(default :attr:`.Table.Meta.order_by`)
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``.
+ ``Meta.order_by`` option to be overridden. If the ``order_by is None``
+ the table's ``Meta.order_by`` will be used. If you want to disable a
+ default ordering, simply use an empty ``tuple``, ``string``, or ``list``,
+ e.g. ``Table(…, order_by='')``.
Example:
def obj_list(request):
...
- # We don't want a default sort
- order_by = request.GET.get('sort', SimpleTable.DoNotOrder)
+ # If there's no ?sort=…, we don't want to fallback to
+ # Table.Meta.order_by, thus we must not default to passing in None
+ order_by = request.GET.get('sort', ())
table = SimpleTable(data, order_by=order_by)
...
"""
__metaclass__ = DeclarativeColumnsMetaclass
-
- # 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.
- DoNotOrder = type('DoNotOrder', (), {})
TableDataClass = TableData
- def __init__(self, data, order_by=None):
+ def __init__(self, data, order_by=None, sortable=None, empty_text=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.
- if not order_by:
+ if order_by is None:
self.order_by = self._meta.order_by
- elif order_by is Table.DoNotOrder:
- self.order_by = None
else:
self.order_by = order_by
+ self.sortable = sortable
+ self.empty_text = empty_text
+
# Make a copy so that modifying this will not touch the class
# definition. Note that this is different from forms, where the
# copy is made available in a ``fields`` attribute. See the
self._order_by = order_by
self._data.order_by(order_by)
+ @property
+ def sortable(self):
+ return self._sortable if self._sortable is not None else self._meta.sortable
+
+ @sortable.setter
+ def sortable(self, value):
+ self._sortable = value
+
+ @property
+ def empty_text(self):
+ return self._empty_text if self._empty_text is not None else self._meta.empty_text
+
+ @empty_text.setter
+ def empty_text(self, value):
+ self._empty_text = value
+
@property
def rows(self):
return self._rows
# The short X.Y version.
version = '0.4.0'
# The full version, including alpha/beta/rc tags.
-release = '0.4.0.beta4'
+release = '0.4.0.beta5'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
setup(
name='django-tables',
- version='0.4.0.beta4',
+ version='0.4.0.beta5',
description='Table framework for Django',
author='Bradley Ayers',
from .templates import templates
from .models import models
from .utils import utils
+from .rows import rows
-everything = Tests([core, templates, models, utils])
+everything = Tests([core, templates, models, utils, rows])
alpha = tables.Column()
beta = tables.Column()
+ class SortedTable(UnsortedTable):
+ class Meta:
+ order_by = 'alpha'
+
table = UnsortedTable(memory_data)
yield Context
@core.test
-def sorting(context):
- class MyUnsortedTable(tables.Table):
- i = tables.Column()
- alpha = tables.Column()
- beta = tables.Column()
-
- # various different ways to say the same thing: don't sort
- Assert(MyUnsortedTable([]).order_by) == ()
- Assert(MyUnsortedTable([], order_by=None).order_by) == ()
- Assert(MyUnsortedTable([], order_by=[]).order_by) == ()
- Assert(MyUnsortedTable([], order_by=()).order_by) == ()
+def sorting(ctx):
+ # fallback to Table.Meta
+ Assert(('alpha', )) == ctx.SortedTable([], order_by=None).order_by == ctx.SortedTable([]).order_by
# values of order_by are wrapped in tuples before being returned
- Assert(MyUnsortedTable([], order_by='alpha').order_by) == ('alpha',)
- Assert(MyUnsortedTable([], order_by=('beta',)).order_by) == ('beta',)
-
- # a rewritten order_by is also wrapped
- table = MyUnsortedTable([])
- table.order_by = 'alpha'
- assert ('alpha', ) == table.order_by
-
- # default sort order can be specified in table options
- class MySortedTable(MyUnsortedTable):
- class Meta:
- order_by = 'alpha'
+ Assert(ctx.SortedTable([], order_by='alpha').order_by) == ('alpha', )
+ Assert(ctx.SortedTable([], order_by=('beta',)).order_by) == ('beta', )
- # order_by is inherited from the options if not explitly set
- table = MySortedTable([])
- assert ('alpha', ) == table.order_by
+ # "no sorting"
+ table = ctx.SortedTable([])
+ table.order_by = []
+ Assert(()) == table.order_by == ctx.SortedTable([], order_by=[]).order_by
- # ...but can be overloaded at __init___
- table = MySortedTable([], order_by='beta')
- assert ('beta', ) == table.order_by
+ table = ctx.SortedTable([])
+ table.order_by = ()
+ Assert(()) == table.order_by == ctx.SortedTable([], order_by=()).order_by
- # ...or rewritten later
- table = MySortedTable(context.memory_data)
- table.order_by = 'beta'
- assert ('beta', ) == table.order_by
- assert 3 == table.rows[0]['i']
-
- # 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']
+ table = ctx.SortedTable([])
+ table.order_by = ''
+ Assert(()) == table.order_by == ctx.SortedTable([], order_by='').order_by
+ # apply a sorting
+ table = ctx.UnsortedTable([])
+ table.order_by = 'alpha'
+ Assert(('alpha', )) == ctx.UnsortedTable([], order_by='alpha').order_by == table.order_by
-@core.test
-def boundrows_iteration(context):
- records = []
- for row in context.table.rows:
- records.append(row.record)
- Assert(records) == context.memory_data
+ table = ctx.SortedTable([])
+ table.order_by = 'alpha'
+ Assert(('alpha', )) == ctx.SortedTable([], order_by='alpha').order_by == table.order_by
+ # let's check the data
+ table = ctx.SortedTable(ctx.memory_data, order_by='beta')
+ Assert(3) == table.rows[0]['i']
-@core.test
-def row_subscripting(context):
- row = context.table.rows[0]
- # attempt number indexing
- Assert(row[0]) == 2
- Assert(row[1]) == 'b'
- Assert(row[2]) == 'b'
- with Assert.raises(IndexError) as error:
- row[3]
- # attempt column name indexing
- Assert(row['i']) == 2
- Assert(row['alpha']) == 'b'
- Assert(row['beta']) == 'b'
- with Assert.raises(KeyError) as error:
- row['gamma']
+ # allow fallback to Table.Meta.order_by
+ table = ctx.SortedTable(ctx.memory_data)
+ Assert(1) == table.rows[0]['i']
@core.test
col2 = tables.Column(accessor='alpha.upper')
table = SimpleTable(context.memory_data)
row = table.rows[0]
- Assert(row['col1']) == True
+ Assert(row['col1']) is True
Assert(row['col2']) == 'B'
# create some sample data
data = []
- for i in range(1,101):
- data.append({'name': 'Book Nr. %d' % i})
+ for i in range(100):
+ data.append({'name': 'Book No. %d' % i})
books = BookTable(data)
# external paginator
paginator = Paginator(books.rows, 10)
assert paginator.num_pages == 10
page = paginator.page(1)
- assert page.has_previous() == False
- assert page.has_next() == True
+ assert page.has_previous() is False
+ assert page.has_next() is True
# integrated paginator
- books.paginate(Paginator, page=1, per_page=10)
- # rows is now paginated
- assert len(list(books.rows.page())) == 10
- assert len(list(books.rows)) == 100
+ books.paginate(page=1)
+ Assert(hasattr(books, 'page')) is True
+
+ books.paginate(page=1, per_page=10)
+ Assert(len(list(books.page.object_list))) == 10
+
# new attributes
- assert books.paginator.num_pages == 10
- assert books.page.has_previous() == False
- assert books.page.has_next() == True
+ Assert(books.paginator.num_pages) == 10
+ Assert(books.page.has_previous()) is False
+ Assert(books.page.has_next()) is True
+
# exceptions are converted into 404s
with Assert.raises(Http404) as error:
books.paginate(Paginator, page=9999, per_page=10)
books.paginate(Paginator, page='abc', per_page=10)
-
-
-if __name__ == '__main__':
- core.main()
--- /dev/null
+"""Test the core table functionality."""
+from attest import Tests, Assert
+import django_tables as tables
+from django_tables import utils
+
+
+rows = Tests()
+
+
+@rows.test
+def bound_rows():
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+
+ data = [
+ {'name': 'Bradley'},
+ {'name': 'Chris'},
+ {'name': 'Peter'},
+ ]
+
+ table = SimpleTable(data)
+
+ # iteration
+ records = []
+ for row in table.rows:
+ records.append(row.record)
+ Assert(records) == data
+
+
+@rows.test
+def bound_row():
+ class SimpleTable(tables.Table):
+ name = tables.Column()
+ occupation = tables.Column()
+ age = tables.Column()
+
+ record = {'name': 'Bradley', 'age': 20, 'occupation': 'programmer'}
+
+ table = SimpleTable([record])
+ row = table.rows[0]
+
+ # integer indexing into a row
+ Assert(row[0]) == record['name']
+ Assert(row[1]) == record['occupation']
+ Assert(row[2]) == record['age']
+
+ with Assert.raises(IndexError) as error:
+ row[3]
+
+ # column name indexing into a row
+ Assert(row['name']) == record['name']
+ Assert(row['occupation']) == record['occupation']
+ Assert(row['age']) == record['age']
+
+ with Assert.raises(KeyError) as error:
+ row['gamma']