actions: Move part into zerver.lib.streams.

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2022-04-14 14:42:50 -07:00
parent 6168c0110a
commit a29f1b39da
12 changed files with 148 additions and 150 deletions

View File

@ -379,7 +379,6 @@ python_rules = RuleList(
# This one in check_message is kinda terrible, since it's # This one in check_message is kinda terrible, since it's
# how most instances are written, but better to exclude something than nothing # how most instances are written, but better to exclude something than nothing
("zerver/lib/actions.py", "stream = get_stream(stream_name, realm)"), ("zerver/lib/actions.py", "stream = get_stream(stream_name, realm)"),
("zerver/lib/actions.py", 'return get_stream("signups", realm)'),
}, },
"description": "Please use access_stream_by_*() to fetch Stream objects", "description": "Please use access_stream_by_*() to fetch Stream objects",
}, },

View File

@ -26,7 +26,7 @@ import orjson
from django.conf import settings from django.conf import settings
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import IntegrityError, connection, transaction from django.db import IntegrityError, connection, transaction
from django.db.models import Exists, F, OuterRef, Q from django.db.models import F
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from django.utils.html import escape from django.utils.html import escape
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
@ -125,7 +125,6 @@ from zerver.lib.stream_subscription import (
get_active_subscriptions_for_stream_id, get_active_subscriptions_for_stream_id,
get_bulk_stream_subscriber_info, get_bulk_stream_subscriber_info,
get_stream_subscriptions_for_user, get_stream_subscriptions_for_user,
get_subscribed_stream_ids_for_user,
get_subscriptions_for_send_message, get_subscriptions_for_send_message,
get_used_colors_for_user_ids, get_used_colors_for_user_ids,
num_subscribers_for_stream_id, num_subscribers_for_stream_id,
@ -138,10 +137,11 @@ from zerver.lib.streams import (
access_stream_for_send_message, access_stream_for_send_message,
can_access_stream_user_ids, can_access_stream_user_ids,
check_stream_access_based_on_stream_post_policy, check_stream_access_based_on_stream_post_policy,
create_stream_if_needed, ensure_stream,
get_default_value_for_history_public_to_subscribers, get_default_value_for_history_public_to_subscribers,
get_occupied_streams,
get_signups_stream,
get_stream_permission_policy_name, get_stream_permission_policy_name,
get_web_public_streams_queryset,
render_stream_description, render_stream_description,
send_stream_creation_event, send_stream_creation_event,
subscribed_to_stream, subscribed_to_stream,
@ -279,11 +279,6 @@ def subscriber_info(user_id: int) -> Dict[str, Any]:
return {"id": user_id, "flags": ["read"]} return {"id": user_id, "flags": ["read"]}
def get_signups_stream(realm: Realm) -> Stream:
# This one-liner helps us work around a lint rule.
return get_stream("signups", realm)
def send_message_to_signup_notification_stream( def send_message_to_signup_notification_stream(
sender: UserProfile, realm: Realm, message: str, topic_name: str = _("signups") sender: UserProfile, realm: Realm, message: str, topic_name: str = _("signups")
) -> None: ) -> None:
@ -2442,23 +2437,6 @@ def do_remove_reaction(
notify_reaction_update(user_profile, message, reaction, "remove") notify_reaction_update(user_profile, message, reaction, "remove")
def ensure_stream(
realm: Realm,
stream_name: str,
invite_only: bool = False,
stream_description: str = "",
*,
acting_user: Optional[UserProfile],
) -> Stream:
return create_stream_if_needed(
realm,
stream_name,
invite_only=invite_only,
stream_description=stream_description,
acting_user=acting_user,
)[0]
def already_sent_mirrored_message_id(message: Message) -> Optional[int]: def already_sent_mirrored_message_id(message: Message) -> Optional[int]:
if message.recipient.type == Recipient.HUDDLE: if message.recipient.type == Recipient.HUDDLE:
# For huddle messages, we use a 10-second window because the # For huddle messages, we use a 10-second window because the
@ -7015,107 +6993,6 @@ def do_remove_realm_domain(
transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id))) transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id)))
def get_occupied_streams(realm: Realm) -> QuerySet:
# TODO: Make a generic stub for QuerySet
"""Get streams with subscribers"""
exists_expression = Exists(
Subscription.objects.filter(
active=True,
is_user_active=True,
user_profile__realm=realm,
recipient_id=OuterRef("recipient_id"),
),
)
occupied_streams = (
Stream.objects.filter(realm=realm, deactivated=False)
.annotate(occupied=exists_expression)
.filter(occupied=True)
)
return occupied_streams
def get_web_public_streams(realm: Realm) -> List[Dict[str, Any]]: # nocoverage
query = get_web_public_streams_queryset(realm)
streams = Stream.get_client_data(query)
return streams
def do_get_streams(
user_profile: UserProfile,
include_public: bool = True,
include_web_public: bool = False,
include_subscribed: bool = True,
include_all_active: bool = False,
include_default: bool = False,
include_owner_subscribed: bool = False,
) -> List[Dict[str, Any]]:
# This function is only used by API clients now.
if include_all_active and not user_profile.is_realm_admin:
raise JsonableError(_("User not authorized for this query"))
include_public = include_public and user_profile.can_access_public_streams()
# Start out with all active streams in the realm.
query = Stream.objects.filter(realm=user_profile.realm, deactivated=False)
if include_all_active:
streams = Stream.get_client_data(query)
else:
# We construct a query as the or (|) of the various sources
# this user requested streams from.
query_filter: Optional[Q] = None
def add_filter_option(option: Q) -> None:
nonlocal query_filter
if query_filter is None:
query_filter = option
else:
query_filter |= option
if include_subscribed:
subscribed_stream_ids = get_subscribed_stream_ids_for_user(user_profile)
recipient_check = Q(id__in=set(subscribed_stream_ids))
add_filter_option(recipient_check)
if include_public:
invite_only_check = Q(invite_only=False)
add_filter_option(invite_only_check)
if include_web_public:
# This should match get_web_public_streams_queryset
web_public_check = Q(
is_web_public=True,
invite_only=False,
history_public_to_subscribers=True,
deactivated=False,
)
add_filter_option(web_public_check)
if include_owner_subscribed and user_profile.is_bot:
bot_owner = user_profile.bot_owner
assert bot_owner is not None
owner_stream_ids = get_subscribed_stream_ids_for_user(bot_owner)
owner_subscribed_check = Q(id__in=set(owner_stream_ids))
add_filter_option(owner_subscribed_check)
if query_filter is not None:
query = query.filter(query_filter)
streams = Stream.get_client_data(query)
else:
# Don't bother going to the database with no valid sources
streams = []
streams.sort(key=lambda elt: elt["name"])
if include_default:
is_default = {}
default_streams = get_default_streams_for_realm(user_profile.realm_id)
for default_stream in default_streams:
is_default[default_stream.id] = True
for stream in streams:
stream["is_default"] = is_default.get(stream["stream_id"], False)
return streams
def notify_attachment_update( def notify_attachment_update(
user_profile: UserProfile, op: str, attachment_dict: Dict[str, Any] user_profile: UserProfile, op: str, attachment_dict: Dict[str, Any]
) -> None: ) -> None:

View File

@ -13,12 +13,7 @@ from zerver.actions.default_streams import (
get_default_streams_for_realm, get_default_streams_for_realm,
streams_to_dicts_sorted, streams_to_dicts_sorted,
) )
from zerver.lib.actions import ( from zerver.lib.actions import gather_subscriptions_helper, get_owned_bot_dicts
do_get_streams,
gather_subscriptions_helper,
get_owned_bot_dicts,
get_web_public_streams,
)
from zerver.lib.alert_words import user_alert_words from zerver.lib.alert_words import user_alert_words
from zerver.lib.avatar import avatar_url from zerver.lib.avatar import avatar_url
from zerver.lib.bot_config import load_bot_config_template from zerver.lib.bot_config import load_bot_config_template
@ -46,6 +41,7 @@ from zerver.lib.realm_logo import get_realm_logo_source, get_realm_logo_url
from zerver.lib.soft_deactivation import reactivate_user_if_soft_deactivated from zerver.lib.soft_deactivation import reactivate_user_if_soft_deactivated
from zerver.lib.sounds import get_available_notification_sounds from zerver.lib.sounds import get_available_notification_sounds
from zerver.lib.stream_subscription import handle_stream_notifications_compatibility from zerver.lib.stream_subscription import handle_stream_notifications_compatibility
from zerver.lib.streams import do_get_streams, get_web_public_streams
from zerver.lib.subscription_info import get_web_public_subs from zerver.lib.subscription_info import get_web_public_subs
from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.timestamp import datetime_to_timestamp
from zerver.lib.timezone import canonicalize_timezone from zerver.lib.timezone import canonicalize_timezone

View File

@ -1,18 +1,23 @@
from typing import Collection, List, Optional, Set, Tuple, Union from typing import Any, Collection, Dict, List, Optional, Set, Tuple, Union
from django.db import transaction from django.db import transaction
from django.db.models import Exists, OuterRef, Q
from django.db.models.query import QuerySet from django.db.models.query 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 django.utils.translation import gettext as _
from typing_extensions import TypedDict from typing_extensions import TypedDict
from zerver.actions.default_streams import get_default_streams_for_realm
from zerver.lib.exceptions import ( from zerver.lib.exceptions import (
JsonableError, JsonableError,
OrganizationOwnerRequired, OrganizationOwnerRequired,
StreamAdministratorRequired, StreamAdministratorRequired,
) )
from zerver.lib.markdown import markdown_convert from zerver.lib.markdown import markdown_convert
from zerver.lib.stream_subscription import get_active_subscriptions_for_stream_id from zerver.lib.stream_subscription import (
get_active_subscriptions_for_stream_id,
get_subscribed_stream_ids_for_user,
)
from zerver.lib.string_validation import check_stream_name from zerver.lib.string_validation import check_stream_name
from zerver.models import ( from zerver.models import (
DefaultStreamGroup, DefaultStreamGroup,
@ -738,3 +743,126 @@ def get_stream_by_narrow_operand_access_unchecked(operand: Union[str, int], real
if isinstance(operand, str): if isinstance(operand, str):
return get_stream(operand, realm) return get_stream(operand, realm)
return get_stream_by_id_in_realm(operand, realm) return get_stream_by_id_in_realm(operand, realm)
def get_signups_stream(realm: Realm) -> Stream:
# This one-liner helps us work around a lint rule.
return get_stream("signups", realm)
def ensure_stream(
realm: Realm,
stream_name: str,
invite_only: bool = False,
stream_description: str = "",
*,
acting_user: Optional[UserProfile],
) -> Stream:
return create_stream_if_needed(
realm,
stream_name,
invite_only=invite_only,
stream_description=stream_description,
acting_user=acting_user,
)[0]
def get_occupied_streams(realm: Realm) -> QuerySet:
# TODO: Make a generic stub for QuerySet
"""Get streams with subscribers"""
exists_expression = Exists(
Subscription.objects.filter(
active=True,
is_user_active=True,
user_profile__realm=realm,
recipient_id=OuterRef("recipient_id"),
),
)
occupied_streams = (
Stream.objects.filter(realm=realm, deactivated=False)
.annotate(occupied=exists_expression)
.filter(occupied=True)
)
return occupied_streams
def get_web_public_streams(realm: Realm) -> List[Dict[str, Any]]: # nocoverage
query = get_web_public_streams_queryset(realm)
streams = Stream.get_client_data(query)
return streams
def do_get_streams(
user_profile: UserProfile,
include_public: bool = True,
include_web_public: bool = False,
include_subscribed: bool = True,
include_all_active: bool = False,
include_default: bool = False,
include_owner_subscribed: bool = False,
) -> List[Dict[str, Any]]:
# This function is only used by API clients now.
if include_all_active and not user_profile.is_realm_admin:
raise JsonableError(_("User not authorized for this query"))
include_public = include_public and user_profile.can_access_public_streams()
# Start out with all active streams in the realm.
query = Stream.objects.filter(realm=user_profile.realm, deactivated=False)
if include_all_active:
streams = Stream.get_client_data(query)
else:
# We construct a query as the or (|) of the various sources
# this user requested streams from.
query_filter: Optional[Q] = None
def add_filter_option(option: Q) -> None:
nonlocal query_filter
if query_filter is None:
query_filter = option
else:
query_filter |= option
if include_subscribed:
subscribed_stream_ids = get_subscribed_stream_ids_for_user(user_profile)
recipient_check = Q(id__in=set(subscribed_stream_ids))
add_filter_option(recipient_check)
if include_public:
invite_only_check = Q(invite_only=False)
add_filter_option(invite_only_check)
if include_web_public:
# This should match get_web_public_streams_queryset
web_public_check = Q(
is_web_public=True,
invite_only=False,
history_public_to_subscribers=True,
deactivated=False,
)
add_filter_option(web_public_check)
if include_owner_subscribed and user_profile.is_bot:
bot_owner = user_profile.bot_owner
assert bot_owner is not None
owner_stream_ids = get_subscribed_stream_ids_for_user(bot_owner)
owner_subscribed_check = Q(id__in=set(owner_stream_ids))
add_filter_option(owner_subscribed_check)
if query_filter is not None:
query = query.filter(query_filter)
streams = Stream.get_client_data(query)
else:
# Don't bother going to the database with no valid sources
streams = []
streams.sort(key=lambda elt: elt["name"])
if include_default:
is_default = {}
default_streams = get_default_streams_for_realm(user_profile.realm_id)
for default_stream in default_streams:
is_default[default_stream.id] = True
for stream in streams:
stream["is_default"] = is_default.get(stream["stream_id"], False)
return streams

View File

@ -2,8 +2,9 @@ from typing import Any
from django.core.management.base import CommandParser from django.core.management.base import CommandParser
from zerver.lib.actions import bulk_add_subscriptions, ensure_stream from zerver.lib.actions import bulk_add_subscriptions
from zerver.lib.management import ZulipBaseCommand from zerver.lib.management import ZulipBaseCommand
from zerver.lib.streams import ensure_stream
class Command(ZulipBaseCommand): class Command(ZulipBaseCommand):

View File

@ -1,8 +1,8 @@
from argparse import ArgumentParser from argparse import ArgumentParser
from typing import Any from typing import Any
from zerver.lib.actions import ensure_stream
from zerver.lib.management import ZulipBaseCommand from zerver.lib.management import ZulipBaseCommand
from zerver.lib.streams import ensure_stream
from zerver.models import DefaultStreamGroup from zerver.models import DefaultStreamGroup

View File

@ -51,7 +51,6 @@ from zerver.lib.actions import (
do_reactivate_realm, do_reactivate_realm,
do_reactivate_user, do_reactivate_user,
do_set_realm_property, do_set_realm_property,
ensure_stream,
) )
from zerver.lib.avatar import avatar_url from zerver.lib.avatar import avatar_url
from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.avatar_hash import user_avatar_path
@ -66,6 +65,7 @@ from zerver.lib.initial_password import initial_password
from zerver.lib.mobile_auth_otp import otp_decrypt_api_key from zerver.lib.mobile_auth_otp import otp_decrypt_api_key
from zerver.lib.rate_limiter import add_ratelimit_rule, remove_ratelimit_rule from zerver.lib.rate_limiter import add_ratelimit_rule, remove_ratelimit_rule
from zerver.lib.storage import static_path from zerver.lib.storage import static_path
from zerver.lib.streams import ensure_stream
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import ( from zerver.lib.test_helpers import (
create_s3_buckets, create_s3_buckets,

View File

@ -11,12 +11,7 @@ import orjson
from django.conf import settings from django.conf import settings
from django.http import HttpResponse from django.http import HttpResponse
from zerver.lib.actions import ( from zerver.lib.actions import do_change_stream_post_policy, do_deactivate_realm, do_deactivate_user
do_change_stream_post_policy,
do_deactivate_realm,
do_deactivate_user,
ensure_stream,
)
from zerver.lib.email_mirror import ( from zerver.lib.email_mirror import (
create_missed_message_address, create_missed_message_address,
filter_footer, filter_footer,
@ -37,6 +32,7 @@ from zerver.lib.email_mirror_helpers import (
) )
from zerver.lib.email_notifications import convert_html_to_markdown from zerver.lib.email_notifications import convert_html_to_markdown
from zerver.lib.send_email import FromAddress from zerver.lib.send_email import FromAddress
from zerver.lib.streams import ensure_stream
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import mock_queue_publish, most_recent_message, most_recent_usermessage from zerver.lib.test_helpers import mock_queue_publish, most_recent_message, most_recent_usermessage
from zerver.models import ( from zerver.models import (

View File

@ -34,9 +34,7 @@ from zerver.lib.actions import (
do_create_realm, do_create_realm,
do_deactivate_stream, do_deactivate_stream,
do_deactivate_user, do_deactivate_user,
do_get_streams,
do_set_realm_property, do_set_realm_property,
ensure_stream,
gather_subscriptions, gather_subscriptions,
gather_subscriptions_helper, gather_subscriptions_helper,
get_topic_messages, get_topic_messages,
@ -63,6 +61,8 @@ from zerver.lib.streams import (
can_access_stream_user_ids, can_access_stream_user_ids,
create_stream_if_needed, create_stream_if_needed,
create_streams_if_needed, create_streams_if_needed,
do_get_streams,
ensure_stream,
filter_stream_authorization, filter_stream_authorization,
list_to_streams, list_to_streams,
) )

View File

@ -6,7 +6,8 @@ import orjson
from django.utils.timezone import now as timezone_now from django.utils.timezone import now as timezone_now
from zerver.actions.user_groups import promote_new_full_members from zerver.actions.user_groups import promote_new_full_members
from zerver.lib.actions import do_set_realm_property, ensure_stream from zerver.lib.actions import do_set_realm_property
from zerver.lib.streams import ensure_stream
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.test_helpers import most_recent_usermessage from zerver.lib.test_helpers import most_recent_usermessage
from zerver.lib.user_groups import ( from zerver.lib.user_groups import (

View File

@ -38,7 +38,6 @@ from zerver.lib.actions import (
do_change_subscription_property, do_change_subscription_property,
do_deactivate_stream, do_deactivate_stream,
do_delete_messages, do_delete_messages,
do_get_streams,
do_rename_stream, do_rename_stream,
do_send_messages, do_send_messages,
gather_subscriptions, gather_subscriptions,
@ -64,6 +63,7 @@ from zerver.lib.streams import (
access_stream_for_delete_or_update, access_stream_for_delete_or_update,
access_web_public_stream, access_web_public_stream,
check_stream_name_available, check_stream_name_available,
do_get_streams,
filter_stream_authorization, filter_stream_authorization,
get_stream_permission_policy_name, get_stream_permission_policy_name,
list_to_streams, list_to_streams,

View File

@ -8,10 +8,10 @@ from zerver.lib.actions import (
do_change_avatar_fields, do_change_avatar_fields,
do_create_user, do_create_user,
do_send_messages, do_send_messages,
ensure_stream,
internal_prep_stream_message, internal_prep_stream_message,
) )
from zerver.lib.emoji import emoji_name_to_emoji_code from zerver.lib.emoji import emoji_name_to_emoji_code
from zerver.lib.streams import ensure_stream
from zerver.lib.upload import upload_avatar_image from zerver.lib.upload import upload_avatar_image
from zerver.models import Message, UserProfile, get_realm from zerver.models import Message, UserProfile, get_realm