1

I have 2 models, Category and Products

class Product(Meta):
    categories = models.ManyToManyField(Category, related_name='products')

class Category(Meta):
    parent = models.ForeignKey('self', blank=True, null=True, verbose_name='parent category', on_delete=models.CASCADE)

When a user select a Category I need to show all the products in that category including all product of his children and grandchildren etc.

The Category has a FK to itself, so the number of depth levels theoretically is infinite so I need to extract all product from any category below parent depth.


I tried something recursive on the Model(traverse the tree, including parent):

 def get_descendants(self, tree=None):
        if tree is None:
            tree = []
        tree.append(self)
        for child in self.category_set.all():
            return self.get_descendants(child)
        return tree

and call in get_object

obj = super().get_object()
        Product.objects.filter(categories__in=obj.get_descendants())

I get the following error:

Product.objects.filter(categories__in=obj.get_descendants())

The tree is adding/appending the first category, parent, but gives the error after recursion call on the second append.

user3541631
  • 3,686
  • 8
  • 48
  • 115

1 Answers1

3

Doing this in the simplest way possible will be as follows:

category = Category.objects.get(id=1)
def get_children(category):
    all_children = []
    current_children = category.category_set.all()
    for child in current_children:
         grand_children = get_children(child)
         if grand_children:
             all_children.append(grand_children)

     return all_children

descendants = get_children(category)
Products.objects.filter(categories__in=descendants)

But doing this the 'right' way is very tricky. First you'll need to analyse data and realize the tradeoffs you want to make. This depends on how you want to store data see here for storing heirarchial data. You may have to optimize either for reads or for writes.


Another alternative would be to use something like django-mptt

oxalorg
  • 2,768
  • 1
  • 16
  • 27
  • thanks, so after I get the categories I chain with prefetch_related in queryset ? what about getting also the parent – user3541631 Dec 11 '17 at 18:38
  • All the descendants have already been fetched. I'm not sure where you'll chain `prefetch_related`. Instead you could maybe use all the categories to filter out products. Something similar to `Products.objects.filter(categories__in=descendants)` – oxalorg Dec 11 '17 at 18:42
  • where are stored, you don't need an array or a base case ? – user3541631 Dec 11 '17 at 18:50
  • I think there was issue in my logic. I've updated the answer. Could you give it a try? (Since I've not tested the code) – oxalorg Dec 11 '17 at 18:57
  • you add/append grandchildren; so if the last one in the tree doesn't have children will return empty, and you add nothing; i think child need to be added – user3541631 Dec 11 '17 at 20:10