mirror of
https://github.com/zulip/zulip.git
synced 2025-11-03 13:33:24 +00:00
mute user: Cache list of muter IDs.
This commit defines a new function `get_muting_users` which will return a list of IDs of users who have muted a given user. Whenever someone mutes/unmutes a user, the cache will be flushed, and subsequently when that user sends a message, the cache will be populated with the list of people who have muted them (maybe empty). This data is a good candidate for caching because- 1. The function will later be called from the message send codepath, and we try to minimize database queries there. 2. The entries will be pretty tiny. 3. The entries won't churn too much. An average user will send messages much more frequently than get muted/unmuted, and the first time penalty of hitting the db and populating the cache should ideally get amortized by avoiding several DB lookups on subsequent message sends. The actual code to call this function will be written in further commits.
This commit is contained in:
committed by
Tim Abbott
parent
9602aa1467
commit
b140c17441
@@ -534,6 +534,10 @@ def realm_user_dicts_cache_key(realm_id: int) -> str:
|
|||||||
return f"realm_user_dicts:{realm_id}"
|
return f"realm_user_dicts:{realm_id}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_muting_users_cache_key(muted_user: "UserProfile") -> str:
|
||||||
|
return f"muting_users_list:{muted_user.id}"
|
||||||
|
|
||||||
|
|
||||||
def get_realm_used_upload_space_cache_key(realm: "Realm") -> str:
|
def get_realm_used_upload_space_cache_key(realm: "Realm") -> str:
|
||||||
return f"realm_used_upload_space:{realm.id}"
|
return f"realm_used_upload_space:{realm.id}"
|
||||||
|
|
||||||
@@ -642,6 +646,11 @@ def flush_user_profile(sender: Any, **kwargs: Any) -> None:
|
|||||||
cache_delete(bot_dicts_in_realm_cache_key(user_profile.realm))
|
cache_delete(bot_dicts_in_realm_cache_key(user_profile.realm))
|
||||||
|
|
||||||
|
|
||||||
|
def flush_muting_users_cache(sender: Any, **kwargs: Any) -> None:
|
||||||
|
mute_object = kwargs["instance"]
|
||||||
|
cache_delete(get_muting_users_cache_key(mute_object.muted_user))
|
||||||
|
|
||||||
|
|
||||||
# Called by models.py to flush various caches whenever we save
|
# Called by models.py to flush various caches whenever we save
|
||||||
# a Realm object. The main tricky thing here is that Realm info is
|
# a Realm object. The main tricky thing here is that Realm info is
|
||||||
# generally cached indirectly through user_profile objects.
|
# generally cached indirectly through user_profile objects.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional, Set
|
||||||
|
|
||||||
|
from zerver.lib.cache import cache_with_key, get_muting_users_cache_key
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.models import MutedUser, UserProfile
|
from zerver.models import MutedUser, UserProfile
|
||||||
|
|
||||||
@@ -34,3 +35,18 @@ def get_mute_object(user_profile: UserProfile, muted_user: UserProfile) -> Optio
|
|||||||
return MutedUser.objects.get(user_profile=user_profile, muted_user=muted_user)
|
return MutedUser.objects.get(user_profile=user_profile, muted_user=muted_user)
|
||||||
except MutedUser.DoesNotExist:
|
except MutedUser.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@cache_with_key(get_muting_users_cache_key, timeout=3600 * 24 * 7)
|
||||||
|
def get_muting_users(muted_user: UserProfile) -> Set[int]:
|
||||||
|
"""
|
||||||
|
This is kind of the inverse of `get_user_mutes` above.
|
||||||
|
While `get_user_mutes` is mainly used for event system work,
|
||||||
|
this is used in the message send codepath, to get a list
|
||||||
|
of IDs of users who have muted a particular user.
|
||||||
|
The result will also include deactivated users.
|
||||||
|
"""
|
||||||
|
rows = MutedUser.objects.filter(
|
||||||
|
muted_user=muted_user,
|
||||||
|
).values("user_profile__id")
|
||||||
|
return {row["user_profile__id"] for row in rows}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ from zerver.lib.cache import (
|
|||||||
cache_set,
|
cache_set,
|
||||||
cache_with_key,
|
cache_with_key,
|
||||||
flush_message,
|
flush_message,
|
||||||
|
flush_muting_users_cache,
|
||||||
flush_realm,
|
flush_realm,
|
||||||
flush_stream,
|
flush_stream,
|
||||||
flush_submessage,
|
flush_submessage,
|
||||||
@@ -1916,6 +1917,10 @@ class MutedUser(models.Model):
|
|||||||
return f"<MutedUser: {self.user_profile.email} -> {self.muted_user.email}>"
|
return f"<MutedUser: {self.user_profile.email} -> {self.muted_user.email}>"
|
||||||
|
|
||||||
|
|
||||||
|
post_save.connect(flush_muting_users_cache, sender=MutedUser)
|
||||||
|
post_delete.connect(flush_muting_users_cache, sender=MutedUser)
|
||||||
|
|
||||||
|
|
||||||
class Client(models.Model):
|
class Client(models.Model):
|
||||||
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID")
|
id: int = models.AutoField(auto_created=True, primary_key=True, verbose_name="ID")
|
||||||
name: str = models.CharField(max_length=30, db_index=True, unique=True)
|
name: str = models.CharField(max_length=30, db_index=True, unique=True)
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ from unittest import mock
|
|||||||
|
|
||||||
import orjson
|
import orjson
|
||||||
|
|
||||||
|
from zerver.lib.cache import cache_get, get_muting_users_cache_key
|
||||||
from zerver.lib.test_classes import ZulipTestCase
|
from zerver.lib.test_classes import ZulipTestCase
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.user_mutes import get_mute_object, get_user_mutes
|
from zerver.lib.user_mutes import get_mute_object, get_muting_users, get_user_mutes
|
||||||
from zerver.models import RealmAuditLog
|
from zerver.models import RealmAuditLog
|
||||||
|
|
||||||
|
|
||||||
@@ -161,3 +162,26 @@ class MutedUsersTests(ZulipTestCase):
|
|||||||
orjson.dumps({"unmuted_user_id": cordelia.id}).decode(),
|
orjson.dumps({"unmuted_user_id": cordelia.id}).decode(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_get_muting_users(self) -> None:
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
self.login_user(hamlet)
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
|
||||||
|
self.assertEqual(None, cache_get(get_muting_users_cache_key(cordelia)))
|
||||||
|
self.assertEqual(set(), get_muting_users(cordelia))
|
||||||
|
self.assertEqual(set(), cache_get(get_muting_users_cache_key(cordelia))[0])
|
||||||
|
|
||||||
|
url = "/api/v1/users/me/muted_users/{}".format(cordelia.id)
|
||||||
|
result = self.api_post(hamlet, url)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
self.assertEqual(None, cache_get(get_muting_users_cache_key(cordelia)))
|
||||||
|
self.assertEqual({hamlet.id}, get_muting_users(cordelia))
|
||||||
|
self.assertEqual({hamlet.id}, cache_get(get_muting_users_cache_key(cordelia))[0])
|
||||||
|
|
||||||
|
url = "/api/v1/users/me/muted_users/{}".format(cordelia.id)
|
||||||
|
result = self.api_delete(hamlet, url)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
self.assertEqual(None, cache_get(get_muting_users_cache_key(cordelia)))
|
||||||
|
self.assertEqual(set(), get_muting_users(cordelia))
|
||||||
|
self.assertEqual(set(), cache_get(get_muting_users_cache_key(cordelia))[0])
|
||||||
|
|||||||
Reference in New Issue
Block a user