views:

465

answers:

1

In a Django application, I want to display a multi-level (but fixed-depth) tree of model objects in table form. The HTML would look something like this:

<tr><td rowspan="2">group 1</td><td>item 1.1</td></tr>
<tr><td>item 1.2</td></tr>
<tr><td rowspan="3">group 2</td><td>item 2.1</td></tr>
<tr><td>item 2.2</td></tr>
<tr><td>item 2.3</td></tr>

The problem is filling in the rowspan. It's easy enough for two levels: you just use group.item_set.count. But let's say we have another level after items (eg subitem 1.1.1 etc.): then the rowspan of the "group 1" cell needs to be the count of all items plus the sum of all subitems within all items. It's easy enough to calculate using an aggregate, but I can't use aggregates in Django's template language.
That leaves a few options:

  • Add a count_all_subitems method to the Group model class. However, it seems wrong to put extra code in the model just because the view layer needs it.
  • Generate a dictionary or list of subitem counts in the view function and pass it as extra contex; however, Django's template language does not allow the key/index in a dictionary/list lookup to be a variable (eg, subitem_counts.group does subitem_counts['group'], and there's no way to make it do subitem_counts[group] AFAIK), so this requires a custom filter.
  • Write a custom filter or tag that calculates and outputs the subitem count (or the entire rowspan value) directly.

I know Django is designed to avoid having much logic in the templates, and instead recommends putting it in the view function, but it seems that I still need an extra piece (a custom template filter or tag) to actually use the results in a template. What's the preferred approach?

A: 

In the view you can use the aggregation "annotate" method to add a subitem_count value to the Group model queryset that is passed to the view without modifying the actual model definition.

A basic example is included in the answers to this SO question.

Indeed, that solves the problem for the 3-level case. Thanks! Is there a way to apply it to 4 or more levels, though? If I have Group>Item>Subitem>Subsubitem, I can annotate Groups, and from Subitems I can count the Subsubitems directly. But what about the Items? I'd need to make a dict of annotated querysets (for each Group, its Items annotated with the subsubitem count), and then I'm back to the second bullet point (having to make a custom filter to match each Group with its annotated Items qs). Or is there something else I'm missing?
LaC
Because I had a deep hierarchy (see previous comment), I had to add a special method to the model in the end. However, I'm accepting mherren's answer because it is still useful in many cases.
LaC