From: Michael Elsdörfer Date: Thu, 19 Jun 2008 18:38:00 +0000 (+0000) Subject: option to cause exceptions to be raised for invalid columns X-Git-Tag: 0.2~61 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=d34bf1e619d944c9ce58561f79234979ade7eba4;p=django-tables2.git option to cause exceptions to be raised for invalid columns --- diff --git a/README b/README index 2a8c628..2b7ec37 100644 --- a/README +++ b/README @@ -232,6 +232,35 @@ for that particular field will be in reverse order. Random ordering is currently not supported. +Error handling +-------------- + +Passing incoming query string values from the request directly to the +table constructor is a common thing to do. However, such data can easily +be invalid, be it that a user manually modified it, or someone put up a +broken link. In those cases, you usually would not want to raise an +exception (nor be notified by Django's error notification mechanism) - +there is nothing you could do anyway. + +Because of this, such errors will by default be silently ignored. For +example, if one out of three columns in an "order_by" is invalid, the other +two will still be applied: + + table.order_by = ('name', 'totallynotacolumn', '-date) + assert table.order_by = ('name', '-date) + +This ensures that the following table will be created regardless of the +string in "sort. + + table = MyTable(data, order_by=request.GET.get('sort')) + +However, if you want, you can disable this behaviour and have an exception +raised instead, using: + + import django_tables + django_tables.options.IGNORE_INVALID_OPTIONS = False + + Template Utilities ------------------ diff --git a/django_tables/tables.py b/django_tables/tables.py index aa09803..da6c504 100644 --- a/django_tables/tables.py +++ b/django_tables/tables.py @@ -4,7 +4,7 @@ from django.utils.encoding import StrAndUnicode from django.utils.text import capfirst from columns import Column -__all__ = ('BaseTable', 'Table') +__all__ = ('BaseTable', 'Table', 'options') def sort_table(data, order_by): """Sort a list of dicts according to the fieldnames in the @@ -89,6 +89,15 @@ class OrderByTuple(tuple, StrAndUnicode): def __unicode__(self): return ",".join(self) +# A common use case is passing incoming query values directly into the +# table constructor - data that can easily be invalid, say if manually +# modified by a user. So by default, such errors will be silently +# ignored. Set the option below to False if you want an exceptions to be +# raised instead. +class DefaultOptions(object): + IGNORE_INVALID_OPTIONS = True +options = DefaultOptions() + class BaseTable(object): def __init__(self, data, order_by=None): """Create a new table instance with the iterable ``data``. @@ -193,12 +202,17 @@ class BaseTable(object): if self._snapshot is not None: self._snapshot = None # accept both string and tuple instructions - self._order_by = (isinstance(value, basestring) \ + order_by = (isinstance(value, basestring) \ and [value.split(',')] \ or [value])[0] # validate, remove all invalid order instructions - self._order_by = OrderByTuple([o for o in self._order_by - if self._validate_column_name((o[:1]=='-' and [o[1:]] or [o])[0], "order_by")]) + validated_order_by = [] + for o in order_by: + if self._validate_column_name((o[:1]=='-' and [o[1:]] or [o])[0], "order_by"): + validated_order_by.append(o) + elif not options.IGNORE_INVALID_OPTIONS: + raise ValueError('Column name %s is invalid.' % o) + self._order_by = OrderByTuple(validated_order_by) order_by = property(lambda s: s._order_by, _set_order_by) def __unicode__(self): diff --git a/tests/test_basic.py b/tests/test_basic.py index 08dd015..086decc 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -3,6 +3,7 @@ This includes the core, as well as static data, non-model tables. """ +from py.test import raises import django_tables as tables def test_declaration(): @@ -85,6 +86,15 @@ def test_basic(): # TODO: row cache currently not used #assert id(list(books.rows)[0]) == id(list(books.rows)[0]) + # optionally, exceptions can be raised when input is invalid + tables.options.IGNORE_INVALID_OPTIONS = False + raises(Exception, "books.order_by = '-name,made-up-column'") + raises(Exception, "books.order_by = ('made-up-column',)") + # when a column name is overwritten, the original won't work anymore + raises(Exception, "books.order_by = 'c'") + # reset for future tests + tables.options.IGNORE_INVALID_OPTIONS = True + def test_sort(): class BookTable(tables.Table): id = tables.Column() @@ -111,6 +121,7 @@ def test_sort(): # using a simple string (for convinience as well as querystring passing test_order('-num_pages', [4,2,3,1]) test_order('language,num_pages', [3,2,1,4]) + # TODO: test that unrewritte name has no effect # [bug] test alternative order formats if passed to constructor BookTable([], 'language,-num_pages') diff --git a/tests/test_models.py b/tests/test_models.py index d04dc19..542d300 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -148,9 +148,9 @@ def test_sort(): test_order(('population',), [1,4,3,2]) test_order(('-population',), [2,3,4,1]) test_order(('name',), [1,3,2,4]) - # test sorting by a "rewritten" column name - countries.order_by = 'domain,tld' - countries.order_by == ('domain',) + # test sorting with a "rewritten" column name + countries.order_by = 'domain,tld' # "tld" would be invalid... + countries.order_by == ('domain',) # ...and is therefore removed test_order(('-domain',), [4,3,2,1]) # test multiple order instructions; note: one row is missing a "system" # value, but has a default set; however, that has no effect on sorting. @@ -173,4 +173,4 @@ def test_pagination(): # TODO: pagination # TODO: support function column sources both for modeltables (methods on model) and static tables (functions in dict) # TODO: manual base columns change -> update() call (add as example in docstr here) -> rebuild snapshot: is row cache, column cache etc. reset? -# TODO: throw an exception on invalid order_by \ No newline at end of file +# TODO: support relationship spanning columns (we could generate select_related() automatically) \ No newline at end of file