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.