Как я могу отфильтровать родителей, у которых есть хотя бы один активный ребенок в модели отношений родитель-ребенок

У меня есть модель "Категория", которая имеет несколько отношений родитель/ребенок.

class Category(models.Model):
    pass

class CategoryParent(models.Model):
    parent_category = models.ForeignKey("Category", related_name="children", on_delete=models.CASCADE)
    child_category = models.ForeignKey("Category", related_name="parents", on_delete=models.CASCADE)
    is_active = models.BooleanField(verbose_name=_("Is active?"), default=True)


class CategoryExlusion(models.Model):
    browser = models.ForeignKey("Browser", on_delete=models.CASCADE)
    country = models.ForeignKey("Country", on_delete=models.CASCADE)
    category = models.ForeignKey("Category", on_delete=models.CASCADE)

Я хочу вывести список всех категорий верхнего уровня, у которых есть хотя бы одна активная дочерняя категория. Если в списке CategoryExlusion есть запись для категории, браузера и страны запрашиваемого пользователя, это означает, что данная категория не активна для этого пользователя. Я могу получить браузер и страну пользователя с помощью request.user.browser и request.user.country.

Самым сложным вопросом, с которым я сталкиваюсь, является возможность того, что у моего ребенка будут дети.

Как вы отметили, это может быть трудно осуществить с помощью django-orm из-за древовидной структуры. Я думаю, что лучше всего будет использовать Raw SQL запросы Django для выполнения Recursive Query. С помощью рекурсивного запроса вы можете выбрать всех потомков (детей, внуков, внучек и так далее) определенной категории верхнего уровня. Затем можно отфильтровать всех детей, которые находятся в списке исключений или неактивны. Если остается хотя бы один потомок, категория верхнего уровня остается в наборе запросов.

Я не тестировал это, но думаю, что рекурсивный запрос для конкретной категории верхнего уровня будет выглядеть примерно так:


team = Category.objects.raw('''
    WITH RECURSIVE descendents(id, is_active, child_category, parent_category) AS (
          SELECT id, is_active, child_category, parent_category 
          FROM category
          JOIN category_parent as children on category_parent.parent_category = category.id
          WHERE id = <id of the top level category>
        UNION ALL
          SELECT id, is_active, child_category, parent_category
          FROM descendents 
          JOIN parent_category as children on children.parent_category = descendents.id
        )
    SELECT * FROM descendents
''')

Разбивка запроса


SELECT id, is_active, child_category, parent_category 
          FROM category
          JOIN category_parent as children on category_parent.parent_category = category.id
          WHERE id = <id of the top level category>

Это нерекурсивный термин, выбирающий прямых детей категории верхнего уровня


UNION ALL
          SELECT id, is_active, child_category, parent_category
          FROM descendents 
          JOIN parent_category as children on children.parent_category = descendents.id

Это рекурсивный термин, самоссылающийся на нерекурсивный термин, который будет выбирать следующее поколение детей. Итерация будет выполняться до тех пор, пока в дереве не останется ни одного ребенка.


Вы можете использовать этот рекурсивный запрос для каждой категории верхнего уровня, используя Subquery от Django. Однако на данном этапе вам лучше сделать это на языке python, выполнив один рекурсивный запрос для каждой категории верхнего уровня. В зависимости от вашей базы данных и количества категорий, вы можете столкнуться с проблемами производительности из-за переписки с базой данных

Вернуться на верх