diff --git a/aleksis/core/managers.py b/aleksis/core/managers.py index aed4cab72d4b0cd53d3544b20f0180a7087dfd2c..a3feacc6cf17731a6d6f38ca5ace42677ed230f1 100644 --- a/aleksis/core/managers.py +++ b/aleksis/core/managers.py @@ -7,6 +7,7 @@ from django.db.models import QuerySet from django.db.models.manager import Manager from calendarweek import CalendarWeek +from django_cte import CTEManager, CTEQuerySet from polymorphic.managers import PolymorphicManager @@ -84,7 +85,7 @@ class SchoolTermRelatedQuerySet(QuerySet): return None -class GroupManager(CurrentSiteManagerWithoutMigrations): +class GroupManager(CurrentSiteManagerWithoutMigrations, CTEManager): """Manager adding specific methods to groups.""" def get_queryset(self): @@ -92,7 +93,7 @@ class GroupManager(CurrentSiteManagerWithoutMigrations): return super().get_queryset().select_related("school_term") -class GroupQuerySet(SchoolTermRelatedQuerySet): +class GroupQuerySet(SchoolTermRelatedQuerySet, CTEQuerySet): pass diff --git a/aleksis/core/models.py b/aleksis/core/models.py index 73ffab18ab217e1698ccc4fbdea9b25c1de97dfb..ee50301394b0f12444bb06262b1bf17ec0a22d62 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -28,6 +28,7 @@ import jsonstore from cachalot.api import cachalot_disabled from cache_memoize import cache_memoize from django_celery_results.models import TaskResult +from django_cte import CTEQuerySet, With from dynamic_preferences.models import PerInstancePreferenceModel from invitations import signals from invitations.adapters import get_invitations_adapter @@ -486,6 +487,25 @@ class Group(SchoolTermRelatedExtensibleModel): return stats + @property + def parent_groups_recursive(self) -> CTEQuerySet: + """Get all parent groups recursively.""" + + def _make_cte(cte): + Through = self.parent_groups.through + return ( + Through.objects.values("to_group_id") + .filter(from_group=self) + .union(cte.join(Through, from_group=cte.col.to_group_id), all=True) + ) + + cte = With.recursive(_make_cte) + return cte.join(Group, id=cte.col.to_group_id).with_cte(cte) + + @property + def child_groups_recursive(self) -> CTEQuerySet: + """Get all child groups recursively.""" + def __str__(self) -> str: if self.school_term: return f"{self.name} ({self.short_name}) ({self.school_term})" diff --git a/pyproject.toml b/pyproject.toml index 357eff863631c377b33777678a28dba88fb81f0d..fd960153b97c4b078931eb9890d38af9cb746152 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -116,6 +116,7 @@ django-titofisto = "^0.2.0" haystack-redis = "^0.0.1" python-gnupg = "^0.4.7" sentry-sdk = {version = "^1.4.3", optional = true} +django-cte = "^1.1.5" [tool.poetry.extras] ldap = ["django-auth-ldap"]