added template tags, fixed up with tests
authorMichael Elsdörfer <michael@elsdoerfer.info>
Sat, 14 Jun 2008 16:47:58 +0000 (16:47 +0000)
committerMichael Elsdörfer <michael@elsdoerfer.info>
Sat, 14 Jun 2008 16:47:58 +0000 (16:47 +0000)
README
django_tables/app/__init__.py [new file with mode: 0644]
django_tables/app/models.py [new file with mode: 0644]
django_tables/app/templatetags/__init__.py [new file with mode: 0644]
django_tables/app/templatetags/tables.py [new file with mode: 0644]
django_tables/tables.py
tests/test.py

diff --git a/README b/README
index 84d9a1b264e402ac549b60ac60d7034ba7cd2258..940885756d59746d4253b94492240d60bc8185ef 100644 (file)
--- a/README
+++ b/README
@@ -1,6 +1,22 @@
-===============\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
@@ -8,9 +24,27 @@ for that particular field will be in reverse order.
 \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
diff --git a/django_tables/app/__init__.py b/django_tables/app/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/django_tables/app/models.py b/django_tables/app/models.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/django_tables/app/templatetags/__init__.py b/django_tables/app/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/django_tables/app/templatetags/tables.py b/django_tables/app/templatetags/tables.py
new file mode 100644 (file)
index 0000000..2e60f8e
--- /dev/null
@@ -0,0 +1,71 @@
+"""\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
index 29247ed49f714741cc712581c16767cdc132c6ef..140a9dc9f3ee3da6442a6056fd81bf97282e3445 100644 (file)
@@ -29,16 +29,12 @@ class Row(object):
     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
@@ -90,6 +86,14 @@ class DeclarativeColumnsMetaclass(type):
 \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
@@ -133,12 +137,15 @@ class BaseTable(object):
     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
index a470cff86172eaddd3a6eacaf9b8dea540897fe5..29ee0dbf7f77ac187a42d00cee8d0c79b5d20b67 100644 (file)
@@ -1,3 +1,4 @@
+from django.core.paginator import Paginator\r
 import os, sys\r
 sys.path.append(os.path.join(os.path.dirname(__file__), '..'))\r
 \r
@@ -77,9 +78,14 @@ def test_sort():
     # 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
@@ -89,7 +95,57 @@ def test_sort():
     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