From 601d014a60329265bf80c2940514a2e532dd87f7 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michael=20Elsd=C3=B6rfer?= 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 }} +{% for row in book %} + + {% for value in row %} + + {% endfor %} + +{% endfor %} +
{{ column }}
{{ value }]
+ +OR: + + +{% for row in book %} + + {% if book.columns.name.visible %} + + {% endif %} + {% if book.columns.score.visible %} + + {% endif %} + +{% endfor %} +
{{ row.name }]{{ row.score }]
+ + +""" \ No newline at end of file -- 2.26.2