2

I want to build with django-mptt a hierarchy of vegetables classes (Fruit -> Berries -> Strawberry -> Douglas Strawberry) being the last level the variety. Retailer stocks are often related to the leaf level, the variety (Golden Apple, Douglas Strawberry...), but consumers are used to ask about upper level (Apple, Pear, Orange, Lettuce...). So a useful query wold be "select all the retailers having a stock product of 'Strawberry' or any children.

Let me illustrate it with some example code:

class Vegetable(MPTTModel) :
    name = TextField(...)
    group = TreeForeignKey('self',
        related_name='subgroups',
        null=True, default=None)
    class MPTTMeta:
        parent_attr = 'group'

    def getRetailers(self) :
        # Here the missing code

class Retailer(Model) :
    name = TextField(...)
    vegetables = ManyToMany(Vegetable, null=True)

fruits = Vegetable(name='Fruit')
pears = Vegetable(name='Pear', group=fruits)
apples = Vegetable(name='Apple', group=fruits)
goldenApples = Vegetable(name='Golden Apple', group=apples)
royalApples = Vegetable(name='Royal Apple', group=apples)
for o in fruits, pears, apples, goldenApples, royalApples : o.save()

toni = Retailer(name='Toni')
pere = Retailer(name='Pere')
benet = Retailer(name='Benet')
mall = Retailer(name='CityMall')
for o in toni, pere, benet, mall : o.save()

toni.vegetables.add(pears)
pere.vegetables.add(goldenApple)
pere.vegetables.add(royalApple)
benet.vegetables.add(goldenApple)
mall.vegetables.add(apples)

# The following query set should return Pere, Benet and CityMall but not Toni.
Vegetable.get(name='Apple').getRetailers()

So how should I build such a query with the Django API?

vokimon
  • 1,602
  • 1
  • 12
  • 9
  • 1
    unhelpful introvert programmer comment: well *actually* a strawberry isn't a berry. :D – Hamish Sep 10 '14 at 22:22
  • Raspberry is not a true berry but it is comercialized as such. Likewise tomatoes and aubergine are true fruits but they are not comercialized as such. – vokimon Sep 11 '14 at 08:18
  • I know, it was a bad joke. Hopefully my real answer helped :) – Hamish Sep 13 '14 at 07:38

1 Answers1

2

You can get a query set of all MPTTModel descendants with get_descendants:

Creates a QuerySet containing descendants of this model instance, in tree order.

If include_self is True, the QuerySet will also include this model instance.

So for a particular vegetable:

# get retailers where...
retailers = Retailers.objects.filter(
    # ...one or more of it's vegetables are in the list of
    # all things descendant of 'vegetable', including itself.
    vegetables__in=vegetable.get_descendants(include_self=True)
).distinct()
Hamish
  • 22,860
  • 8
  • 53
  • 67
  • Cool, it works! I just had to add .distinct() to the query in order to remove duplications, for example if a retailer has three different kinds of apples. vegetables__in=vegetable.get_descendants(include_self=True).distinct() – vokimon Sep 14 '14 at 04:05
  • There is a syntax error on the code of my previous comment: `retailers = retailers.objects.filter(vegetables__in=vegetable.get_descendants(include_self=True)).distinct()` – vokimon Sep 14 '14 at 11:04