-===============\r
+Tables and Pagination\r
+---------------------\r
+\r
+ table = MyTable(queryset)\r
+ p = Paginator(table.rows, 10) # paginator will need to be able to handle our modelproxy\r
+\r
+ table = MyTable(queryset)\r
+ table.pagination = Paginator(10, padding=2)\r
+ table.paginate(DiggPaginator, 10, padding=2)\r
+\r
+Works exactly like in the Django database API. Order may be specified as\r
+a list (or tuple) of column names. If prefixed with a hypen, the ordering\r
+for that particular field will be in reverse order.\r
+\r
+Random ordering is currently not supported.\r
+\r
+\r
Ordering Syntax\r
-===============\r
+---------------\r
\r
Works exactly like in the Django database API. Order may be specified as\r
a list (or tuple) of column names. If prefixed with a hypen, the ordering\r
\r
Random ordering is currently not supported.\r
\r
-====\r
+\r
+Template Utilities\r
+------------------\r
+\r
+If you want the give your users the ability to interact with your table (e.g.\r
+change the ordering), you will need to create urls with the appropriate\r
+queries. To simplify that process, django-tables comes with helpful\r
+templatetag:\r
+\r
+ {% set_url_param "sort" "name" %} # ?sort=name\r
+ {% set_url_param "sort" %} # delete "sort" param\r
+\r
+The template library can be found in 'django_modules.app.templates.tables'.\r
+If you add ''django_modules.app' to your INSTALLED_APPS setting, you will\r
+be able to do:\r
+\r
+ {% load tables %}\r
+\r
+\r
TODO\r
-====\r
+----\r
\r
- Support table filters\r
- Support grouping\r
--- /dev/null
+"""\r
+Allows setting/changing/removing of chosen url query string parameters,\r
+while maintaining any existing others.\r
+\r
+Expects the current request to be available in the context as ``request``.\r
+\r
+Examples:\r
+\r
+ {% set_url_param page=next_page %}\r
+ {% set_url_param page="" %}\r
+ {% set_url_param filter="books" page=1 %}\r
+"""\r
+\r
+import urllib\r
+import tokenize\r
+import StringIO\r
+from django import template\r
+from django.utils.safestring import mark_safe\r
+\r
+register = template.Library()\r
+\r
+class SetUrlParamNode(template.Node):\r
+ def __init__(self, changes):\r
+ self.changes = changes\r
+\r
+ def render(self, context):\r
+ request = context.get('request', None)\r
+ if not request: return ""\r
+\r
+ # Note that we want params to **not** be a ``QueryDict`` (thus we\r
+ # don't use it's ``copy()`` method), as it would force all values\r
+ # to be unicode, and ``urllib.urlencode`` can't handle that.\r
+ params = dict(request.GET)\r
+ for key, newvalue in self.changes.items():\r
+ newvalue = newvalue.resolve(context)\r
+ if newvalue=='' or newvalue is None: params.pop(key, False)\r
+ else: params[key] = newvalue\r
+ # ``urlencode`` chokes on unicode input, so convert everything to\r
+ # utf8. Note that if some query arguments passed to the site have\r
+ # their non-ascii characters screwed up when passed though this,\r
+ # it's most likely not our fault. Django (the ``QueryDict`` class\r
+ # to be exact) uses your projects DEFAULT_CHARSET to decode incoming\r
+ # query strings, whereas your browser might encode the url\r
+ # differently. For example, typing "ä" in my German Firefox's (v2)\r
+ # address bar results in "%E4" being passed to the server (in\r
+ # iso-8859-1), but Django might expect utf-8, where ä would be\r
+ # "%C3%A4"\r
+ def mkstr(s):\r
+ if isinstance(s, list): return map(mkstr, s)\r
+ else: return (isinstance(s, unicode) and [s.encode('utf-8')] or [s])[0]\r
+ params = dict([(mkstr(k), mkstr(v)) for k, v in params.items()])\r
+ # done, return (string is already safe)\r
+ return '?'+urllib.urlencode(params, doseq=True)\r
+\r
+def do_seturlparam(parser, token):\r
+ bits = token.contents.split()\r
+ qschanges = {}\r
+ for i in bits[1:]:\r
+ try:\r
+ a, b = i.split('=', 1); a = a.strip(); b = b.strip()\r
+ keys = list(tokenize.generate_tokens(StringIO.StringIO(a).readline))\r
+ if keys[0][0] == tokenize.NAME:\r
+ if b == '""': b = template.Variable('""') # workaround bug #5270\r
+ else: b = parser.compile_filter(b)\r
+ qschanges[str(a)] = b\r
+ else: raise ValueError\r
+ except ValueError:\r
+ raise template.TemplateSyntaxError, "Argument syntax wrong: should be key=value"\r
+ return SetUrlParamNode(qschanges)\r
+\r
+register.tag('set_url_param', do_seturlparam)
\ No newline at end of file
def as_html(self):\r
pass\r
\r
-from smartinspect.auto import *\r
-si.enabled = True\r
-\r
class DeclarativeColumnsMetaclass(type):\r
"""\r
Metaclass that converts Column attributes to a dictionary called\r
'base_columns', taking into account parent class 'base_columns'\r
as well.\r
"""\r
- @si_main.track\r
def __new__(cls, name, bases, attrs, parent_cols_from=None):\r
"""\r
The ``parent_cols_from`` argument determins from which attribute\r
\r
return type.__new__(cls, name, bases, attrs)\r
\r
+class OrderByTuple(tuple, StrAndUnicode):\r
+ """Stores 'order by' instructions; Currently only used to render\r
+ to the output (especially in templates) in a format we understand\r
+ as input.\r
+ """\r
+ def __unicode__(self):\r
+ return ",".join(self)\r
+\r
class BaseTable(object):\r
def __init__(self, data, order_by=None):\r
"""Create a new table instance with the iterable ``data``.\r
def _set_order_by(self, value):\r
if self._data_cache is not None:\r
self._data_cache = None\r
- self._order_by = isinstance(value, (tuple, list)) and value or (value,)\r
+ # accept both string and tuple instructions\r
+ self._order_by = (isinstance(value, basestring) \\r
+ and [value.split(',')] \\r
+ or [value])[0]\r
# validate, remove all invalid order instructions\r
def can_be_used(o):\r
c = (o[:1]=='-' and [o[1:]] or [o])[0]\r
return c in self.columns and self.columns[c].sortable\r
- self._order_by = [o for o in self._order_by if can_be_used(o)]\r
+ self._order_by = OrderByTuple([o for o in self._order_by if can_be_used(o)])\r
# TODO: optionally, throw an exception\r
order_by = property(lambda s: s._order_by, _set_order_by)\r
\r
+from django.core.paginator import Paginator\r
import os, sys\r
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))\r
\r
# test various orderings\r
test_order(('pages',), [1,3,2,4])\r
test_order(('-pages',), [4,2,3,1])\r
- test_order('-pages', [4,2,3,1]) # using a simple string\r
test_order(('name',), [2,4,3,1])\r
test_order(('language', 'pages'), [3,2,1,4])\r
+ # using a simple string (for convinience as well as querystring passing\r
+ test_order('-pages', [4,2,3,1])\r
+ test_order('language,pages', [3,2,1,4])\r
+\r
+ # [bug] test alternative order formats if passed to constructor\r
+ BookTable([], 'language,-pages')\r
\r
# test invalid order instructions\r
books.order_by = 'xyz'\r
assert not books.order_by\r
test_order(('language', 'pages'), [1,3,2,4]) # as if: 'pages'\r
\r
+def test_for_templates():\r
+ class BookTable(tables.Table):\r
+ id = tables.Column()\r
+ name = tables.Column()\r
+ books = BookTable([\r
+ {'id': 1, 'name': 'Foo: Bar'},\r
+ ])\r
+\r
+ # cast to a string we get a value ready to be passed to the querystring\r
+ books.order_by = ('name',)\r
+ assert str(books.order_by) == 'name'\r
+ books.order_by = ('name', '-id')\r
+ assert str(books.order_by) == 'name,-id'\r
\r
test_declaration()\r
test_basic()\r
-test_sort()
\ No newline at end of file
+test_sort()\r
+test_for_templates()\r
+\r
+\r
+"""\r
+<table>\r
+<tr>\r
+ {% for column in book.columns %}\r
+ <th><a href="{{ column.name }}">{{ column }}</a></th\r
+ <th><a href="{% set_url_param "sort" column.name }}">{{ column }}</a></th\r
+ {% endfor %}\r
+</tr>\r
+{% for row in book %}\r
+ <tr>\r
+ {% for value in row %}\r
+ <td>{{ value }]</td>\r
+ {% endfor %}\r
+ </tr>\r
+{% endfor %}\r
+</table>\r
+\r
+OR:\r
+\r
+<table>\r
+{% for row in book %}\r
+ <tr>\r
+ {% if book.columns.name.visible %}\r
+ <td>{{ row.name }]</td>\r
+ {% endif %}\r
+ {% if book.columns.score.visible %}\r
+ <td>{{ row.score }]</td>\r
+ {% endif %}\r
+ </tr>\r
+{% endfor %}\r
+</table>\r
+\r
+\r
+"""
\ No newline at end of file