Better usage of cache for groups and clubs related operations (#634)

* Better usage of cache for group retrieval

* Cache clearing on object deletion or update

* replace signals by save and delete override

* add is_anonymous check in is_owned_by

Add in many is_owned_by(self, user) methods that user is not anonymous. Since many of those functions do db queries, this should reduce a little bit the load of the db.

* Stricter usage of User.is_in_group

Constrain the parameters that can be passed to the function to make sure only a str or an int can be used. Also force to explicitly specify if the group id or the group name is used.

* write test and correct bugs

* remove forgotten populate commands

* Correct test
This commit is contained in:
thomas girod
2023-05-02 12:36:59 +02:00
committed by GitHub
parent 96dede5077
commit ef968f3673
50 changed files with 1315 additions and 699 deletions

View File

@ -15,13 +15,18 @@
#
import os
from datetime import timedelta
from django.core.cache import cache
from django.test import Client, TestCase
from django.urls import reverse
from django.core.management import call_command
from django.utils.timezone import now
from core.models import User, Group, Page
from club.models import Membership
from core.models import User, Group, Page, AnonymousUser
from core.markdown import markdown
from sith import settings
"""
to run these tests :
@ -457,3 +462,150 @@ class FileHandlingTest(TestCase):
)
self.assertTrue(response.status_code == 200)
self.assertTrue("ls</a>" in str(response.content))
class UserIsInGroupTest(TestCase):
"""
Test that the User.is_in_group() and AnonymousUser.is_in_group()
work as intended
"""
@classmethod
def setUpTestData(cls):
from club.models import Club
cls.root_group = Group.objects.get(name="Root")
cls.public = Group.objects.get(name="Public")
cls.subscribers = Group.objects.get(name="Subscribers")
cls.old_subscribers = Group.objects.get(name="Old subscribers")
cls.accounting_admin = Group.objects.get(name="Accounting admin")
cls.com_admin = Group.objects.get(name="Communication admin")
cls.counter_admin = Group.objects.get(name="Counter admin")
cls.banned_alcohol = Group.objects.get(name="Banned from buying alcohol")
cls.banned_counters = Group.objects.get(name="Banned from counters")
cls.banned_subscription = Group.objects.get(name="Banned to subscribe")
cls.sas_admin = Group.objects.get(name="SAS admin")
cls.club = Club.objects.create(
name="Fake Club",
unix_name="fake-club",
address="Fake address",
)
cls.main_club = Club.objects.get(id=1)
def setUp(self) -> None:
self.toto = User.objects.create(
username="toto", first_name="a", last_name="b", email="a.b@toto.fr"
)
self.skia = User.objects.get(username="skia")
def assert_in_public_group(self, user):
self.assertTrue(user.is_in_group(pk=self.public.id))
self.assertTrue(user.is_in_group(name=self.public.name))
def assert_in_club_metagroups(self, user, club):
meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board))
self.assertFalse(user.is_in_group(name=meta_groups_members))
def assert_only_in_public_group(self, user):
self.assert_in_public_group(user)
for group in (
self.root_group,
self.banned_counters,
self.accounting_admin,
self.sas_admin,
self.subscribers,
self.old_subscribers,
):
self.assertFalse(user.is_in_group(pk=group.pk))
self.assertFalse(user.is_in_group(name=group.name))
meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board))
self.assertFalse(user.is_in_group(name=meta_groups_members))
def test_anonymous_user(self):
"""
Test that anonymous users are only in the public group
"""
user = AnonymousUser()
self.assert_only_in_public_group(user)
def test_not_subscribed_user(self):
"""
Test that users who never subscribed are only in the public group
"""
self.assert_only_in_public_group(self.toto)
def test_wrong_parameter_fail(self):
"""
Test that when neither the pk nor the name argument is given,
the function raises a ValueError
"""
with self.assertRaises(ValueError):
self.toto.is_in_group()
def test_number_queries(self):
"""
Test that the number of db queries is stable
and that less queries are made when making a new call
"""
# make sure Skia is in at least one group
self.skia.groups.add(Group.objects.first().pk)
skia_groups = self.skia.groups.all()
group_in = skia_groups.first()
cache.clear()
# Test when the user is in the group
with self.assertNumQueries(2):
self.skia.is_in_group(pk=group_in.id)
with self.assertNumQueries(0):
self.skia.is_in_group(pk=group_in.id)
ids = skia_groups.values_list("pk", flat=True)
group_not_in = Group.objects.exclude(pk__in=ids).first()
cache.clear()
# Test when the user is not in the group
with self.assertNumQueries(2):
self.skia.is_in_group(pk=group_not_in.id)
with self.assertNumQueries(0):
self.skia.is_in_group(pk=group_not_in.id)
def test_cache_properly_cleared_membership(self):
"""
Test that when the membership of a user end,
the cache is properly invalidated
"""
membership = Membership.objects.create(
club=self.club, user=self.toto, end_date=None
)
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
cache.clear()
self.assertTrue(self.toto.is_in_group(name=meta_groups_members))
self.assertEqual(
membership, cache.get(f"membership_{self.club.id}_{self.toto.id}")
)
membership.end_date = now() - timedelta(minutes=5)
membership.save()
cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}")
self.assertEqual(cached_membership, "not_member")
self.assertFalse(self.toto.is_in_group(name=meta_groups_members))
def test_cache_properly_cleared_group(self):
"""
Test that when a user is removed from a group,
the is_in_group_method return False when calling it again
"""
self.toto.groups.add(self.com_admin.pk)
self.assertTrue(self.toto.is_in_group(pk=self.com_admin.pk))
self.toto.groups.remove(self.com_admin.pk)
self.assertFalse(self.toto.is_in_group(pk=self.com_admin.pk))
def test_not_existing_group(self):
"""
Test that searching for a not existing group
returns False
"""
self.assertFalse(self.skia.is_in_group(name="This doesn't exist"))