Django-tables2: how to order_by accessor

3.9k views Asked by At

Say we have a column like:

num_member = tables.Column(accessor = 'members.count', verbose_name = 'number of members' )

When I tried to sort this in the template, it raises:

Field Error: Cannot resolve keyword u'count' into field

I read the document and it says we can use order_by by passing in some sort of accessor, but how exactly do we do this please?

2

There are 2 answers

2
ruddra On BEST ANSWER

For function like Model's property method, you can access it directly using accessor. For example:

Class MyModel(models.Model):
    data= models.CharField(max_length=255)

    @property
    def print_function(self):
       return 'hello world'

#Table class
class MyTable(tables.Table):
        data= tables.Column(accessor='print_function')

    class Meta:
        model = MyModel
        fields = ('data')

Using the above method, you can show different kinds of data in table using accessor:

Class SomeModel(models.Model):
    some_data= models.CharField(max_length=255)
    data= models.ManyToManyField(MyModel)

    @property
    def count_function(self):
       some_data= self.data.objects.count() #returns count of the objects
       return some_data

#Table class
class SomeTable(tables.Table):
    data= tables.Column(accessor='count_function')

    class Meta:
        model = SomeModel
        fields = ('data')

And accessor can be used for directly accessing related foreignkey model's field value like:

Class SomeModel(models.Model):
    somedata= models.ForeignKey(MyModel)


#Table class
class MyTable(tables.Table):
        data= tables.Column(accessor='somedata.data')

    class Meta:
        model = SomeModel
        fields = ('data')

EDIT

Lets give an example of how order_by can be used:

#Class Based view, I am subclassing ListView and SingleTableView here for example

class MyView(ListView, SingleTableView):
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['table'].order_by = '-last_updated' #last_updated is a datetimefield in model
        return context

In the above code, what I have done is that, I am changing the order of the table data in context which will later be rendered in template.

1
user2390182 On

Fairly old question, however, I have been confronted with the same problem today: I couldn't order my tables if the accessor was a property (or 0-argument-method) and not a model field.

After not finding anything in the docs and inspecting the source code, it turned out that tables2 will pass the ordering to the database if its data is a QuerySet, but otherwise it will do a Python list sort with an appropriate key:

# django_tables2/tables.py  -> class TableData

def order_by(self, aliases):
    # ...
    if hasattr(self, "queryset"):
        translate = lambda accessor: accessor.replace(Accessor.SEPARATOR, QUERYSET_ACCESSOR_SEPARATOR)
        if accessors:
            self.queryset = self.queryset.order_by(*(translate(a) for a in accessors))
    else:
        self.list.sort(key=OrderByTuple(accessors).key)

I assume that this can not be trivially solved by using a try-except instead of the if-else because an exception would only be raised once the queryset is evaluated which only happens later.

Solution: whenever your sort-parameter is not a model field, turn the QuerySet into a list before handing it to the table. For many cases in django, this will be as simple as overriding get_queryset:

def get_queryset(self):
    qs = super(ViewName, self).get_queryset()
    return list(qs)

This should work best if your accessor is a cached_property on the model of your table, e.g.:

from django.utils.functional import cached_property

@cached_property
def member_count(self):
    # do the heavy stuff here in the model
    return whatever

Then, in the table:

num_member = tables.Column(
    accessor='members_count', 
    verbose_name='number of members'
)