(tables) - Callables aren't sortable?

0 views
Skip to first unread message

isolationism

unread,
Sep 28, 2008, 6:18:45 PM9/28/08
to Django Apps
I'm still relatively new to django-tables, so forgive me if I'm
missing something here. In several cases in my application, I want to
use callables to display a value since the values built into the model
don't work (for example, wanting to pull "%s %s" % (self.first_name,
self.last_name) from the User object.

Passing a callable in the data argument of my tables.Column instance
seems to print as expected; I get a string with exactly what I expect.

However, the strange behaviour I'm seeing is that columns whose data
is based on callables doesn't seem to be sortable -- that is, I can
issue a sort command, but nothing happens to the resulting sort order,
and the is_ordered property for the column is False. I'm getting the
same behaviour consistently whenever a callable is returning a value
for multiple different fields. Is this expected behaviour (e.g. not
yet written), or am I missing something required to make the callable
sort properly in my model/callable?

Michael Elsdörfer

unread,
Sep 29, 2008, 2:19:06 AM9/29/08
to Django Apps
I unterstand you are using a ModelTable, in which case your most
likely hitting this limitation from the Readme:

ModelTables currently have some restrictions with respect to
ordering:
* Custom columns not based on a model field do not support
ordering,
regardless of the ``sortable`` property (it is ignored).

ModelTable ordering is currently based entirely on the underlying
Django ORM and the SQL behind it, i.e. queryset.order_by(). Callables
defined in Python won't work. Besides regular database columns, you're
only other other really is to use an .extra() query to insert a custom
column.

Unfortunately, this limitation is something that probably will never
go away either. Apart from SQLite, where it potentially could work, I
am not aware that any of the database backends do allow registering
custom functions from code, so the only other option is to do the
sorting, when callables are used, in Python itself. However, this is
obviously slow, and there are additional complications when multiple
sort fields are used, e.g. on real database column, and a callable.

Michael

isolationism

unread,
Sep 29, 2008, 8:19:08 PM9/29/08
to Django Apps
Michael, that makes perfect sense. I figured something of the kind was
the case; thank you for taking the time to point it out to me; I had
looked through the readme but must have missed that line.

I suppose the logical follow-up to that question is then if the
possibility might exist in a future version to sort on one value but
display an alternative (e.g. callable) value?

Michael Elsdörfer

unread,
Sep 30, 2008, 3:34:08 PM9/30/08
to Django Apps
> I suppose the logical follow-up to that question is then if the
> possibility might exist in a future version to sort on one value but
> display an alternative (e.g. callable) value?

That should already be possible: If "data" is a callable, it will be
used to display a cell. Just make sure that the column itself can be
sorted by Django, or it will simply be ignored (unless the global
IGNORE_INVALID_OPTION option is set to False, in which case an
exception will be raised)

For example:

class MyTable(tables.ModelTable):
sortable_django_field = tables.Column(data=display_func)

Or, with a field added via extra():

class MyTable(tables.ModelTable):
fullname = tables.Column(data=display_func)
qs = MyModel.objects.extra(select={'fullname': 'CONCAT(first_name,
last_name)'})
table = MyTable(qs)

Michael

isolationism

unread,
Oct 13, 2008, 10:25:35 AM10/13/08
to Django Apps
Michael,

Thanks for taking the time to reply -- sorry mine is so late, been a
busy couple weeks.

> class MyTable(tables.ModelTable):
> sortable_django_field = tables.Column(data=display_func)

class MyTable(tables.ModelTable):
priority = tables.Column(data='get_priority')

I am using a string for the callable, but I assume that was implied in
your example. I set django_tables.options.IGNORE_INVALID_OPTIONS on
your suggestion and now I do get an error when I sort on that column:

"Column name [colname] is invalid."

This happens whether I use an existing, sortable column name
("priority") or a new name ("pretty_priority"). Both display the
"pretty" value, but both display an error (with the [colname]
populated appropriately). Am I missing something from this first
example? I am having difficulty seeing how this could work.

> class MyTable(tables.ModelTable):
> fullname = tables.Column(data=display_func)
> qs = MyModel.objects.extra(select={'fullname': 'CONCAT(first_name,
> last_name)'})
> table = MyTable(qs)

I've never extended the model in this fashion before, but I can print
the new field out to the console from the view.

Again, the table renders fine -- right up until I try to sort on the
new column; I get "Column name [colname] is invalid" again. I've gone
through all the permutations I can think of but no matter what
happens, if a callable is set as the data argument in tables.Column,
it fails with the same error -- even if the column name listed as
being invalid exists.


isolationism

unread,
Oct 13, 2008, 11:31:04 AM10/13/08
to Django Apps
While working on another issue I realized that maybe the implied
string on the function _was_ the problem, and it turned out to be the
case -- I now have custom text working on a sortable row.

The problem was this: I wrote my callable function as part of the
Model, and was calling it using a string. This actually works really
well, and for "non-table-specific" rows makes a lot of sense. The only
problem is, the results aren't sortable (I don't know why).

Now, if I create a _separate_ function all off on its lonesome
somewhere that does the exact same thing, it works fine. Exact same
data, almost the exact same function -- only difference is I have to
access 'row.data' instead of 'self' to get at the fields.

I don't know if this is a design limitation or an oversight, but for
now I'm happy as at least I have a way to move forward. Can you chime
in with your thoughts, Michael? Is this a limitation of Django's Model
class and table callables will forever be separate from the Model
itself?

Michael Elsdörfer

unread,
Oct 13, 2008, 8:18:55 PM10/13/08
to Django Apps
I hinted previously that the "data" column option is unfortunately
slightly overloaded.

The original problem that needed solving was that one might have a
model field that should be exposed under a different, maybe more
"pretty", column name, e.g. "published_at" as "date".

The "name" option does exactly that:

published_at = Column(name="date")

"published_at" is now exposed as "date".

However, in a more complex scenario, one might want to expose a field
from a related model - say the "author" column is actually
"issue.writer.name". One would have to write:

issue__writer__name = Column(name="author")

Which is of course somewhat ugly. So I additionally added the "data"
attribute, which basically was supposed to work like "name" in
reverse:

author = Column(data="issue__writer__name")

Now "issue__writer__name" is exposed as "author".

The ability to make "data" a callable was added afterwards on top of
that. Of course, we now have the dilemma: Because ordering is based on
the Django ORM, and the Django ORM can only sort on model fields, not
arbitrary callables (whether they are model methods or not), table
columns that use a callable "data" could no longer be sortable. The
callable would still be used for display of the cell, but not for
ordering.

Now, you *should* be able to specify a callable both as a string AND a
function object. You mentioned that the latter works for you. The
former works for me as well:

class MyModel(models.Model):
...
def get_priority(self):
return "foo"

class MyTable(tables.Table):
priority = tables.Column(data='get_priority')

table = MyTable(MyModel.objects.all())
print table.rows[0]['priority']
# => prints "foo"


Ok, so here's where I think the confusion stems from:

I mentioned that once you make a columns "data" option a callable,
that column is no longer sortable. As a matter of fact, that is not
entirely true. There is an exception to this rule (see also
test_models.py:269+): If "data" is a callable, and the column
otherwise refers to a valid model field name, then that field name
will be used for ordernig, and the column actually remains sortable.
So for example:

author = table.Columns(data=get_author)

If "author" is a valid field of the model, the column can be sorted.
Otherwise, it can't.

Unfortunately though, that only works if you specify a function
object. If you specify the callable as a string, the column will be
unsortable in any case. Whether or not that should be otherwise could
potentially be argued, but I think it probably should. I opened a
ticket for the issue.

I hope I wasn't too confusing. The short version is: Callables should
work both given as a string and a callable object, but only in the
latter will the column be sortable.

Michael

Michael Elsdörfer

unread,
Oct 13, 2008, 8:24:16 PM10/13/08
to Django Apps
On Oct 14, 2:18 am, Michael Elsdörfer <elsdoer...@gmail.com> wrote:
> I opened a ticket for the issue.

For reference: https://bugs.launchpad.net/django-tables/+bug/282964
Reply all
Reply to author
Forward
0 new messages