diff --git a/zerver/actions/default_streams.py b/zerver/actions/default_streams.py new file mode 100644 index 0000000000..a8932c1227 --- /dev/null +++ b/zerver/actions/default_streams.py @@ -0,0 +1,191 @@ +from typing import Any, Dict, List + +from django.db import transaction +from django.utils.translation import gettext as _ + +from zerver.lib.exceptions import JsonableError +from zerver.models import ( + DefaultStream, + DefaultStreamGroup, + Realm, + Stream, + active_non_guest_user_ids, + get_default_stream_groups, +) +from zerver.tornado.django_api import send_event + + +def check_default_stream_group_name(group_name: str) -> None: + if group_name.strip() == "": + raise JsonableError(_("Invalid default stream group name '{}'").format(group_name)) + if len(group_name) > DefaultStreamGroup.MAX_NAME_LENGTH: + raise JsonableError( + _("Default stream group name too long (limit: {} characters)").format( + DefaultStreamGroup.MAX_NAME_LENGTH, + ) + ) + for i in group_name: + if ord(i) == 0: + raise JsonableError( + _("Default stream group name '{}' contains NULL (0x00) characters.").format( + group_name, + ) + ) + + +def lookup_default_stream_groups( + default_stream_group_names: List[str], realm: Realm +) -> List[DefaultStreamGroup]: + default_stream_groups = [] + for group_name in default_stream_group_names: + try: + default_stream_group = DefaultStreamGroup.objects.get(name=group_name, realm=realm) + except DefaultStreamGroup.DoesNotExist: + raise JsonableError(_("Invalid default stream group {}").format(group_name)) + default_stream_groups.append(default_stream_group) + return default_stream_groups + + +def notify_default_streams(realm: Realm) -> None: + event = dict( + type="default_streams", + default_streams=streams_to_dicts_sorted(get_default_streams_for_realm(realm.id)), + ) + transaction.on_commit(lambda: send_event(realm, event, active_non_guest_user_ids(realm.id))) + + +def notify_default_stream_groups(realm: Realm) -> None: + event = dict( + type="default_stream_groups", + default_stream_groups=default_stream_groups_to_dicts_sorted( + get_default_stream_groups(realm) + ), + ) + transaction.on_commit(lambda: send_event(realm, event, active_non_guest_user_ids(realm.id))) + + +def do_add_default_stream(stream: Stream) -> None: + realm_id = stream.realm_id + stream_id = stream.id + if not DefaultStream.objects.filter(realm_id=realm_id, stream_id=stream_id).exists(): + DefaultStream.objects.create(realm_id=realm_id, stream_id=stream_id) + notify_default_streams(stream.realm) + + +@transaction.atomic(savepoint=False) +def do_remove_default_stream(stream: Stream) -> None: + realm_id = stream.realm_id + stream_id = stream.id + DefaultStream.objects.filter(realm_id=realm_id, stream_id=stream_id).delete() + notify_default_streams(stream.realm) + + +def do_create_default_stream_group( + realm: Realm, group_name: str, description: str, streams: List[Stream] +) -> None: + default_streams = get_default_streams_for_realm(realm.id) + for stream in streams: + if stream in default_streams: + raise JsonableError( + _( + "'{stream_name}' is a default stream and cannot be added to '{group_name}'", + ).format(stream_name=stream.name, group_name=group_name) + ) + + check_default_stream_group_name(group_name) + (group, created) = DefaultStreamGroup.objects.get_or_create( + name=group_name, realm=realm, description=description + ) + if not created: + raise JsonableError( + _( + "Default stream group '{group_name}' already exists", + ).format(group_name=group_name) + ) + + group.streams.set(streams) + notify_default_stream_groups(realm) + + +def do_add_streams_to_default_stream_group( + realm: Realm, group: DefaultStreamGroup, streams: List[Stream] +) -> None: + default_streams = get_default_streams_for_realm(realm.id) + for stream in streams: + if stream in default_streams: + raise JsonableError( + _( + "'{stream_name}' is a default stream and cannot be added to '{group_name}'", + ).format(stream_name=stream.name, group_name=group.name) + ) + if stream in group.streams.all(): + raise JsonableError( + _( + "Stream '{stream_name}' is already present in default stream group '{group_name}'", + ).format(stream_name=stream.name, group_name=group.name) + ) + group.streams.add(stream) + + group.save() + notify_default_stream_groups(realm) + + +def do_remove_streams_from_default_stream_group( + realm: Realm, group: DefaultStreamGroup, streams: List[Stream] +) -> None: + for stream in streams: + if stream not in group.streams.all(): + raise JsonableError( + _( + "Stream '{stream_name}' is not present in default stream group '{group_name}'", + ).format(stream_name=stream.name, group_name=group.name) + ) + group.streams.remove(stream) + + group.save() + notify_default_stream_groups(realm) + + +def do_change_default_stream_group_name( + realm: Realm, group: DefaultStreamGroup, new_group_name: str +) -> None: + if group.name == new_group_name: + raise JsonableError( + _("This default stream group is already named '{}'").format(new_group_name) + ) + + if DefaultStreamGroup.objects.filter(name=new_group_name, realm=realm).exists(): + raise JsonableError(_("Default stream group '{}' already exists").format(new_group_name)) + + group.name = new_group_name + group.save() + notify_default_stream_groups(realm) + + +def do_change_default_stream_group_description( + realm: Realm, group: DefaultStreamGroup, new_description: str +) -> None: + group.description = new_description + group.save() + notify_default_stream_groups(realm) + + +def do_remove_default_stream_group(realm: Realm, group: DefaultStreamGroup) -> None: + group.delete() + notify_default_stream_groups(realm) + + +def get_default_streams_for_realm(realm_id: int) -> List[Stream]: + return [ + default.stream + for default in DefaultStream.objects.select_related().filter(realm_id=realm_id) + ] + + +# returns default streams in JSON serializable format +def streams_to_dicts_sorted(streams: List[Stream]) -> List[Dict[str, Any]]: + return sorted((stream.to_dict() for stream in streams), key=lambda elt: elt["name"]) + + +def default_stream_groups_to_dicts_sorted(groups: List[DefaultStreamGroup]) -> List[Dict[str, Any]]: + return sorted((group.to_dict() for group in groups), key=lambda elt: elt["name"]) diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 8004e4c839..67009016e3 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -48,6 +48,11 @@ from confirmation.models import ( create_confirmation_link, generate_key, ) +from zerver.actions.default_streams import ( + do_remove_default_stream, + do_remove_streams_from_default_stream_group, + get_default_streams_for_realm, +) from zerver.actions.user_groups import ( do_send_user_group_members_update_event, update_users_in_full_members_system_group, @@ -264,7 +269,6 @@ from zerver.models import ( get_bot_dicts_in_realm, get_bot_services, get_client, - get_default_stream_groups, get_fake_email_domain, get_huddle_user_ids, get_old_unclaimed_attachments, @@ -2928,24 +2932,6 @@ def check_update_message( return number_changed -def check_default_stream_group_name(group_name: str) -> None: - if group_name.strip() == "": - raise JsonableError(_("Invalid default stream group name '{}'").format(group_name)) - if len(group_name) > DefaultStreamGroup.MAX_NAME_LENGTH: - raise JsonableError( - _("Default stream group name too long (limit: {} characters)").format( - DefaultStreamGroup.MAX_NAME_LENGTH, - ) - ) - for i in group_name: - if ord(i) == 0: - raise JsonableError( - _("Default stream group name '{}' contains NULL (0x00) characters.").format( - group_name, - ) - ) - - def send_rate_limited_pm_notification_to_bot_owner( sender: UserProfile, realm: Realm, content: str ) -> None: @@ -5510,170 +5496,12 @@ def do_change_user_setting( Draft.objects.filter(user_profile=user_profile).delete() -def lookup_default_stream_groups( - default_stream_group_names: List[str], realm: Realm -) -> List[DefaultStreamGroup]: - default_stream_groups = [] - for group_name in default_stream_group_names: - try: - default_stream_group = DefaultStreamGroup.objects.get(name=group_name, realm=realm) - except DefaultStreamGroup.DoesNotExist: - raise JsonableError(_("Invalid default stream group {}").format(group_name)) - default_stream_groups.append(default_stream_group) - return default_stream_groups - - -def notify_default_streams(realm: Realm) -> None: - event = dict( - type="default_streams", - default_streams=streams_to_dicts_sorted(get_default_streams_for_realm(realm.id)), - ) - transaction.on_commit(lambda: send_event(realm, event, active_non_guest_user_ids(realm.id))) - - -def notify_default_stream_groups(realm: Realm) -> None: - event = dict( - type="default_stream_groups", - default_stream_groups=default_stream_groups_to_dicts_sorted( - get_default_stream_groups(realm) - ), - ) - transaction.on_commit(lambda: send_event(realm, event, active_non_guest_user_ids(realm.id))) - - -def do_add_default_stream(stream: Stream) -> None: - realm_id = stream.realm_id - stream_id = stream.id - if not DefaultStream.objects.filter(realm_id=realm_id, stream_id=stream_id).exists(): - DefaultStream.objects.create(realm_id=realm_id, stream_id=stream_id) - notify_default_streams(stream.realm) - - -@transaction.atomic(savepoint=False) -def do_remove_default_stream(stream: Stream) -> None: - realm_id = stream.realm_id - stream_id = stream.id - DefaultStream.objects.filter(realm_id=realm_id, stream_id=stream_id).delete() - notify_default_streams(stream.realm) - - -def do_create_default_stream_group( - realm: Realm, group_name: str, description: str, streams: List[Stream] -) -> None: - default_streams = get_default_streams_for_realm(realm.id) - for stream in streams: - if stream in default_streams: - raise JsonableError( - _( - "'{stream_name}' is a default stream and cannot be added to '{group_name}'", - ).format(stream_name=stream.name, group_name=group_name) - ) - - check_default_stream_group_name(group_name) - (group, created) = DefaultStreamGroup.objects.get_or_create( - name=group_name, realm=realm, description=description - ) - if not created: - raise JsonableError( - _( - "Default stream group '{group_name}' already exists", - ).format(group_name=group_name) - ) - - group.streams.set(streams) - notify_default_stream_groups(realm) - - -def do_add_streams_to_default_stream_group( - realm: Realm, group: DefaultStreamGroup, streams: List[Stream] -) -> None: - default_streams = get_default_streams_for_realm(realm.id) - for stream in streams: - if stream in default_streams: - raise JsonableError( - _( - "'{stream_name}' is a default stream and cannot be added to '{group_name}'", - ).format(stream_name=stream.name, group_name=group.name) - ) - if stream in group.streams.all(): - raise JsonableError( - _( - "Stream '{stream_name}' is already present in default stream group '{group_name}'", - ).format(stream_name=stream.name, group_name=group.name) - ) - group.streams.add(stream) - - group.save() - notify_default_stream_groups(realm) - - -def do_remove_streams_from_default_stream_group( - realm: Realm, group: DefaultStreamGroup, streams: List[Stream] -) -> None: - for stream in streams: - if stream not in group.streams.all(): - raise JsonableError( - _( - "Stream '{stream_name}' is not present in default stream group '{group_name}'", - ).format(stream_name=stream.name, group_name=group.name) - ) - group.streams.remove(stream) - - group.save() - notify_default_stream_groups(realm) - - -def do_change_default_stream_group_name( - realm: Realm, group: DefaultStreamGroup, new_group_name: str -) -> None: - if group.name == new_group_name: - raise JsonableError( - _("This default stream group is already named '{}'").format(new_group_name) - ) - - if DefaultStreamGroup.objects.filter(name=new_group_name, realm=realm).exists(): - raise JsonableError(_("Default stream group '{}' already exists").format(new_group_name)) - - group.name = new_group_name - group.save() - notify_default_stream_groups(realm) - - -def do_change_default_stream_group_description( - realm: Realm, group: DefaultStreamGroup, new_description: str -) -> None: - group.description = new_description - group.save() - notify_default_stream_groups(realm) - - -def do_remove_default_stream_group(realm: Realm, group: DefaultStreamGroup) -> None: - group.delete() - notify_default_stream_groups(realm) - - -def get_default_streams_for_realm(realm_id: int) -> List[Stream]: - return [ - default.stream - for default in DefaultStream.objects.select_related().filter(realm_id=realm_id) - ] - - def get_default_subs(user_profile: UserProfile) -> List[Stream]: # Right now default streams are realm-wide. This wrapper gives us flexibility # to some day further customize how we set up default streams for new users. return get_default_streams_for_realm(user_profile.realm_id) -# returns default streams in JSON serializable format -def streams_to_dicts_sorted(streams: List[Stream]) -> List[Dict[str, Any]]: - return sorted((stream.to_dict() for stream in streams), key=lambda elt: elt["name"]) - - -def default_stream_groups_to_dicts_sorted(groups: List[DefaultStreamGroup]) -> List[Dict[str, Any]]: - return sorted((group.to_dict() for group in groups), key=lambda elt: elt["name"]) - - def do_update_user_activity_interval( user_profile: UserProfile, log_time: datetime.datetime ) -> None: diff --git a/zerver/lib/events.py b/zerver/lib/events.py index b6f0dc4bca..1f4c526cad 100644 --- a/zerver/lib/events.py +++ b/zerver/lib/events.py @@ -8,14 +8,16 @@ from django.conf import settings from django.utils.translation import gettext as _ from version import API_FEATURE_LEVEL, ZULIP_MERGE_BASE, ZULIP_VERSION -from zerver.lib.actions import ( +from zerver.actions.default_streams import ( default_stream_groups_to_dicts_sorted, + get_default_streams_for_realm, + streams_to_dicts_sorted, +) +from zerver.lib.actions import ( do_get_streams, gather_subscriptions_helper, - get_default_streams_for_realm, get_owned_bot_dicts, get_web_public_streams, - streams_to_dicts_sorted, ) from zerver.lib.alert_words import user_alert_words from zerver.lib.avatar import avatar_url diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index b4e50145b6..f7c2b9275e 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -13,6 +13,17 @@ from unittest import mock import orjson from django.utils.timezone import now as timezone_now +from zerver.actions.default_streams import ( + do_add_default_stream, + do_add_streams_to_default_stream_group, + do_change_default_stream_group_description, + do_change_default_stream_group_name, + do_create_default_stream_group, + do_remove_default_stream, + do_remove_default_stream_group, + do_remove_streams_from_default_stream_group, + lookup_default_stream_groups, +) from zerver.actions.hotspots import do_mark_hotspot_as_read from zerver.actions.realm_linkifiers import ( do_add_linkifier, @@ -36,17 +47,13 @@ from zerver.lib.actions import ( bulk_remove_subscriptions, check_add_realm_emoji, do_add_alert_words, - do_add_default_stream, do_add_reaction, do_add_realm_domain, - do_add_streams_to_default_stream_group, do_change_avatar_fields, do_change_bot_owner, do_change_default_all_public_streams, do_change_default_events_register_stream, do_change_default_sending_stream, - do_change_default_stream_group_description, - do_change_default_stream_group_name, do_change_full_name, do_change_icon_source, do_change_logo_source, @@ -60,7 +67,6 @@ from zerver.lib.actions import ( do_change_user_delivery_email, do_change_user_role, do_change_user_setting, - do_create_default_stream_group, do_create_multiuse_invite_link, do_create_user, do_deactivate_realm, @@ -74,13 +80,10 @@ from zerver.lib.actions import ( do_reactivate_user, do_regenerate_api_key, do_remove_alert_words, - do_remove_default_stream, - do_remove_default_stream_group, do_remove_reaction, do_remove_realm_custom_profile_field, do_remove_realm_domain, do_remove_realm_emoji, - do_remove_streams_from_default_stream_group, do_rename_stream, do_revoke_multi_use_invite, do_revoke_user_invite, @@ -99,7 +102,6 @@ from zerver.lib.actions import ( do_update_user_custom_profile_data_if_changed, do_update_user_presence, do_update_user_status, - lookup_default_stream_groups, try_add_realm_custom_profile_field, try_update_realm_custom_profile_field, ) diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index 4be3fcb48c..a4fdde133f 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -25,17 +25,20 @@ from confirmation.models import ( one_click_unsubscribe_link, ) from corporate.lib.stripe import get_latest_seat_count +from zerver.actions.default_streams import ( + do_add_default_stream, + do_create_default_stream_group, + get_default_streams_for_realm, +) from zerver.context_processors import common_context from zerver.decorator import do_two_factor_login from zerver.forms import HomepageForm, check_subdomain_available from zerver.lib.actions import ( add_new_user_history, change_user_is_active, - do_add_default_stream, do_change_full_name, do_change_realm_subdomain, do_change_user_role, - do_create_default_stream_group, do_create_multiuse_invite_link, do_create_realm, do_create_user, @@ -45,7 +48,6 @@ from zerver.lib.actions import ( do_invite_users, do_set_realm_property, do_set_realm_user_default_setting, - get_default_streams_for_realm, process_new_human_user, ) from zerver.lib.email_notifications import enqueue_welcome_emails, followup_day2_email_delay diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index 5347959515..c6c9687001 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -11,33 +11,35 @@ from django.core.exceptions import ValidationError from django.http import HttpResponse from django.utils.timezone import now as timezone_now -from zerver.lib.actions import ( - bulk_add_subscriptions, - bulk_get_subscriber_user_ids, - bulk_remove_subscriptions, +from zerver.actions.default_streams import ( do_add_default_stream, do_add_streams_to_default_stream_group, do_change_default_stream_group_description, do_change_default_stream_group_name, + do_create_default_stream_group, + do_remove_default_stream, + do_remove_default_stream_group, + do_remove_streams_from_default_stream_group, + get_default_streams_for_realm, + lookup_default_stream_groups, +) +from zerver.lib.actions import ( + bulk_add_subscriptions, + bulk_get_subscriber_user_ids, + bulk_remove_subscriptions, do_change_realm_plan_type, do_change_stream_post_policy, do_change_subscription_property, do_change_user_role, - do_create_default_stream_group, do_create_realm, do_deactivate_stream, do_deactivate_user, do_get_streams, - do_remove_default_stream, - do_remove_default_stream_group, - do_remove_streams_from_default_stream_group, do_set_realm_property, ensure_stream, gather_subscriptions, gather_subscriptions_helper, - get_default_streams_for_realm, get_topic_messages, - lookup_default_stream_groups, validate_user_access_to_subscribers_helper, ) from zerver.lib.exceptions import JsonableError diff --git a/zerver/views/registration.py b/zerver/views/registration.py index f913fb1fc1..fa4549fe47 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -25,6 +25,7 @@ from confirmation.models import ( render_confirmation_key_error, validate_key, ) +from zerver.actions.default_streams import lookup_default_stream_groups from zerver.context_processors import get_realm_from_request, login_context from zerver.decorator import do_login, rate_limit_request_by_ip, require_post from zerver.forms import ( @@ -41,7 +42,6 @@ from zerver.lib.actions import ( do_change_user_setting, do_create_realm, do_create_user, - lookup_default_stream_groups, ) from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm from zerver.lib.exceptions import RateLimited diff --git a/zerver/views/streams.py b/zerver/views/streams.py index a03a3cf456..5cadb80d3c 100644 --- a/zerver/views/streams.py +++ b/zerver/views/streams.py @@ -10,6 +10,17 @@ from django.http import HttpRequest, HttpResponse from django.utils.translation import gettext as _ from django.utils.translation import override as override_language +from zerver.actions.default_streams import ( + do_add_default_stream, + do_add_streams_to_default_stream_group, + do_change_default_stream_group_description, + do_change_default_stream_group_name, + do_create_default_stream_group, + do_remove_default_stream, + do_remove_default_stream_group, + do_remove_streams_from_default_stream_group, + get_default_streams_for_realm, +) from zerver.context_processors import get_valid_realm_from_request from zerver.decorator import ( authenticated_json_view, @@ -20,26 +31,17 @@ from zerver.decorator import ( from zerver.lib.actions import ( bulk_add_subscriptions, bulk_remove_subscriptions, - do_add_default_stream, - do_add_streams_to_default_stream_group, - do_change_default_stream_group_description, - do_change_default_stream_group_name, do_change_stream_description, do_change_stream_message_retention_days, do_change_stream_permission, do_change_stream_post_policy, do_change_subscription_property, - do_create_default_stream_group, do_deactivate_stream, do_delete_messages, do_get_streams, - do_remove_default_stream, - do_remove_default_stream_group, - do_remove_streams_from_default_stream_group, do_rename_stream, do_send_messages, gather_subscriptions, - get_default_streams_for_realm, get_subscriber_ids, internal_prep_private_message, internal_prep_stream_message,