I’m trying to figure out the best way to derive a nested menu from a set of non-nested models. Given a layout something like this:
class Beverage(models.Model):
country = models.ForeignKey(Country,null=True,blank=True)
region = models.ForeignKey(Region,null=True,blank=True)
subregion = models.ForeignKey(SubRegion,null=True,blank=True)
in_stock = models.BooleanField()
...
The resulting menu will be something like:
France
Region 1
Subregion 1
Subregion 2
Region 2
Subregion 3
Subregion 4
Spain
....
No country, region, or subregion should appear in the menu if there are no beverages in it that are not in stock. Because a subregion always belongs to a region and a region always belongs to a country, my initial approach was to nest the models themselves, and only put SubRegion on Beverage. Region and Country would then always be known by the subregion of the beverage. Unfortunately there are too many real-world exceptions to make this viable – wines with a region but not a subregion, etc. So I flattened the layout as above.
The question now is how to derive the menu from this model layout. It’s looking like a list of deeply nested querysets is going to be the way to go but that seems computationally expensive and complex code-wise. Is there a cleaner way?
After much fiddling, I believe I’ve found a working solution that uses very few LOC by building a set of nested dictionaries and lists. I wanted to send real objects to the template, not just strings (basically trying to stay as close as possible to the general queryset methodology as possible). The form of the generated dictionary is:
where each of country, region, and subregion is a real object, not a string. Here’s the business end (this is in a templatetag). Note that we check for available inventory in each iteration, only setting a dictionary or list item if something is in stock.
Dictionaries suit the needs perfectly except you lose the ability to sort, so I’ll have to figure out something for alphabetization later.
To iterate through dicts in the template:
Since we passed in objects rather than strings, I can now do URL reversals, get slugs etc. for each item at each level (removed in this example).