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