diff --git a/analytics/tests/test_support_views.py b/analytics/tests/test_support_views.py index 303f7cfc73..af08f9fe64 100644 --- a/analytics/tests/test_support_views.py +++ b/analytics/tests/test_support_views.py @@ -8,7 +8,7 @@ from django.utils.timezone import now as timezone_now from corporate.lib.stripe import add_months, update_sponsorship_status from corporate.models import Customer, CustomerPlan, LicenseLedger, get_customer_by_realm from zerver.actions.invites import do_create_multiuse_invite_link -from zerver.lib.actions import do_send_realm_reactivation_email, do_set_realm_property +from zerver.actions.realm_settings import do_send_realm_reactivation_email, do_set_realm_property from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import reset_emails_in_zulip_realm from zerver.models import ( diff --git a/analytics/views/support.py b/analytics/views/support.py index 5da64c9104..4174d0bde9 100644 --- a/analytics/views/support.py +++ b/analytics/views/support.py @@ -16,16 +16,16 @@ from django.utils.translation import gettext as _ from confirmation.models import Confirmation, confirmation_url from confirmation.settings import STATUS_ACTIVE -from zerver.decorator import require_server_admin -from zerver.forms import check_subdomain_available -from zerver.lib.actions import ( +from zerver.actions.realm_settings import ( do_change_realm_org_type, do_change_realm_plan_type, - do_change_realm_subdomain, do_deactivate_realm, do_scrub_realm, do_send_realm_reactivation_email, ) +from zerver.decorator import require_server_admin +from zerver.forms import check_subdomain_available +from zerver.lib.actions import do_change_realm_subdomain from zerver.lib.exceptions import JsonableError from zerver.lib.realm_icon import realm_icon_url from zerver.lib.request import REQ, has_request_variables diff --git a/corporate/lib/stripe.py b/corporate/lib/stripe.py index 4ded9ee9aa..e8b22fd48b 100644 --- a/corporate/lib/stripe.py +++ b/corporate/lib/stripe.py @@ -746,7 +746,7 @@ def process_initial_upgrade( ) stripe.Invoice.finalize_invoice(stripe_invoice) - from zerver.lib.actions import do_change_realm_plan_type + from zerver.actions.realm_settings import do_change_realm_plan_type do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD, acting_user=user) @@ -970,7 +970,7 @@ def update_sponsorship_status( def approve_sponsorship(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: from zerver.actions.message_send import internal_send_private_message - from zerver.lib.actions import do_change_realm_plan_type + from zerver.actions.realm_settings import do_change_realm_plan_type do_change_realm_plan_type(realm, Realm.PLAN_TYPE_STANDARD_FREE, acting_user=acting_user) customer = get_customer_by_realm(realm) @@ -1019,7 +1019,7 @@ def do_change_plan_status(plan: CustomerPlan, status: int) -> None: def process_downgrade(plan: CustomerPlan) -> None: - from zerver.lib.actions import do_change_realm_plan_type + from zerver.actions.realm_settings import do_change_realm_plan_type assert plan.customer.realm is not None do_change_realm_plan_type(plan.customer.realm, Realm.PLAN_TYPE_LIMITED, acting_user=None) diff --git a/corporate/tests/test_stripe.py b/corporate/tests/test_stripe.py index b4dae87f6a..7dc9fb9ed6 100644 --- a/corporate/tests/test_stripe.py +++ b/corporate/tests/test_stripe.py @@ -85,8 +85,9 @@ from zerver.actions.create_user import ( do_create_user, do_reactivate_user, ) +from zerver.actions.realm_settings import do_deactivate_realm, do_reactivate_realm from zerver.actions.users import do_deactivate_user -from zerver.lib.actions import do_create_realm, do_deactivate_realm, do_reactivate_realm +from zerver.lib.actions import do_create_realm from zerver.lib.test_classes import ZulipTestCase from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.utils import assert_is_not_none diff --git a/docs/tutorials/new-feature-tutorial.md b/docs/tutorials/new-feature-tutorial.md index efc0d396cd..6a169a34bb 100644 --- a/docs/tutorials/new-feature-tutorial.md +++ b/docs/tutorials/new-feature-tutorial.md @@ -38,7 +38,7 @@ organization in Zulip). The following files are involved in the process: - `zerver/models.py`: Defines the database model. - `zerver/views/realm.py`: The view function that implements the API endpoint for editing realm objects. -- `zerver/lib/actions.py`: Contains code for updating and interacting with the database. +- `zerver/actions/realm_settings.py`: Contains code for updating and interacting with the database. - `zerver/lib/events.py`: Ensures that the state Zulip sends to clients is always consistent and correct. @@ -101,7 +101,7 @@ the flow of events even if the `property_types` framework means you don't have to write much code for a new setting. **Database interaction:** Add any necessary code for updating and -interacting with the database in `zerver/lib/actions.py`. It should +interacting with the database in `zerver/actions/realm_settings.py`. It should update the database and send an event announcing the change. **Application state:** Modify the `fetch_initial_state_data` and @@ -290,10 +290,10 @@ gory details. Anyway, getting back to implementation details... If you are working on a feature that is in the realm `property_types` -dictionary, you will not need to add code to `zerver/lib/actions.py`, but +dictionary, you will not need to add code to `zerver/actions/realm_settings.py`, but we will describe what the process in that file does: -In `zerver/lib/actions.py`, the function `do_set_realm_property` takes +In `zerver/actions/realm_settings.py`, the function `do_set_realm_property` takes in the name of a realm property to update and the value it should have. This function updates the database and triggers an event to notify clients about the change. It uses the field's type, specified @@ -310,7 +310,7 @@ time display format), members in a particular stream only or all active users in a realm. ```python -# zerver/lib/actions.py +# zerver/actions/realm_settings.py def do_set_realm_property( realm: Realm, name: str, value: Any, *, acting_user: Optional[UserProfile] @@ -340,7 +340,7 @@ field), you'll need to create a new function to explicitly update this field and send an event. For example: ```python -# zerver/lib/actions.py +# zerver/actions/realm_settings.p def do_set_realm_authentication_methods( realm: Realm, authentication_methods: Dict[str, bool], *, acting_user: Optional[UserProfile] @@ -480,7 +480,7 @@ with the new value. E.g., for `authentication_methods`, we created # zerver/views/realm.py # import do_set_realm_authentication_methods from actions.py -from zerver.lib.actions import ( +from zerver.actions.realm_settings import ( do_set_realm_message_editing, do_set_realm_authentication_methods, # ... diff --git a/tools/test-api b/tools/test-api index b95550420f..0439dd3ea6 100755 --- a/tools/test-api +++ b/tools/test-api @@ -32,8 +32,8 @@ with test_server_running( # zerver imports should happen after `django.setup()` is run # by the test_server_running decorator. from zerver.actions.create_user import do_create_user, do_reactivate_user + from zerver.actions.realm_settings import do_deactivate_realm, do_reactivate_realm from zerver.actions.users import change_user_is_active - from zerver.lib.actions import do_deactivate_realm, do_reactivate_realm from zerver.lib.test_helpers import reset_emails_in_zulip_realm from zerver.lib.users import get_api_key from zerver.models import get_realm, get_user diff --git a/zerver/actions/realm_settings.py b/zerver/actions/realm_settings.py new file mode 100644 index 0000000000..89bc5e44d3 --- /dev/null +++ b/zerver/actions/realm_settings.py @@ -0,0 +1,472 @@ +from typing import Any, Dict, Optional, Sequence + +import orjson +from django.conf import settings +from django.db import transaction +from django.utils.timezone import now as timezone_now + +from confirmation.models import Confirmation, create_confirmation_link, generate_key +from zerver.actions.custom_profile_fields import do_remove_realm_custom_profile_fields +from zerver.actions.message_edit import do_delete_messages_by_sender +from zerver.actions.user_groups import update_users_in_full_members_system_group +from zerver.actions.user_settings import do_delete_avatar_image, send_user_email_update_event +from zerver.lib.cache import flush_user_profile +from zerver.lib.create_user import get_display_email_address +from zerver.lib.message import update_first_visible_message_id +from zerver.lib.send_email import FromAddress, send_email_to_admins +from zerver.lib.sessions import delete_user_sessions +from zerver.lib.user_counts import realm_user_count_by_role +from zerver.models import ( + Attachment, + Realm, + RealmAuditLog, + RealmUserDefault, + ScheduledEmail, + Stream, + UserProfile, + active_user_ids, +) +from zerver.tornado.django_api import send_event + +if settings.BILLING_ENABLED: + from corporate.lib.stripe import downgrade_now_without_creating_additional_invoices + + +def active_humans_in_realm(realm: Realm) -> Sequence[UserProfile]: + return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False) + + +@transaction.atomic(savepoint=False) +def do_set_realm_property( + realm: Realm, name: str, value: Any, *, acting_user: Optional[UserProfile] +) -> None: + """Takes in a realm object, the name of an attribute to update, the + value to update and and the user who initiated the update. + """ + property_type = Realm.property_types[name] + assert isinstance( + value, property_type + ), f"Cannot update {name}: {value} is not an instance of {property_type}" + + old_value = getattr(realm, name) + setattr(realm, name, value) + realm.save(update_fields=[name]) + + event = dict( + type="realm", + op="update", + property=name, + value=value, + ) + transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id))) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, + event_time=event_time, + acting_user=acting_user, + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_value, + RealmAuditLog.NEW_VALUE: value, + "property": name, + } + ).decode(), + ) + + if name == "email_address_visibility": + if Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE not in [old_value, value]: + # We use real email addresses on UserProfile.email only if + # EMAIL_ADDRESS_VISIBILITY_EVERYONE is configured, so + # changes between values that will not require changing + # that field, so we can save work and return here. + return + + user_profiles = UserProfile.objects.filter(realm=realm, is_bot=False) + for user_profile in user_profiles: + user_profile.email = get_display_email_address(user_profile) + UserProfile.objects.bulk_update(user_profiles, ["email"]) + + for user_profile in user_profiles: + transaction.on_commit( + lambda: flush_user_profile(sender=UserProfile, instance=user_profile) + ) + # TODO: Design a bulk event for this or force-reload all clients + send_user_email_update_event(user_profile) + + if name == "waiting_period_threshold": + update_users_in_full_members_system_group(realm) + + +def do_set_realm_authentication_methods( + realm: Realm, authentication_methods: Dict[str, bool], *, acting_user: Optional[UserProfile] +) -> None: + old_value = realm.authentication_methods_dict() + with transaction.atomic(): + for key, value in list(authentication_methods.items()): + index = getattr(realm.authentication_methods, key).number + realm.authentication_methods.set_bit(index, int(value)) + realm.save(update_fields=["authentication_methods"]) + updated_value = realm.authentication_methods_dict() + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, + event_time=timezone_now(), + acting_user=acting_user, + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_value, + RealmAuditLog.NEW_VALUE: updated_value, + "property": "authentication_methods", + } + ).decode(), + ) + + event = dict( + type="realm", + op="update_dict", + property="default", + data=dict(authentication_methods=updated_value), + ) + send_event(realm, event, active_user_ids(realm.id)) + + +def do_set_realm_message_editing( + realm: Realm, + allow_message_editing: bool, + message_content_edit_limit_seconds: int, + edit_topic_policy: int, + *, + acting_user: Optional[UserProfile], +) -> None: + old_values = dict( + allow_message_editing=realm.allow_message_editing, + message_content_edit_limit_seconds=realm.message_content_edit_limit_seconds, + edit_topic_policy=realm.edit_topic_policy, + ) + + realm.allow_message_editing = allow_message_editing + realm.message_content_edit_limit_seconds = message_content_edit_limit_seconds + realm.edit_topic_policy = edit_topic_policy + + event_time = timezone_now() + updated_properties = dict( + allow_message_editing=allow_message_editing, + message_content_edit_limit_seconds=message_content_edit_limit_seconds, + edit_topic_policy=edit_topic_policy, + ) + + with transaction.atomic(): + for updated_property, updated_value in updated_properties.items(): + if updated_value == old_values[updated_property]: + continue + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, + event_time=event_time, + acting_user=acting_user, + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_values[updated_property], + RealmAuditLog.NEW_VALUE: updated_value, + "property": updated_property, + } + ).decode(), + ) + + realm.save(update_fields=list(updated_properties.keys())) + + event = dict( + type="realm", + op="update_dict", + property="default", + data=updated_properties, + ) + send_event(realm, event, active_user_ids(realm.id)) + + +def do_set_realm_notifications_stream( + realm: Realm, stream: Optional[Stream], stream_id: int, *, acting_user: Optional[UserProfile] +) -> None: + old_value = realm.notifications_stream_id + realm.notifications_stream = stream + with transaction.atomic(): + realm.save(update_fields=["notifications_stream"]) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, + event_time=event_time, + acting_user=acting_user, + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_value, + RealmAuditLog.NEW_VALUE: stream_id, + "property": "notifications_stream", + } + ).decode(), + ) + + event = dict( + type="realm", + op="update", + property="notifications_stream_id", + value=stream_id, + ) + send_event(realm, event, active_user_ids(realm.id)) + + +def do_set_realm_signup_notifications_stream( + realm: Realm, stream: Optional[Stream], stream_id: int, *, acting_user: Optional[UserProfile] +) -> None: + old_value = realm.signup_notifications_stream_id + realm.signup_notifications_stream = stream + with transaction.atomic(): + realm.save(update_fields=["signup_notifications_stream"]) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, + event_time=event_time, + acting_user=acting_user, + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_value, + RealmAuditLog.NEW_VALUE: stream_id, + "property": "signup_notifications_stream", + } + ).decode(), + ) + event = dict( + type="realm", + op="update", + property="signup_notifications_stream_id", + value=stream_id, + ) + send_event(realm, event, active_user_ids(realm.id)) + + +def do_set_realm_user_default_setting( + realm_user_default: RealmUserDefault, + name: str, + value: Any, + *, + acting_user: Optional[UserProfile], +) -> None: + old_value = getattr(realm_user_default, name) + realm = realm_user_default.realm + event_time = timezone_now() + + with transaction.atomic(savepoint=False): + setattr(realm_user_default, name, value) + realm_user_default.save(update_fields=[name]) + + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_DEFAULT_USER_SETTINGS_CHANGED, + event_time=event_time, + acting_user=acting_user, + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_value, + RealmAuditLog.NEW_VALUE: value, + "property": name, + } + ).decode(), + ) + + event = dict( + type="realm_user_settings_defaults", + op="update", + property=name, + value=value, + ) + send_event(realm, event, active_user_ids(realm.id)) + + +def do_deactivate_realm(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: + """ + Deactivate this realm. Do NOT deactivate the users -- we need to be able to + tell the difference between users that were intentionally deactivated, + e.g. by a realm admin, and users who can't currently use Zulip because their + realm has been deactivated. + """ + if realm.deactivated: + return + + realm.deactivated = True + realm.save(update_fields=["deactivated"]) + + if settings.BILLING_ENABLED: + downgrade_now_without_creating_additional_invoices(realm) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_DEACTIVATED, + event_time=event_time, + acting_user=acting_user, + extra_data=orjson.dumps( + { + RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(realm), + } + ).decode(), + ) + + ScheduledEmail.objects.filter(realm=realm).delete() + for user in active_humans_in_realm(realm): + # Don't deactivate the users, but do delete their sessions so they get + # bumped to the login screen, where they'll get a realm deactivation + # notice when they try to log in. + delete_user_sessions(user) + + # This event will only ever be received by clients with an active + # longpoll connection, because by this point clients will be + # unable to authenticate again to their event queue (triggering an + # immediate reload into the page explaining the realm was + # deactivated). So the purpose of sending this is to flush all + # active longpoll connections for the realm. + event = dict(type="realm", op="deactivated", realm_id=realm.id) + send_event(realm, event, active_user_ids(realm.id)) + + +def do_reactivate_realm(realm: Realm) -> None: + realm.deactivated = False + with transaction.atomic(): + realm.save(update_fields=["deactivated"]) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=realm, + event_type=RealmAuditLog.REALM_REACTIVATED, + event_time=event_time, + extra_data=orjson.dumps( + { + RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(realm), + } + ).decode(), + ) + + +def do_add_deactivated_redirect(realm: Realm, redirect_url: str) -> None: + realm.deactivated_redirect = redirect_url + realm.save(update_fields=["deactivated_redirect"]) + + +def do_scrub_realm(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: + if settings.BILLING_ENABLED: + downgrade_now_without_creating_additional_invoices(realm) + + users = UserProfile.objects.filter(realm=realm) + for user in users: + do_delete_messages_by_sender(user) + do_delete_avatar_image(user, acting_user=acting_user) + user.full_name = f"Scrubbed {generate_key()[:15]}" + scrubbed_email = f"scrubbed-{generate_key()[:15]}@{realm.host}" + user.email = scrubbed_email + user.delivery_email = scrubbed_email + user.save(update_fields=["full_name", "email", "delivery_email"]) + + do_remove_realm_custom_profile_fields(realm) + Attachment.objects.filter(realm=realm).delete() + + RealmAuditLog.objects.create( + realm=realm, + event_time=timezone_now(), + acting_user=acting_user, + event_type=RealmAuditLog.REALM_SCRUBBED, + ) + + +@transaction.atomic(durable=True) +def do_change_realm_org_type( + realm: Realm, + org_type: int, + acting_user: Optional[UserProfile], +) -> None: + old_value = realm.org_type + realm.org_type = org_type + realm.save(update_fields=["org_type"]) + + RealmAuditLog.objects.create( + event_type=RealmAuditLog.REALM_ORG_TYPE_CHANGED, + realm=realm, + event_time=timezone_now(), + acting_user=acting_user, + extra_data={"old_value": old_value, "new_value": org_type}, + ) + + +@transaction.atomic(savepoint=False) +def do_change_realm_plan_type( + realm: Realm, plan_type: int, *, acting_user: Optional[UserProfile] +) -> None: + old_value = realm.plan_type + realm.plan_type = plan_type + realm.save(update_fields=["plan_type"]) + RealmAuditLog.objects.create( + event_type=RealmAuditLog.REALM_PLAN_TYPE_CHANGED, + realm=realm, + event_time=timezone_now(), + acting_user=acting_user, + extra_data={"old_value": old_value, "new_value": plan_type}, + ) + + if plan_type == Realm.PLAN_TYPE_PLUS: + realm.max_invites = Realm.INVITES_STANDARD_REALM_DAILY_MAX + realm.message_visibility_limit = None + realm.upload_quota_gb = Realm.UPLOAD_QUOTA_STANDARD + elif plan_type == Realm.PLAN_TYPE_STANDARD: + realm.max_invites = Realm.INVITES_STANDARD_REALM_DAILY_MAX + realm.message_visibility_limit = None + realm.upload_quota_gb = Realm.UPLOAD_QUOTA_STANDARD + elif plan_type == Realm.PLAN_TYPE_SELF_HOSTED: + realm.max_invites = None # type: ignore[assignment] # Apparent mypy bug with Optional[int] setter. + realm.message_visibility_limit = None + realm.upload_quota_gb = None + elif plan_type == Realm.PLAN_TYPE_STANDARD_FREE: + realm.max_invites = Realm.INVITES_STANDARD_REALM_DAILY_MAX + realm.message_visibility_limit = None + realm.upload_quota_gb = Realm.UPLOAD_QUOTA_STANDARD + elif plan_type == Realm.PLAN_TYPE_LIMITED: + realm.max_invites = settings.INVITES_DEFAULT_REALM_DAILY_MAX + realm.message_visibility_limit = Realm.MESSAGE_VISIBILITY_LIMITED + realm.upload_quota_gb = Realm.UPLOAD_QUOTA_LIMITED + else: + raise AssertionError("Invalid plan type") + + update_first_visible_message_id(realm) + + realm.save(update_fields=["_max_invites", "message_visibility_limit", "upload_quota_gb"]) + + event = { + "type": "realm", + "op": "update", + "property": "plan_type", + "value": plan_type, + "extra_data": {"upload_quota": realm.upload_quota_bytes()}, + } + transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id))) + + +def do_send_realm_reactivation_email(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: + url = create_confirmation_link(realm, Confirmation.REALM_REACTIVATION) + RealmAuditLog.objects.create( + realm=realm, + acting_user=acting_user, + event_type=RealmAuditLog.REALM_REACTIVATION_EMAIL_SENT, + event_time=timezone_now(), + ) + context = {"confirmation_url": url, "realm_uri": realm.uri, "realm_name": realm.name} + language = realm.default_language + send_email_to_admins( + "zerver/emails/realm_reactivation", + realm, + from_address=FromAddress.tokenized_no_reply_address(), + from_name=FromAddress.security_email_from_name(language=language), + language=language, + context=context, + ) diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 644dc6978d..5654919cba 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -1,6 +1,6 @@ import datetime import logging -from typing import Any, Dict, List, Optional, Sequence +from typing import Any, Dict, List, Optional import orjson from django.conf import settings @@ -8,32 +8,25 @@ from django.db import transaction from django.utils.timezone import now as timezone_now from django.utils.translation import gettext as _ -from confirmation.models import Confirmation, create_confirmation_link, generate_key -from zerver.actions.custom_profile_fields import do_remove_realm_custom_profile_fields -from zerver.actions.message_edit import do_delete_messages_by_sender from zerver.actions.message_send import internal_send_stream_message -from zerver.actions.user_groups import update_users_in_full_members_system_group -from zerver.actions.user_settings import do_delete_avatar_image, send_user_email_update_event +from zerver.actions.realm_settings import ( + do_add_deactivated_redirect, + do_change_realm_plan_type, + do_deactivate_realm, + do_set_realm_property, +) from zerver.lib.bulk_create import create_users -from zerver.lib.cache import flush_user_profile -from zerver.lib.create_user import get_display_email_address -from zerver.lib.message import update_first_visible_message_id -from zerver.lib.send_email import FromAddress, send_email_to_admins from zerver.lib.server_initialization import create_internal_realm, server_initialized -from zerver.lib.sessions import delete_user_sessions from zerver.lib.streams import ensure_stream, get_signups_stream from zerver.lib.topic import filter_by_topic_name_via_message -from zerver.lib.user_counts import realm_user_count_by_role from zerver.lib.user_groups import create_system_user_groups_for_realm from zerver.models import ( - Attachment, DefaultStream, Message, Realm, RealmAuditLog, RealmDomain, RealmUserDefault, - ScheduledEmail, Stream, UserMessage, UserProfile, @@ -44,328 +37,6 @@ from zerver.models import ( ) from zerver.tornado.django_api import send_event -if settings.BILLING_ENABLED: - from corporate.lib.stripe import downgrade_now_without_creating_additional_invoices - - -def active_humans_in_realm(realm: Realm) -> Sequence[UserProfile]: - return UserProfile.objects.filter(realm=realm, is_active=True, is_bot=False) - - -@transaction.atomic(savepoint=False) -def do_set_realm_property( - realm: Realm, name: str, value: Any, *, acting_user: Optional[UserProfile] -) -> None: - """Takes in a realm object, the name of an attribute to update, the - value to update and and the user who initiated the update. - """ - property_type = Realm.property_types[name] - assert isinstance( - value, property_type - ), f"Cannot update {name}: {value} is not an instance of {property_type}" - - old_value = getattr(realm, name) - setattr(realm, name, value) - realm.save(update_fields=[name]) - - event = dict( - type="realm", - op="update", - property=name, - value=value, - ) - transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id))) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, - event_time=event_time, - acting_user=acting_user, - extra_data=orjson.dumps( - { - RealmAuditLog.OLD_VALUE: old_value, - RealmAuditLog.NEW_VALUE: value, - "property": name, - } - ).decode(), - ) - - if name == "email_address_visibility": - if Realm.EMAIL_ADDRESS_VISIBILITY_EVERYONE not in [old_value, value]: - # We use real email addresses on UserProfile.email only if - # EMAIL_ADDRESS_VISIBILITY_EVERYONE is configured, so - # changes between values that will not require changing - # that field, so we can save work and return here. - return - - user_profiles = UserProfile.objects.filter(realm=realm, is_bot=False) - for user_profile in user_profiles: - user_profile.email = get_display_email_address(user_profile) - UserProfile.objects.bulk_update(user_profiles, ["email"]) - - for user_profile in user_profiles: - transaction.on_commit( - lambda: flush_user_profile(sender=UserProfile, instance=user_profile) - ) - # TODO: Design a bulk event for this or force-reload all clients - send_user_email_update_event(user_profile) - - if name == "waiting_period_threshold": - update_users_in_full_members_system_group(realm) - - -def do_set_realm_authentication_methods( - realm: Realm, authentication_methods: Dict[str, bool], *, acting_user: Optional[UserProfile] -) -> None: - old_value = realm.authentication_methods_dict() - with transaction.atomic(): - for key, value in list(authentication_methods.items()): - index = getattr(realm.authentication_methods, key).number - realm.authentication_methods.set_bit(index, int(value)) - realm.save(update_fields=["authentication_methods"]) - updated_value = realm.authentication_methods_dict() - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, - event_time=timezone_now(), - acting_user=acting_user, - extra_data=orjson.dumps( - { - RealmAuditLog.OLD_VALUE: old_value, - RealmAuditLog.NEW_VALUE: updated_value, - "property": "authentication_methods", - } - ).decode(), - ) - - event = dict( - type="realm", - op="update_dict", - property="default", - data=dict(authentication_methods=updated_value), - ) - send_event(realm, event, active_user_ids(realm.id)) - - -def do_set_realm_message_editing( - realm: Realm, - allow_message_editing: bool, - message_content_edit_limit_seconds: int, - edit_topic_policy: int, - *, - acting_user: Optional[UserProfile], -) -> None: - old_values = dict( - allow_message_editing=realm.allow_message_editing, - message_content_edit_limit_seconds=realm.message_content_edit_limit_seconds, - edit_topic_policy=realm.edit_topic_policy, - ) - - realm.allow_message_editing = allow_message_editing - realm.message_content_edit_limit_seconds = message_content_edit_limit_seconds - realm.edit_topic_policy = edit_topic_policy - - event_time = timezone_now() - updated_properties = dict( - allow_message_editing=allow_message_editing, - message_content_edit_limit_seconds=message_content_edit_limit_seconds, - edit_topic_policy=edit_topic_policy, - ) - - with transaction.atomic(): - for updated_property, updated_value in updated_properties.items(): - if updated_value == old_values[updated_property]: - continue - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, - event_time=event_time, - acting_user=acting_user, - extra_data=orjson.dumps( - { - RealmAuditLog.OLD_VALUE: old_values[updated_property], - RealmAuditLog.NEW_VALUE: updated_value, - "property": updated_property, - } - ).decode(), - ) - - realm.save(update_fields=list(updated_properties.keys())) - - event = dict( - type="realm", - op="update_dict", - property="default", - data=updated_properties, - ) - send_event(realm, event, active_user_ids(realm.id)) - - -def do_set_realm_notifications_stream( - realm: Realm, stream: Optional[Stream], stream_id: int, *, acting_user: Optional[UserProfile] -) -> None: - old_value = realm.notifications_stream_id - realm.notifications_stream = stream - with transaction.atomic(): - realm.save(update_fields=["notifications_stream"]) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, - event_time=event_time, - acting_user=acting_user, - extra_data=orjson.dumps( - { - RealmAuditLog.OLD_VALUE: old_value, - RealmAuditLog.NEW_VALUE: stream_id, - "property": "notifications_stream", - } - ).decode(), - ) - - event = dict( - type="realm", - op="update", - property="notifications_stream_id", - value=stream_id, - ) - send_event(realm, event, active_user_ids(realm.id)) - - -def do_set_realm_signup_notifications_stream( - realm: Realm, stream: Optional[Stream], stream_id: int, *, acting_user: Optional[UserProfile] -) -> None: - old_value = realm.signup_notifications_stream_id - realm.signup_notifications_stream = stream - with transaction.atomic(): - realm.save(update_fields=["signup_notifications_stream"]) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_PROPERTY_CHANGED, - event_time=event_time, - acting_user=acting_user, - extra_data=orjson.dumps( - { - RealmAuditLog.OLD_VALUE: old_value, - RealmAuditLog.NEW_VALUE: stream_id, - "property": "signup_notifications_stream", - } - ).decode(), - ) - event = dict( - type="realm", - op="update", - property="signup_notifications_stream_id", - value=stream_id, - ) - send_event(realm, event, active_user_ids(realm.id)) - - -def do_set_realm_user_default_setting( - realm_user_default: RealmUserDefault, - name: str, - value: Any, - *, - acting_user: Optional[UserProfile], -) -> None: - old_value = getattr(realm_user_default, name) - realm = realm_user_default.realm - event_time = timezone_now() - - with transaction.atomic(savepoint=False): - setattr(realm_user_default, name, value) - realm_user_default.save(update_fields=[name]) - - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_DEFAULT_USER_SETTINGS_CHANGED, - event_time=event_time, - acting_user=acting_user, - extra_data=orjson.dumps( - { - RealmAuditLog.OLD_VALUE: old_value, - RealmAuditLog.NEW_VALUE: value, - "property": name, - } - ).decode(), - ) - - event = dict( - type="realm_user_settings_defaults", - op="update", - property=name, - value=value, - ) - send_event(realm, event, active_user_ids(realm.id)) - - -def do_deactivate_realm(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: - """ - Deactivate this realm. Do NOT deactivate the users -- we need to be able to - tell the difference between users that were intentionally deactivated, - e.g. by a realm admin, and users who can't currently use Zulip because their - realm has been deactivated. - """ - if realm.deactivated: - return - - realm.deactivated = True - realm.save(update_fields=["deactivated"]) - - if settings.BILLING_ENABLED: - downgrade_now_without_creating_additional_invoices(realm) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_DEACTIVATED, - event_time=event_time, - acting_user=acting_user, - extra_data=orjson.dumps( - { - RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(realm), - } - ).decode(), - ) - - ScheduledEmail.objects.filter(realm=realm).delete() - for user in active_humans_in_realm(realm): - # Don't deactivate the users, but do delete their sessions so they get - # bumped to the login screen, where they'll get a realm deactivation - # notice when they try to log in. - delete_user_sessions(user) - - # This event will only ever be received by clients with an active - # longpoll connection, because by this point clients will be - # unable to authenticate again to their event queue (triggering an - # immediate reload into the page explaining the realm was - # deactivated). So the purpose of sending this is to flush all - # active longpoll connections for the realm. - event = dict(type="realm", op="deactivated", realm_id=realm.id) - send_event(realm, event, active_user_ids(realm.id)) - - -def do_reactivate_realm(realm: Realm) -> None: - realm.deactivated = False - with transaction.atomic(): - realm.save(update_fields=["deactivated"]) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=realm, - event_type=RealmAuditLog.REALM_REACTIVATED, - event_time=event_time, - extra_data=orjson.dumps( - { - RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(realm), - } - ).decode(), - ) - def do_change_realm_subdomain( realm: Realm, new_subdomain: str, *, acting_user: Optional[UserProfile] @@ -412,107 +83,6 @@ def do_change_realm_subdomain( do_add_deactivated_redirect(placeholder_realm, realm.uri) -def do_add_deactivated_redirect(realm: Realm, redirect_url: str) -> None: - realm.deactivated_redirect = redirect_url - realm.save(update_fields=["deactivated_redirect"]) - - -def do_scrub_realm(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: - if settings.BILLING_ENABLED: - downgrade_now_without_creating_additional_invoices(realm) - - users = UserProfile.objects.filter(realm=realm) - for user in users: - do_delete_messages_by_sender(user) - do_delete_avatar_image(user, acting_user=acting_user) - user.full_name = f"Scrubbed {generate_key()[:15]}" - scrubbed_email = f"scrubbed-{generate_key()[:15]}@{realm.host}" - user.email = scrubbed_email - user.delivery_email = scrubbed_email - user.save(update_fields=["full_name", "email", "delivery_email"]) - - do_remove_realm_custom_profile_fields(realm) - Attachment.objects.filter(realm=realm).delete() - - RealmAuditLog.objects.create( - realm=realm, - event_time=timezone_now(), - acting_user=acting_user, - event_type=RealmAuditLog.REALM_SCRUBBED, - ) - - -@transaction.atomic(durable=True) -def do_change_realm_org_type( - realm: Realm, - org_type: int, - acting_user: Optional[UserProfile], -) -> None: - old_value = realm.org_type - realm.org_type = org_type - realm.save(update_fields=["org_type"]) - - RealmAuditLog.objects.create( - event_type=RealmAuditLog.REALM_ORG_TYPE_CHANGED, - realm=realm, - event_time=timezone_now(), - acting_user=acting_user, - extra_data={"old_value": old_value, "new_value": org_type}, - ) - - -@transaction.atomic(savepoint=False) -def do_change_realm_plan_type( - realm: Realm, plan_type: int, *, acting_user: Optional[UserProfile] -) -> None: - old_value = realm.plan_type - realm.plan_type = plan_type - realm.save(update_fields=["plan_type"]) - RealmAuditLog.objects.create( - event_type=RealmAuditLog.REALM_PLAN_TYPE_CHANGED, - realm=realm, - event_time=timezone_now(), - acting_user=acting_user, - extra_data={"old_value": old_value, "new_value": plan_type}, - ) - - if plan_type == Realm.PLAN_TYPE_PLUS: - realm.max_invites = Realm.INVITES_STANDARD_REALM_DAILY_MAX - realm.message_visibility_limit = None - realm.upload_quota_gb = Realm.UPLOAD_QUOTA_STANDARD - elif plan_type == Realm.PLAN_TYPE_STANDARD: - realm.max_invites = Realm.INVITES_STANDARD_REALM_DAILY_MAX - realm.message_visibility_limit = None - realm.upload_quota_gb = Realm.UPLOAD_QUOTA_STANDARD - elif plan_type == Realm.PLAN_TYPE_SELF_HOSTED: - realm.max_invites = None # type: ignore[assignment] # Apparent mypy bug with Optional[int] setter. - realm.message_visibility_limit = None - realm.upload_quota_gb = None - elif plan_type == Realm.PLAN_TYPE_STANDARD_FREE: - realm.max_invites = Realm.INVITES_STANDARD_REALM_DAILY_MAX - realm.message_visibility_limit = None - realm.upload_quota_gb = Realm.UPLOAD_QUOTA_STANDARD - elif plan_type == Realm.PLAN_TYPE_LIMITED: - realm.max_invites = settings.INVITES_DEFAULT_REALM_DAILY_MAX - realm.message_visibility_limit = Realm.MESSAGE_VISIBILITY_LIMITED - realm.upload_quota_gb = Realm.UPLOAD_QUOTA_LIMITED - else: - raise AssertionError("Invalid plan type") - - update_first_visible_message_id(realm) - - realm.save(update_fields=["_max_invites", "message_visibility_limit", "upload_quota_gb"]) - - event = { - "type": "realm", - "op": "update", - "property": "plan_type", - "value": plan_type, - "extra_data": {"upload_quota": realm.upload_quota_bytes()}, - } - transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id))) - - def set_realm_permissions_based_on_org_type(realm: Realm) -> None: """This function implements overrides for the default configuration for new organizations when the administrator selected specific @@ -779,26 +349,6 @@ def do_remove_realm_domain( transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id))) -def do_send_realm_reactivation_email(realm: Realm, *, acting_user: Optional[UserProfile]) -> None: - url = create_confirmation_link(realm, Confirmation.REALM_REACTIVATION) - RealmAuditLog.objects.create( - realm=realm, - acting_user=acting_user, - event_type=RealmAuditLog.REALM_REACTIVATION_EMAIL_SENT, - event_time=timezone_now(), - ) - context = {"confirmation_url": url, "realm_uri": realm.uri, "realm_name": realm.name} - language = realm.default_language - send_email_to_admins( - "zerver/emails/realm_reactivation", - realm, - from_address=FromAddress.tokenized_no_reply_address(), - from_name=FromAddress.security_email_from_name(language=language), - language=language, - context=context, - ) - - def get_topic_messages(user_profile: UserProfile, stream: Stream, topic_name: str) -> List[Message]: query = UserMessage.objects.filter( user_profile=user_profile, diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index fd8714d30e..d87dc27ad7 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -16,8 +16,8 @@ from psycopg2.extras import execute_values from psycopg2.sql import SQL, Identifier from analytics.models import RealmCount, StreamCount, UserCount +from zerver.actions.realm_settings import do_change_realm_plan_type from zerver.actions.user_settings import do_change_avatar_fields -from zerver.lib.actions import do_change_realm_plan_type from zerver.lib.avatar_hash import user_avatar_path_from_ids from zerver.lib.bulk_create import bulk_create_users, bulk_set_users_or_streams_recipient_fields from zerver.lib.export import DATE_FIELDS, Field, Path, Record, TableData, TableName diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index 2a65ec143f..c67cf854dc 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -48,9 +48,9 @@ from two_factor.models import PhoneDevice from corporate.models import Customer, CustomerPlan, LicenseLedger from zerver.actions.message_send import check_send_message, check_send_stream_message +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.streams import bulk_add_subscriptions, bulk_remove_subscriptions from zerver.decorator import do_two_factor_login -from zerver.lib.actions import do_set_realm_property from zerver.lib.cache import bounce_key_prefix_for_testing from zerver.lib.initial_password import initial_password from zerver.lib.notification_data import UserMessageNotificationsData diff --git a/zerver/lib/test_helpers.py b/zerver/lib/test_helpers.py index 4ce95571af..4aaa66c624 100644 --- a/zerver/lib/test_helpers.py +++ b/zerver/lib/test_helpers.py @@ -39,8 +39,8 @@ from moto import mock_s3 from mypy_boto3_s3.service_resource import Bucket import zerver.lib.upload +from zerver.actions.realm_settings import do_set_realm_property from zerver.lib import cache -from zerver.lib.actions import do_set_realm_property from zerver.lib.avatar import avatar_url from zerver.lib.cache import get_cache_backend from zerver.lib.db import Params, ParamsT, Query, TimeTrackingCursor diff --git a/zerver/management/commands/deactivate_realm.py b/zerver/management/commands/deactivate_realm.py index ce50856f59..0526d5f9ba 100644 --- a/zerver/management/commands/deactivate_realm.py +++ b/zerver/management/commands/deactivate_realm.py @@ -1,7 +1,7 @@ from argparse import ArgumentParser from typing import Any -from zerver.lib.actions import do_add_deactivated_redirect, do_deactivate_realm +from zerver.actions.realm_settings import do_add_deactivated_redirect, do_deactivate_realm from zerver.lib.management import ZulipBaseCommand diff --git a/zerver/management/commands/export.py b/zerver/management/commands/export.py index 4f12c9444e..4811a7a72b 100644 --- a/zerver/management/commands/export.py +++ b/zerver/management/commands/export.py @@ -6,7 +6,7 @@ from typing import Any from django.conf import settings from django.core.management.base import CommandError -from zerver.lib.actions import do_deactivate_realm +from zerver.actions.realm_settings import do_deactivate_realm from zerver.lib.export import export_realm_wrapper from zerver.lib.management import ZulipBaseCommand from zerver.models import Message, Reaction, UserProfile diff --git a/zerver/management/commands/reactivate_realm.py b/zerver/management/commands/reactivate_realm.py index c8e1144323..b1650bcb2b 100644 --- a/zerver/management/commands/reactivate_realm.py +++ b/zerver/management/commands/reactivate_realm.py @@ -1,7 +1,7 @@ from argparse import ArgumentParser from typing import Any -from zerver.lib.actions import do_reactivate_realm +from zerver.actions.realm_settings import do_reactivate_realm from zerver.lib.management import ZulipBaseCommand diff --git a/zerver/management/commands/scrub_realm.py b/zerver/management/commands/scrub_realm.py index c5a56ce051..5e7b130f70 100644 --- a/zerver/management/commands/scrub_realm.py +++ b/zerver/management/commands/scrub_realm.py @@ -1,7 +1,7 @@ from argparse import ArgumentParser from typing import Any -from zerver.lib.actions import do_scrub_realm +from zerver.actions.realm_settings import do_scrub_realm from zerver.lib.management import ZulipBaseCommand diff --git a/zerver/management/commands/send_realm_reactivation_email.py b/zerver/management/commands/send_realm_reactivation_email.py index 6477785377..6ab31e67f1 100644 --- a/zerver/management/commands/send_realm_reactivation_email.py +++ b/zerver/management/commands/send_realm_reactivation_email.py @@ -3,7 +3,7 @@ from typing import Any from django.core.management.base import CommandError -from zerver.lib.actions import do_send_realm_reactivation_email +from zerver.actions.realm_settings import do_send_realm_reactivation_email from zerver.lib.management import ZulipBaseCommand diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index a77a8f6f0e..e8cd6bf076 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -19,6 +19,14 @@ from zerver.actions.create_user import ( ) from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_playgrounds import do_add_realm_playground, do_remove_realm_playground +from zerver.actions.realm_settings import ( + do_deactivate_realm, + do_reactivate_realm, + do_set_realm_authentication_methods, + do_set_realm_message_editing, + do_set_realm_notifications_stream, + do_set_realm_signup_notifications_stream, +) from zerver.actions.streams import ( bulk_add_subscriptions, bulk_remove_subscriptions, @@ -35,17 +43,7 @@ from zerver.actions.user_settings import ( do_regenerate_api_key, ) from zerver.actions.users import do_change_user_role, do_deactivate_user -from zerver.lib.actions import ( - do_add_realm_domain, - do_change_realm_domain, - do_deactivate_realm, - do_reactivate_realm, - do_remove_realm_domain, - do_set_realm_authentication_methods, - do_set_realm_message_editing, - do_set_realm_notifications_stream, - do_set_realm_signup_notifications_stream, -) +from zerver.lib.actions import do_add_realm_domain, do_change_realm_domain, do_remove_realm_domain from zerver.lib.message import get_last_message_id from zerver.lib.stream_traffic import get_streams_traffic from zerver.lib.streams import create_stream_if_needed diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index c4a656a6af..5b2b530fed 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -42,13 +42,13 @@ from social_django.strategy import DjangoStrategy from confirmation.models import Confirmation, create_confirmation_link from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.invites import do_invite_users -from zerver.actions.users import change_user_is_active, do_deactivate_user -from zerver.lib.actions import ( - do_create_realm, +from zerver.actions.realm_settings import ( do_deactivate_realm, do_reactivate_realm, do_set_realm_property, ) +from zerver.actions.users import change_user_is_active, do_deactivate_user +from zerver.lib.actions import do_create_realm from zerver.lib.avatar import avatar_url from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.dev_ldap_directory import generate_dev_ldap_dir diff --git a/zerver/tests/test_bots.py b/zerver/tests/test_bots.py index c03d0214b9..b8d53b998d 100644 --- a/zerver/tests/test_bots.py +++ b/zerver/tests/test_bots.py @@ -8,9 +8,9 @@ from django.core import mail from django.test import override_settings from zulip_bots.custom_exceptions import ConfigValidationError +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.streams import do_change_stream_permission from zerver.actions.users import do_change_can_create_users, do_deactivate_user -from zerver.lib.actions import do_set_realm_property from zerver.lib.bot_config import ConfigError, get_bot_config from zerver.lib.bot_lib import get_bot_handler from zerver.lib.integrations import EMBEDDED_BOTS, WebhookIntegration diff --git a/zerver/tests/test_decorators.py b/zerver/tests/test_decorators.py index 8e4bb7320d..3e10775c14 100644 --- a/zerver/tests/test_decorators.py +++ b/zerver/tests/test_decorators.py @@ -14,6 +14,11 @@ from django.http import HttpRequest, HttpResponse from django.utils.timezone import now as timezone_now from zerver.actions.create_user import do_reactivate_user +from zerver.actions.realm_settings import ( + do_deactivate_realm, + do_reactivate_realm, + do_set_realm_property, +) from zerver.actions.users import change_user_is_active, do_deactivate_user from zerver.decorator import ( authenticate_notify, @@ -30,12 +35,7 @@ from zerver.decorator import ( zulip_login_required, ) from zerver.forms import OurAuthenticationForm -from zerver.lib.actions import ( - do_create_realm, - do_deactivate_realm, - do_reactivate_realm, - do_set_realm_property, -) +from zerver.lib.actions import do_create_realm from zerver.lib.cache import dict_to_items_tuple, ignore_unhashable_lru_cache, items_tuple_to_dict from zerver.lib.exceptions import ( AccessDeniedError, diff --git a/zerver/tests/test_email_change.py b/zerver/tests/test_email_change.py index 2e842f6a90..142ec3350b 100644 --- a/zerver/tests/test_email_change.py +++ b/zerver/tests/test_email_change.py @@ -11,9 +11,9 @@ from confirmation.models import ( create_confirmation_link, generate_key, ) +from zerver.actions.realm_settings import do_deactivate_realm, do_set_realm_property from zerver.actions.user_settings import do_start_email_change_process from zerver.actions.users import do_deactivate_user -from zerver.lib.actions import do_deactivate_realm, do_set_realm_property from zerver.lib.test_classes import ZulipTestCase from zerver.models import ( EmailChangeStatus, diff --git a/zerver/tests/test_email_mirror.py b/zerver/tests/test_email_mirror.py index 00512f9ac9..759a725b98 100644 --- a/zerver/tests/test_email_mirror.py +++ b/zerver/tests/test_email_mirror.py @@ -11,9 +11,9 @@ import orjson from django.conf import settings from django.http import HttpResponse +from zerver.actions.realm_settings import do_deactivate_realm from zerver.actions.streams import do_change_stream_post_policy from zerver.actions.users import do_deactivate_user -from zerver.lib.actions import do_deactivate_realm from zerver.lib.email_mirror import ( create_missed_message_address, filter_footer, diff --git a/zerver/tests/test_event_system.py b/zerver/tests/test_event_system.py index 8769060cf3..433caa2f0a 100644 --- a/zerver/tests/test_event_system.py +++ b/zerver/tests/test_event_system.py @@ -10,8 +10,8 @@ from django.utils.timezone import now as timezone_now from version import API_FEATURE_LEVEL, ZULIP_MERGE_BASE, ZULIP_VERSION from zerver.actions.message_send import check_send_message from zerver.actions.presence import do_update_user_presence +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.users import do_change_user_role -from zerver.lib.actions import do_set_realm_property from zerver.lib.event_schema import check_restart_event from zerver.lib.events import fetch_initial_state_data from zerver.lib.exceptions import AccessDeniedError diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index d2221cdb2e..0cc6c52664 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -63,6 +63,16 @@ from zerver.actions.realm_linkifiers import ( ) from zerver.actions.realm_logo import do_change_logo_source from zerver.actions.realm_playgrounds import do_add_realm_playground, do_remove_realm_playground +from zerver.actions.realm_settings import ( + do_change_realm_plan_type, + do_deactivate_realm, + do_set_realm_authentication_methods, + do_set_realm_message_editing, + do_set_realm_notifications_stream, + do_set_realm_property, + do_set_realm_signup_notifications_stream, + do_set_realm_user_default_setting, +) from zerver.actions.streams import ( bulk_add_subscriptions, bulk_remove_subscriptions, @@ -99,19 +109,7 @@ from zerver.actions.users import ( do_update_outgoing_webhook_service, ) from zerver.actions.video_calls import do_set_zoom_token -from zerver.lib.actions import ( - do_add_realm_domain, - do_change_realm_domain, - do_change_realm_plan_type, - do_deactivate_realm, - do_remove_realm_domain, - do_set_realm_authentication_methods, - do_set_realm_message_editing, - do_set_realm_notifications_stream, - do_set_realm_property, - do_set_realm_signup_notifications_stream, - do_set_realm_user_default_setting, -) +from zerver.lib.actions import do_add_realm_domain, do_change_realm_domain, do_remove_realm_domain from zerver.lib.drafts import do_create_drafts, do_delete_draft, do_edit_draft from zerver.lib.event_schema import ( check_alert_words, diff --git a/zerver/tests/test_home.py b/zerver/tests/test_home.py index 7696d491a1..5808171f0e 100644 --- a/zerver/tests/test_home.py +++ b/zerver/tests/test_home.py @@ -14,8 +14,8 @@ from django.utils.timezone import now as timezone_now from corporate.models import Customer, CustomerPlan from zerver.actions.create_user import do_create_user +from zerver.actions.realm_settings import do_change_realm_plan_type from zerver.actions.users import change_user_is_active -from zerver.lib.actions import do_change_realm_plan_type from zerver.lib.compatibility import LAST_SERVER_UPGRADE_TIME, is_outdated_server from zerver.lib.home import ( get_billing_info, diff --git a/zerver/tests/test_import_export.py b/zerver/tests/test_import_export.py index 6c2ec56045..206686d601 100644 --- a/zerver/tests/test_import_export.py +++ b/zerver/tests/test_import_export.py @@ -22,11 +22,11 @@ from zerver.actions.reactions import check_add_reaction, do_add_reaction from zerver.actions.realm_emoji import check_add_realm_emoji from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_logo import do_change_logo_source +from zerver.actions.realm_settings import do_change_realm_plan_type from zerver.actions.user_activity import do_update_user_activity, do_update_user_activity_interval from zerver.actions.user_topics import do_mute_topic from zerver.actions.users import do_deactivate_user from zerver.lib import upload -from zerver.lib.actions import do_change_realm_plan_type from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.bot_config import set_bot_config from zerver.lib.bot_lib import StateHandler diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py index 97de45ee67..1e0332f9a5 100644 --- a/zerver/tests/test_markdown.py +++ b/zerver/tests/test_markdown.py @@ -12,9 +12,10 @@ from markdown import Markdown from zerver.actions.alert_words import do_add_alert_words from zerver.actions.realm_emoji import do_remove_realm_emoji +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.user_settings import do_change_user_setting from zerver.actions.users import change_user_is_active -from zerver.lib.actions import do_create_realm, do_set_realm_property +from zerver.lib.actions import do_create_realm from zerver.lib.alert_words import get_alert_word_automaton from zerver.lib.camo import get_camo_url from zerver.lib.create_user import create_user diff --git a/zerver/tests/test_message_edit.py b/zerver/tests/test_message_edit.py index 9a785ff345..7d2f27eb2a 100644 --- a/zerver/tests/test_message_edit.py +++ b/zerver/tests/test_message_edit.py @@ -15,9 +15,10 @@ from zerver.actions.message_edit import ( get_user_info_for_message_updates, ) from zerver.actions.reactions import do_add_reaction +from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property from zerver.actions.streams import do_change_stream_post_policy, do_deactivate_stream from zerver.actions.users import do_change_user_role -from zerver.lib.actions import do_change_realm_plan_type, do_set_realm_property, get_topic_messages +from zerver.lib.actions import get_topic_messages from zerver.lib.message import MessageDict, has_message_access, messages_for_ids from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import cache_tries_captured, queries_captured diff --git a/zerver/tests/test_message_fetch.py b/zerver/tests/test_message_fetch.py index 750a285618..69da0db873 100644 --- a/zerver/tests/test_message_fetch.py +++ b/zerver/tests/test_message_fetch.py @@ -14,9 +14,9 @@ from sqlalchemy.types import Integer from analytics.lib.counts import COUNT_STATS from analytics.models import RealmCount from zerver.actions.message_edit import do_update_message +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.uploads import do_claim_attachments from zerver.actions.users import do_deactivate_user -from zerver.lib.actions import do_set_realm_property from zerver.lib.avatar import avatar_url from zerver.lib.exceptions import JsonableError from zerver.lib.mention import MentionBackend, MentionData diff --git a/zerver/tests/test_message_send.py b/zerver/tests/test_message_send.py index b63d934595..32c1ba5f35 100644 --- a/zerver/tests/test_message_send.py +++ b/zerver/tests/test_message_send.py @@ -26,9 +26,10 @@ from zerver.actions.message_send import ( internal_send_stream_message_by_name, send_rate_limited_pm_notification_to_bot_owner, ) +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.streams import do_change_stream_post_policy from zerver.actions.users import do_change_can_forge_sender, do_deactivate_user -from zerver.lib.actions import do_add_realm_domain, do_create_realm, do_set_realm_property +from zerver.lib.actions import do_add_realm_domain, do_create_realm from zerver.lib.addressee import Addressee from zerver.lib.cache import cache_delete, get_stream_cache_key from zerver.lib.exceptions import JsonableError diff --git a/zerver/tests/test_realm.py b/zerver/tests/test_realm.py index ec47ad78ad..1d4e75c1aa 100644 --- a/zerver/tests/test_realm.py +++ b/zerver/tests/test_realm.py @@ -9,19 +9,18 @@ from django.conf import settings from django.utils.timezone import now as timezone_now from confirmation.models import Confirmation, create_confirmation_link -from zerver.actions.streams import do_deactivate_stream -from zerver.lib.actions import ( +from zerver.actions.realm_settings import ( do_add_deactivated_redirect, do_change_realm_org_type, do_change_realm_plan_type, - do_change_realm_subdomain, - do_create_realm, do_deactivate_realm, do_scrub_realm, do_send_realm_reactivation_email, do_set_realm_property, do_set_realm_user_default_setting, ) +from zerver.actions.streams import do_deactivate_stream +from zerver.lib.actions import do_change_realm_subdomain, do_create_realm from zerver.lib.realm_description import get_realm_rendered_description, get_realm_text_description from zerver.lib.send_email import send_future_email from zerver.lib.streams import create_stream_if_needed diff --git a/zerver/tests/test_realm_domains.py b/zerver/tests/test_realm_domains.py index cd932937d4..791aeca805 100644 --- a/zerver/tests/test_realm_domains.py +++ b/zerver/tests/test_realm_domains.py @@ -2,13 +2,9 @@ import orjson from django.core.exceptions import ValidationError from django.db.utils import IntegrityError +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.users import do_change_user_role -from zerver.lib.actions import ( - do_change_realm_domain, - do_create_realm, - do_remove_realm_domain, - do_set_realm_property, -) +from zerver.lib.actions import do_change_realm_domain, do_create_realm, do_remove_realm_domain from zerver.lib.domains import validate_domain from zerver.lib.email_validation import email_allowed_for_realm from zerver.lib.test_classes import ZulipTestCase diff --git a/zerver/tests/test_realm_emoji.py b/zerver/tests/test_realm_emoji.py index cb6a07a75b..c5f5308d81 100644 --- a/zerver/tests/test_realm_emoji.py +++ b/zerver/tests/test_realm_emoji.py @@ -2,8 +2,9 @@ from unittest import mock from zerver.actions.create_user import do_create_user from zerver.actions.realm_emoji import check_add_realm_emoji +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.users import do_change_user_role -from zerver.lib.actions import do_create_realm, do_set_realm_property +from zerver.lib.actions import do_create_realm from zerver.lib.exceptions import JsonableError from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_helpers import get_test_image_file diff --git a/zerver/tests/test_retention.py b/zerver/tests/test_retention.py index 5f6abf49cd..bc3d7200e9 100644 --- a/zerver/tests/test_retention.py +++ b/zerver/tests/test_retention.py @@ -7,8 +7,9 @@ from django.utils.timezone import now as timezone_now from zerver.actions.message_edit import do_delete_messages from zerver.actions.message_send import internal_send_private_message +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.submessage import do_add_submessage -from zerver.lib.actions import do_create_realm, do_set_realm_property +from zerver.lib.actions import do_create_realm from zerver.lib.retention import ( archive_messages, clean_archived_data, diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index b76eb9322e..ee0632941d 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -36,18 +36,17 @@ from zerver.actions.invites import ( do_get_invites_controlled_by_user, do_invite_users, ) +from zerver.actions.realm_settings import ( + do_deactivate_realm, + do_set_realm_property, + do_set_realm_user_default_setting, +) from zerver.actions.user_settings import do_change_full_name from zerver.actions.users import change_user_is_active, do_change_user_role, do_deactivate_user 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 ( - do_change_realm_subdomain, - do_create_realm, - do_deactivate_realm, - do_set_realm_property, - do_set_realm_user_default_setting, -) +from zerver.lib.actions import do_change_realm_subdomain, do_create_realm from zerver.lib.email_notifications import enqueue_welcome_emails, followup_day2_email_delay from zerver.lib.initial_password import initial_password from zerver.lib.mobile_auth_otp import ( diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index d3dd4c4a5e..ced52aecf5 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -23,6 +23,7 @@ from zerver.actions.default_streams import ( get_default_streams_for_realm, lookup_default_stream_groups, ) +from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property from zerver.actions.streams import ( bulk_add_subscriptions, bulk_remove_subscriptions, @@ -31,12 +32,7 @@ from zerver.actions.streams import ( do_deactivate_stream, ) from zerver.actions.users import do_change_user_role, do_deactivate_user -from zerver.lib.actions import ( - do_change_realm_plan_type, - do_create_realm, - do_set_realm_property, - get_topic_messages, -) +from zerver.lib.actions import do_create_realm, get_topic_messages from zerver.lib.exceptions import JsonableError from zerver.lib.message import UnreadStreamInfo, aggregate_unread_data, get_raw_unread_data from zerver.lib.response import json_success diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index a769f1c695..dea2ba5624 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -22,9 +22,10 @@ import zerver.lib.upload from zerver.actions.message_send import internal_send_private_message from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_logo import do_change_logo_source +from zerver.actions.realm_settings import do_change_realm_plan_type, do_set_realm_property from zerver.actions.uploads import do_delete_old_unclaimed_attachments from zerver.actions.user_settings import do_delete_avatar_image -from zerver.lib.actions import do_change_realm_plan_type, do_create_realm, do_set_realm_property +from zerver.lib.actions import do_create_realm from zerver.lib.avatar import avatar_url, get_avatar_field from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.cache import cache_get, get_realm_used_upload_space_cache_key diff --git a/zerver/tests/test_user_groups.py b/zerver/tests/test_user_groups.py index 3725be0f8e..db277b4a59 100644 --- a/zerver/tests/test_user_groups.py +++ b/zerver/tests/test_user_groups.py @@ -5,8 +5,8 @@ from unittest import mock import orjson from django.utils.timezone import now as timezone_now +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.user_groups import promote_new_full_members -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_helpers import most_recent_usermessage diff --git a/zerver/tests/test_users.py b/zerver/tests/test_users.py index dccda72d6c..52ca5b0b39 100644 --- a/zerver/tests/test_users.py +++ b/zerver/tests/test_users.py @@ -16,6 +16,7 @@ from zerver.actions.create_user import do_create_user, do_reactivate_user from zerver.actions.invites import do_create_multiuse_invite_link, do_invite_users from zerver.actions.message_send import get_recipient_info from zerver.actions.muted_users import do_mute_user +from zerver.actions.realm_settings import do_set_realm_property from zerver.actions.users import ( change_user_is_active, do_change_can_create_users, @@ -23,7 +24,6 @@ from zerver.actions.users import ( do_deactivate_user, do_delete_user, ) -from zerver.lib.actions import do_set_realm_property from zerver.lib.avatar import avatar_url, get_gravatar_url from zerver.lib.bulk_create import create_users from zerver.lib.create_user import copy_default_settings diff --git a/zerver/views/development/email_log.py b/zerver/views/development/email_log.py index 286fd10bfd..013ba74dde 100755 --- a/zerver/views/development/email_log.py +++ b/zerver/views/development/email_log.py @@ -9,9 +9,9 @@ from django.shortcuts import redirect, render from django.views.decorators.http import require_safe from confirmation.models import Confirmation, confirmation_url +from zerver.actions.realm_settings import do_send_realm_reactivation_email from zerver.actions.user_settings import do_change_user_delivery_email from zerver.actions.users import change_user_is_active -from zerver.lib.actions import do_send_realm_reactivation_email from zerver.lib.email_notifications import enqueue_welcome_emails from zerver.lib.response import json_success from zerver.models import Realm, get_realm, get_realm_stream, get_user_by_delivery_email diff --git a/zerver/views/realm.py b/zerver/views/realm.py index 322bdec9ea..177606e449 100644 --- a/zerver/views/realm.py +++ b/zerver/views/realm.py @@ -7,10 +7,7 @@ from django.utils.translation import gettext as _ from django.views.decorators.http import require_safe from confirmation.models import Confirmation, ConfirmationKeyException, get_object_from_key -from zerver.decorator import require_realm_admin, require_realm_owner -from zerver.forms import check_subdomain_available as check_subdomain -from zerver.lib.actions import ( - do_change_realm_subdomain, +from zerver.actions.realm_settings import ( do_deactivate_realm, do_reactivate_realm, do_set_realm_authentication_methods, @@ -20,6 +17,9 @@ from zerver.lib.actions import ( do_set_realm_signup_notifications_stream, do_set_realm_user_default_setting, ) +from zerver.decorator import require_realm_admin, require_realm_owner +from zerver.forms import check_subdomain_available as check_subdomain +from zerver.lib.actions import do_change_realm_subdomain from zerver.lib.exceptions import JsonableError, OrganizationOwnerRequired from zerver.lib.i18n import get_available_language_codes from zerver.lib.message import parse_message_content_delete_limit