mirror of
https://github.com/zulip/zulip.git
synced 2025-11-06 15:03:34 +00:00
user_topics: Refactor add_topic_mute.
In order to support different types of topic visibility policies, this renames 'add_topic_mute' to 'set_user_topic_visibility_policy_in_database' and refactors it to accept a parameter 'visibility_policy'. Create a corresponding UserTopic row for any visibility policy, not just muting topics. When a UserTopic row for (user_profile, stream, topic, recipient_id) exists already, it updates the row with the new visibility_policy. In the event of a duplicate request, raises a JsonableError. i.e., new_visibility_policy == existing_visibility_policy. There is an increase in the database query count in the message-edit code path. Reason: Earlier, 'add_topic_mute' used 'bulk_create' which either creates or raises IntegrityError -- 1 query. Now, 'set_user_topic_visibility_policy' uses get_or_create -- 2 queries in the case of creating new row. We can't use the previous approach, because now we have to handle the case of updating the visibility_policy too. Also, using bulk_* for a single row is not the correct way. Co-authored-by: Kartik Srivastava <kaushiksri0908@gmail.com> Co-authored-by: Prakhar Pratyush <prakhar841301@gmail.com>
This commit is contained in:
committed by
Tim Abbott
parent
e9580f8c5a
commit
f844cb6dad
@@ -6,7 +6,11 @@ from django.utils.translation import gettext as _
|
|||||||
|
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.user_topics import add_topic_mute, get_topic_mutes, remove_topic_mute
|
from zerver.lib.user_topics import (
|
||||||
|
get_topic_mutes,
|
||||||
|
remove_topic_mute,
|
||||||
|
set_user_topic_visibility_policy_in_database,
|
||||||
|
)
|
||||||
from zerver.models import Stream, UserProfile, UserTopic
|
from zerver.models import Stream, UserProfile, UserTopic
|
||||||
from zerver.tornado.django_api import send_event
|
from zerver.tornado.django_api import send_event
|
||||||
|
|
||||||
@@ -31,12 +35,13 @@ def do_set_user_topic_visibility_policy(
|
|||||||
raise JsonableError(_("Topic is not muted"))
|
raise JsonableError(_("Topic is not muted"))
|
||||||
else:
|
else:
|
||||||
assert stream.recipient_id is not None
|
assert stream.recipient_id is not None
|
||||||
add_topic_mute(
|
set_user_topic_visibility_policy_in_database(
|
||||||
user_profile,
|
user_profile,
|
||||||
stream.id,
|
stream.id,
|
||||||
stream.recipient_id,
|
|
||||||
topic,
|
topic,
|
||||||
last_updated,
|
visibility_policy=visibility_policy,
|
||||||
|
recipient_id=stream.recipient_id,
|
||||||
|
last_updated=last_updated,
|
||||||
ignore_duplicate=ignore_duplicate,
|
ignore_duplicate=ignore_duplicate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from typing import Callable, List, Optional, Tuple, TypedDict
|
from typing import Callable, Dict, List, Optional, Tuple, TypedDict
|
||||||
|
|
||||||
|
from django.db import transaction
|
||||||
from django.db.models import QuerySet
|
from django.db.models import QuerySet
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from sqlalchemy.sql import ClauseElement, and_, column, not_, or_
|
from sqlalchemy.sql import ClauseElement, and_, column, not_, or_
|
||||||
from sqlalchemy.types import Integer
|
from sqlalchemy.types import Integer
|
||||||
|
|
||||||
|
from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.topic import topic_match_sa
|
from zerver.lib.topic import topic_match_sa
|
||||||
from zerver.lib.types import UserTopicDict
|
from zerver.lib.types import UserTopicDict
|
||||||
@@ -95,39 +98,62 @@ def set_topic_mutes(
|
|||||||
recipient_id = stream.recipient_id
|
recipient_id = stream.recipient_id
|
||||||
assert recipient_id is not None
|
assert recipient_id is not None
|
||||||
|
|
||||||
add_topic_mute(
|
set_user_topic_visibility_policy_in_database(
|
||||||
user_profile=user_profile,
|
user_profile=user_profile,
|
||||||
stream_id=stream.id,
|
stream_id=stream.id,
|
||||||
recipient_id=recipient_id,
|
recipient_id=recipient_id,
|
||||||
topic_name=topic_name,
|
topic_name=topic_name,
|
||||||
date_muted=date_muted,
|
visibility_policy=UserTopic.MUTED,
|
||||||
|
last_updated=date_muted,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_topic_mute(
|
@transaction.atomic(savepoint=False)
|
||||||
|
def set_user_topic_visibility_policy_in_database(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
stream_id: int,
|
stream_id: int,
|
||||||
recipient_id: int,
|
|
||||||
topic_name: str,
|
topic_name: str,
|
||||||
date_muted: Optional[datetime.datetime] = None,
|
*,
|
||||||
|
visibility_policy: int,
|
||||||
|
recipient_id: int,
|
||||||
|
last_updated: Optional[datetime.datetime] = None,
|
||||||
ignore_duplicate: bool = False,
|
ignore_duplicate: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
if date_muted is None:
|
assert last_updated is not None
|
||||||
date_muted = timezone_now()
|
(row, created) = UserTopic.objects.get_or_create(
|
||||||
UserTopic.objects.bulk_create(
|
|
||||||
[
|
|
||||||
UserTopic(
|
|
||||||
user_profile=user_profile,
|
user_profile=user_profile,
|
||||||
stream_id=stream_id,
|
stream_id=stream_id,
|
||||||
|
topic_name__iexact=topic_name,
|
||||||
recipient_id=recipient_id,
|
recipient_id=recipient_id,
|
||||||
topic_name=topic_name,
|
defaults={
|
||||||
last_updated=date_muted,
|
"topic_name": topic_name,
|
||||||
visibility_policy=UserTopic.MUTED,
|
"last_updated": last_updated,
|
||||||
),
|
"visibility_policy": visibility_policy,
|
||||||
],
|
},
|
||||||
ignore_conflicts=ignore_duplicate,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
return
|
||||||
|
|
||||||
|
duplicate_request: bool = row.visibility_policy == visibility_policy
|
||||||
|
|
||||||
|
if duplicate_request and ignore_duplicate:
|
||||||
|
return
|
||||||
|
|
||||||
|
if duplicate_request and not ignore_duplicate:
|
||||||
|
visibility_policy_string: Dict[int, str] = {
|
||||||
|
1: "muted",
|
||||||
|
2: "unmuted",
|
||||||
|
3: "followed",
|
||||||
|
}
|
||||||
|
raise JsonableError(
|
||||||
|
_("Topic already {}").format(visibility_policy_string[visibility_policy])
|
||||||
|
)
|
||||||
|
# The request is to just 'update' the visibility policy of a topic
|
||||||
|
row.visibility_policy = visibility_policy
|
||||||
|
row.last_updated = last_updated
|
||||||
|
row.save(update_fields=["visibility_policy", "last_updated"])
|
||||||
|
|
||||||
|
|
||||||
def remove_topic_mute(user_profile: UserProfile, stream_id: int, topic_name: str) -> None:
|
def remove_topic_mute(user_profile: UserProfile, stream_id: int, topic_name: str) -> None:
|
||||||
row = UserTopic.objects.get(
|
row = UserTopic.objects.get(
|
||||||
|
|||||||
@@ -1456,6 +1456,17 @@ class NormalActionsTest(BaseAction):
|
|||||||
)
|
)
|
||||||
check_user_topic("events[0]", events[0])
|
check_user_topic("events[0]", events[0])
|
||||||
|
|
||||||
|
def test_unmuted_topics_events(self) -> None:
|
||||||
|
stream = get_stream("Denmark", self.user_profile.realm)
|
||||||
|
events = self.verify_action(
|
||||||
|
lambda: do_set_user_topic_visibility_policy(
|
||||||
|
self.user_profile, stream, "topic", visibility_policy=UserTopic.UNMUTED
|
||||||
|
),
|
||||||
|
num_events=2,
|
||||||
|
)
|
||||||
|
check_muted_topics("events[0]", events[0])
|
||||||
|
check_user_topic("events[1]", events[1])
|
||||||
|
|
||||||
def test_muted_users_events(self) -> None:
|
def test_muted_users_events(self) -> None:
|
||||||
muted_user = self.example_user("othello")
|
muted_user = self.example_user("othello")
|
||||||
events = self.verify_action(
|
events = self.verify_action(
|
||||||
|
|||||||
@@ -1338,7 +1338,7 @@ class EditMessageTest(EditMessageTestCase):
|
|||||||
|
|
||||||
# This code path adds 9 (1 + 4/user with muted topics) + 1 to
|
# This code path adds 9 (1 + 4/user with muted topics) + 1 to
|
||||||
# the number of database queries for moving a topic.
|
# the number of database queries for moving a topic.
|
||||||
with self.assert_database_query_count(19):
|
with self.assert_database_query_count(21):
|
||||||
check_update_message(
|
check_update_message(
|
||||||
user_profile=hamlet,
|
user_profile=hamlet,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
@@ -1422,7 +1422,7 @@ class EditMessageTest(EditMessageTestCase):
|
|||||||
set_topic_mutes(desdemona, muted_topics)
|
set_topic_mutes(desdemona, muted_topics)
|
||||||
set_topic_mutes(cordelia, muted_topics)
|
set_topic_mutes(cordelia, muted_topics)
|
||||||
|
|
||||||
with self.assert_database_query_count(30):
|
with self.assert_database_query_count(32):
|
||||||
check_update_message(
|
check_update_message(
|
||||||
user_profile=desdemona,
|
user_profile=desdemona,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
@@ -1453,7 +1453,7 @@ class EditMessageTest(EditMessageTestCase):
|
|||||||
set_topic_mutes(desdemona, muted_topics)
|
set_topic_mutes(desdemona, muted_topics)
|
||||||
set_topic_mutes(cordelia, muted_topics)
|
set_topic_mutes(cordelia, muted_topics)
|
||||||
|
|
||||||
with self.assert_database_query_count(32):
|
with self.assert_database_query_count(33):
|
||||||
check_update_message(
|
check_update_message(
|
||||||
user_profile=desdemona,
|
user_profile=desdemona,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
@@ -1486,7 +1486,7 @@ class EditMessageTest(EditMessageTestCase):
|
|||||||
set_topic_mutes(desdemona, muted_topics)
|
set_topic_mutes(desdemona, muted_topics)
|
||||||
set_topic_mutes(cordelia, muted_topics)
|
set_topic_mutes(cordelia, muted_topics)
|
||||||
|
|
||||||
with self.assert_database_query_count(30):
|
with self.assert_database_query_count(32):
|
||||||
check_update_message(
|
check_update_message(
|
||||||
user_profile=desdemona,
|
user_profile=desdemona,
|
||||||
message_id=message_id,
|
message_id=message_id,
|
||||||
|
|||||||
@@ -199,3 +199,47 @@ class MutedTopicsTests(ZulipTestCase):
|
|||||||
data = {"stream": stream.name, "stream_id": stream.id, "topic": "Verona3", "op": "remove"}
|
data = {"stream": stream.name, "stream_id": stream.id, "topic": "Verona3", "op": "remove"}
|
||||||
result = self.api_patch(user, url, data)
|
result = self.api_patch(user, url, data)
|
||||||
self.assert_json_error(result, "Please choose one: 'stream' or 'stream_id'.")
|
self.assert_json_error(result, "Please choose one: 'stream' or 'stream_id'.")
|
||||||
|
|
||||||
|
|
||||||
|
class UnmutedTopicsTests(ZulipTestCase):
|
||||||
|
def test_user_ids_unmuting_topic(self) -> None:
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
cordelia = self.example_user("cordelia")
|
||||||
|
realm = hamlet.realm
|
||||||
|
stream = get_stream("Verona", realm)
|
||||||
|
topic_name = "teST topic"
|
||||||
|
date_unmuted = datetime(2020, 1, 1, tzinfo=timezone.utc)
|
||||||
|
|
||||||
|
stream_topic_target = StreamTopicTarget(
|
||||||
|
stream_id=stream.id,
|
||||||
|
topic_name=topic_name,
|
||||||
|
)
|
||||||
|
|
||||||
|
user_ids = stream_topic_target.user_ids_with_visibility_policy(UserTopic.UNMUTED)
|
||||||
|
self.assertEqual(user_ids, set())
|
||||||
|
|
||||||
|
def set_topic_visibility_for_user(user: UserProfile, visibility_policy: int) -> None:
|
||||||
|
do_set_user_topic_visibility_policy(
|
||||||
|
user,
|
||||||
|
stream,
|
||||||
|
"test TOPIC",
|
||||||
|
visibility_policy=visibility_policy,
|
||||||
|
last_updated=date_unmuted,
|
||||||
|
)
|
||||||
|
|
||||||
|
set_topic_visibility_for_user(hamlet, UserTopic.UNMUTED)
|
||||||
|
set_topic_visibility_for_user(cordelia, UserTopic.MUTED)
|
||||||
|
user_ids = stream_topic_target.user_ids_with_visibility_policy(UserTopic.UNMUTED)
|
||||||
|
self.assertEqual(user_ids, {hamlet.id})
|
||||||
|
hamlet_date_unmuted = UserTopic.objects.filter(
|
||||||
|
user_profile=hamlet, visibility_policy=UserTopic.UNMUTED
|
||||||
|
)[0].last_updated
|
||||||
|
self.assertEqual(hamlet_date_unmuted, date_unmuted)
|
||||||
|
|
||||||
|
set_topic_visibility_for_user(cordelia, UserTopic.UNMUTED)
|
||||||
|
user_ids = stream_topic_target.user_ids_with_visibility_policy(UserTopic.UNMUTED)
|
||||||
|
self.assertEqual(user_ids, {hamlet.id, cordelia.id})
|
||||||
|
cordelia_date_unmuted = UserTopic.objects.filter(
|
||||||
|
user_profile=cordelia, visibility_policy=UserTopic.UNMUTED
|
||||||
|
)[0].last_updated
|
||||||
|
self.assertEqual(cordelia_date_unmuted, date_unmuted)
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.db import IntegrityError
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.utils.timezone import now as timezone_now
|
from django.utils.timezone import now as timezone_now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
|
from zerver.actions.user_topics import do_set_user_topic_visibility_policy
|
||||||
from zerver.lib.exceptions import JsonableError
|
|
||||||
from zerver.lib.request import REQ, has_request_variables
|
from zerver.lib.request import REQ, has_request_variables
|
||||||
from zerver.lib.response import json_success
|
from zerver.lib.response import json_success
|
||||||
from zerver.lib.streams import (
|
from zerver.lib.streams import (
|
||||||
@@ -34,7 +32,6 @@ def mute_topic(
|
|||||||
assert stream_id is not None
|
assert stream_id is not None
|
||||||
(stream, sub) = access_stream_by_id(user_profile, stream_id)
|
(stream, sub) = access_stream_by_id(user_profile, stream_id)
|
||||||
|
|
||||||
try:
|
|
||||||
do_set_user_topic_visibility_policy(
|
do_set_user_topic_visibility_policy(
|
||||||
user_profile,
|
user_profile,
|
||||||
stream,
|
stream,
|
||||||
@@ -42,8 +39,6 @@ def mute_topic(
|
|||||||
visibility_policy=UserTopic.MUTED,
|
visibility_policy=UserTopic.MUTED,
|
||||||
last_updated=date_muted,
|
last_updated=date_muted,
|
||||||
)
|
)
|
||||||
except IntegrityError:
|
|
||||||
raise JsonableError(_("Topic already muted"))
|
|
||||||
|
|
||||||
|
|
||||||
def unmute_topic(
|
def unmute_topic(
|
||||||
|
|||||||
Reference in New Issue
Block a user