From 52759764a1a27f1921e9ddee89cd3e25df46a4b7 Mon Sep 17 00:00:00 2001 From: imperosol Date: Fri, 20 Feb 2026 18:46:37 +0100 Subject: [PATCH] feat: `User.all_groups` --- core/auth/mixins.py | 2 +- core/models.py | 29 +++++++++++++++++------------ core/tests/test_core.py | 8 ++++---- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/core/auth/mixins.py b/core/auth/mixins.py index 917200ed..b8d8ee10 100644 --- a/core/auth/mixins.py +++ b/core/auth/mixins.py @@ -308,5 +308,5 @@ class PermissionOrClubBoardRequiredMixin(PermissionRequiredMixin): if super().has_permission(): return True return self.club is not None and any( - g.id == self.club.board_group_id for g in self.request.user.cached_groups + g.id == self.club.board_group_id for g in self.request.user.all_groups ) diff --git a/core/models.py b/core/models.py index 27744775..a5ae84a3 100644 --- a/core/models.py +++ b/core/models.py @@ -356,23 +356,30 @@ class User(AbstractUser): ) if group_id is None: return False - if group_id == settings.SITH_GROUP_SUBSCRIBERS_ID: - return self.is_subscribed - if group_id == settings.SITH_GROUP_ROOT_ID: - return self.is_root - return any(g.id == group_id for g in self.cached_groups) + return any(g.id == group_id for g in self.all_groups) @cached_property - def cached_groups(self) -> list[Group]: + def all_groups(self) -> list[Group]: """Get the list of groups this user is in.""" - return list(self.groups.all()) + additional_groups = [] + if self.is_subscribed: + additional_groups.append(settings.SITH_GROUP_SUBSCRIBERS_ID) + if self.is_superuser: + additional_groups.append(settings.SITH_GROUP_ROOT_ID) + qs = self.groups.all() + if additional_groups: + # This is somewhat counter-intuitive, but this query runs way faster with + # a UNION rather than a OR (in average, 0.25ms vs 14ms). + # For the why, cf. https://dba.stackexchange.com/questions/293836/why-is-an-or-statement-slower-than-union + qs = qs.union(Group.objects.filter(id__in=additional_groups)) + return list(qs) @cached_property def is_root(self) -> bool: if self.is_superuser: return True root_id = settings.SITH_GROUP_ROOT_ID - return any(g.id == root_id for g in self.cached_groups) + return any(g.id == root_id for g in self.all_groups) @cached_property def is_board_member(self) -> bool: @@ -1099,9 +1106,7 @@ class PageQuerySet(models.QuerySet): return self.filter(view_groups=settings.SITH_GROUP_PUBLIC_ID) if user.has_perm("core.view_page"): return self.all() - groups_ids = [g.id for g in user.cached_groups] - if user.is_subscribed: - groups_ids.append(settings.SITH_GROUP_SUBSCRIBERS_ID) + groups_ids = [g.id for g in user.all_groups] return self.filter(view_groups__in=groups_ids) @@ -1376,7 +1381,7 @@ class PageRev(models.Model): return self.page.can_be_edited_by(user) def is_owned_by(self, user: User) -> bool: - return any(g.id == self.page.owner_group_id for g in user.cached_groups) + return any(g.id == self.page.owner_group_id for g in user.all_groups) def similarity_ratio(self, text: str) -> float: """Similarity ratio between this revision's content and the given text. diff --git a/core/tests/test_core.py b/core/tests/test_core.py index 631e5b51..f6dc8570 100644 --- a/core/tests/test_core.py +++ b/core/tests/test_core.py @@ -418,16 +418,16 @@ class TestUserIsInGroup(TestCase): group_in = baker.make(Group) self.public_user.groups.add(group_in) - # clear the cached property `User.cached_groups` - self.public_user.__dict__.pop("cached_groups", None) + # clear the cached property `User.all_groups` + self.public_user.__dict__.pop("all_groups", None) # Test when the user is in the group - with self.assertNumQueries(1): + with self.assertNumQueries(2): self.public_user.is_in_group(pk=group_in.id) with self.assertNumQueries(0): self.public_user.is_in_group(pk=group_in.id) group_not_in = baker.make(Group) - self.public_user.__dict__.pop("cached_groups", None) + self.public_user.__dict__.pop("all_groups", None) # Test when the user is not in the group with self.assertNumQueries(1): self.public_user.is_in_group(pk=group_not_in.id)