Is it possible to render a column for each of a many-to-many relationship with django-tables2?

It's simple enough to get all the many-to-many relationships of a particular row - but they're rendered in the same column.

Is it possible to have a column created for each one?

For example, with a many-to-many relationship between Child and Year, and data for each child/year pair, I'd want a column for each Year in the table:

Child       1998  1999  2000   
----------------------------        
Eldest       155   162   170
Youngest      53    61    72
Middlest      80    91   103

(many to many in django-tables2 is the closest I've seen to a question about this, and I think the accepted answer is wrong; it doesn't render a column for each relationship at all.)

I was able to work out a solution.

The approach

In the example below, I have a Child class, and Year object. I also have a Height class:

class Height(models.Model):
    # a Child's Height for each Year
    child = models.ForeignKey(Child)
    year = models.ForeignKey(Year)
    height = DecimalField()

To get a column for each year containing the child's height that year, you can supply extra_columns to the Table class, defining the extra columns you want.

The way to do this is through the get_table_kwargs() method of a table view.

You also need a Column subclass, with a render() method that can query the database to find the right value for the row/column (realising that I needed a new subclass was the key to this problem for me).

The solution

Start by defining a view and pointing it at the model that holds the data:

class ChildrenTableView(tables.SingleTableView):
    model = Children

    def get_table_kwargs(self):
        year_height_columns = [
            (year.name, YearColumn(attrs={"year": year}))
            for year in Year.objects.all()
        ]
        return {"extra_columns": (year_height_columns)}

For each Year, we want a YearColumn.

attrs={"year": year} is a convenient way to supply the Year object to the column for use later in a query. (Depending on your data, you might also want to add an empty_values=() to the definition, so that the column doesn't falsely think it's empty.)

YearColumn is a very simple subclass of Column:

import django_tables2 as tables


class YearColumn(tables.Column):

    def render(self, record):
        return record.height_set.get(year=self.attrs["year"]).height

Here, record is the child for the row, and height_set is all the heights that already have a foreign key to the child.

Вернуться на верх