cache: Flush realm caches after both pre_delete and post_delete signals.

Fixes #19468.
This commit is contained in:
Mateusz Mandera 2021-08-04 16:47:36 +02:00 committed by Tim Abbott
parent ea6929457c
commit f1d5f3904d
2 changed files with 25 additions and 4 deletions

View File

@ -29,7 +29,7 @@ from django.core.validators import MinLengthValidator, RegexValidator, URLValida
from django.db import models, transaction
from django.db.models import CASCADE, Manager, Q, Sum
from django.db.models.query import QuerySet
from django.db.models.signals import post_delete, post_save
from django.db.models.signals import post_delete, post_save, pre_delete
from django.utils.functional import Promise
from django.utils.timezone import now as timezone_now
from django.utils.translation import gettext as _
@ -849,14 +849,27 @@ class Realm(models.Model):
return self.is_zephyr_mirror_realm
def realm_post_delete_handler(*, instance: Realm, **kwargs: object) -> None:
post_save.connect(flush_realm, sender=Realm)
# We register realm cache flushing in a duplicate way to be run both
# pre_delete and post_delete on purpose:
# 1. pre_delete is needed because flush_realm wants to flush the UserProfile caches,
# and UserProfile objects are deleted via on_delete=CASCADE before the post_delete handler
# is called, which results in the `flush_realm` logic not having access to the details
# for the deleted users if called at that time.
# 2. post_delete is run as a precaution to reduce the risk of races where items might be
# added to the cache after the pre_delete handler but before the save.
# Note that it does not eliminate this risk, not least because it only flushes
# the realm cache, and not the user caches, for the reasons explained above.
def realm_pre_and_post_delete_handler(*, instance: Realm, **kwargs: object) -> None:
# This would be better as a functools.partial, but for some reason
# Django doesn't call it even when it's registered as a post_delete handler.
flush_realm(instance=instance, from_deletion=True)
post_save.connect(flush_realm, sender=Realm)
post_delete.connect(realm_post_delete_handler, sender=Realm)
pre_delete.connect(realm_pre_and_post_delete_handler, sender=Realm)
post_delete.connect(realm_pre_and_post_delete_handler, sender=Realm)
def get_realm(string_id: str) -> Realm:

View File

@ -186,6 +186,14 @@ class RealmTest(ZulipTestCase):
user = get_user_profile_by_id(hamlet_id)
self.assertTrue(user.realm.deactivated)
def test_do_change_realm_delete_clears_user_realm_cache(self) -> None:
hamlet_id = self.example_user("hamlet").id
get_user_profile_by_id(hamlet_id)
realm = get_realm("zulip")
realm.delete()
with self.assertRaises(UserProfile.DoesNotExist):
get_user_profile_by_id(hamlet_id)
def test_do_change_realm_subdomain_clears_user_realm_cache(self) -> None:
"""The main complicated thing about changing realm subdomains is
updating the cache, and we start by populating the cache for