diff --git a/tools/generate-integration-docs-screenshot b/tools/generate-integration-docs-screenshot index 58cfc18d06..5383b714e0 100755 --- a/tools/generate-integration-docs-screenshot +++ b/tools/generate-integration-docs-screenshot @@ -33,12 +33,8 @@ import zulip from scripts.lib.zulip_tools import BOLDRED, ENDC from tools.lib.test_script import prepare_puppeteer_run -from zerver.lib.actions import ( - bulk_add_subscriptions, - do_change_avatar_fields, - do_create_user, - notify_created_bot, -) +from zerver.actions.user_settings import do_change_avatar_fields +from zerver.lib.actions import bulk_add_subscriptions, do_create_user, notify_created_bot from zerver.lib.integrations import ( DOC_SCREENSHOT_CONFIG, INTEGRATIONS, diff --git a/zerver/actions/user_settings.py b/zerver/actions/user_settings.py new file mode 100644 index 0000000000..92b68131a6 --- /dev/null +++ b/zerver/actions/user_settings.py @@ -0,0 +1,449 @@ +import datetime +from typing import Optional, Union + +import orjson +from django.db import transaction +from django.db.models import F +from django.utils.timezone import now as timezone_now + +from confirmation.models import Confirmation, create_confirmation_link +from zerver.lib.avatar import avatar_url +from zerver.lib.cache import ( + cache_delete, + delete_user_profile_caches, + user_profile_by_api_key_cache_key, +) +from zerver.lib.i18n import get_language_name +from zerver.lib.queue import queue_json_publish +from zerver.lib.send_email import FromAddress, clear_scheduled_emails, send_email +from zerver.lib.timezone import canonicalize_timezone +from zerver.lib.upload import delete_avatar_image +from zerver.lib.users import check_bot_name_available, check_full_name +from zerver.lib.utils import generate_api_key +from zerver.models import ( + Draft, + EmailChangeStatus, + RealmAuditLog, + ScheduledEmail, + ScheduledMessageNotificationEmail, + UserProfile, + active_user_ids, + bot_owner_user_ids, +) +from zerver.tornado.django_api import send_event + + +def send_user_email_update_event(user_profile: UserProfile) -> None: + payload = dict(user_id=user_profile.id, new_email=user_profile.email) + event = dict(type="realm_user", op="update", person=payload) + transaction.on_commit( + lambda: send_event( + user_profile.realm, + event, + active_user_ids(user_profile.realm_id), + ) + ) + + +@transaction.atomic(savepoint=False) +def do_change_user_delivery_email(user_profile: UserProfile, new_email: str) -> None: + delete_user_profile_caches([user_profile]) + + user_profile.delivery_email = new_email + if user_profile.email_address_is_realm_public(): + user_profile.email = new_email + user_profile.save(update_fields=["email", "delivery_email"]) + else: + user_profile.save(update_fields=["delivery_email"]) + + # We notify just the target user (and eventually org admins, only + # when email_address_visibility=EMAIL_ADDRESS_VISIBILITY_ADMINS) + # about their new delivery email, since that field is private. + payload = dict(user_id=user_profile.id, delivery_email=new_email) + event = dict(type="realm_user", op="update", person=payload) + transaction.on_commit(lambda: send_event(user_profile.realm, event, [user_profile.id])) + + if user_profile.avatar_source == UserProfile.AVATAR_FROM_GRAVATAR: + # If the user is using Gravatar to manage their email address, + # their Gravatar just changed, and we need to notify other + # clients. + notify_avatar_url_change(user_profile) + + if user_profile.email_address_is_realm_public(): + # Additionally, if we're also changing the publicly visible + # email, we send a new_email event as well. + send_user_email_update_event(user_profile) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=user_profile.realm, + acting_user=user_profile, + modified_user=user_profile, + event_type=RealmAuditLog.USER_EMAIL_CHANGED, + event_time=event_time, + ) + + +def do_start_email_change_process(user_profile: UserProfile, new_email: str) -> None: + old_email = user_profile.delivery_email + obj = EmailChangeStatus.objects.create( + new_email=new_email, + old_email=old_email, + user_profile=user_profile, + realm=user_profile.realm, + ) + + activation_url = create_confirmation_link(obj, Confirmation.EMAIL_CHANGE) + from zerver.context_processors import common_context + + context = common_context(user_profile) + context.update( + old_email=old_email, + new_email=new_email, + activate_url=activation_url, + ) + language = user_profile.default_language + send_email( + "zerver/emails/confirm_new_email", + to_emails=[new_email], + from_name=FromAddress.security_email_from_name(language=language), + from_address=FromAddress.tokenized_no_reply_address(), + language=language, + context=context, + realm=user_profile.realm, + ) + + +def do_change_password(user_profile: UserProfile, password: str, commit: bool = True) -> None: + user_profile.set_password(password) + if commit: + user_profile.save(update_fields=["password"]) + + # Imported here to prevent import cycles + from zproject.backends import RateLimitedAuthenticationByUsername + + RateLimitedAuthenticationByUsername(user_profile.delivery_email).clear_history() + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=user_profile.realm, + acting_user=user_profile, + modified_user=user_profile, + event_type=RealmAuditLog.USER_PASSWORD_CHANGED, + event_time=event_time, + ) + + +def do_change_full_name( + user_profile: UserProfile, full_name: str, acting_user: Optional[UserProfile] +) -> None: + old_name = user_profile.full_name + user_profile.full_name = full_name + user_profile.save(update_fields=["full_name"]) + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=user_profile.realm, + acting_user=acting_user, + modified_user=user_profile, + event_type=RealmAuditLog.USER_FULL_NAME_CHANGED, + event_time=event_time, + extra_data=old_name, + ) + payload = dict(user_id=user_profile.id, full_name=user_profile.full_name) + send_event( + user_profile.realm, + dict(type="realm_user", op="update", person=payload), + active_user_ids(user_profile.realm_id), + ) + if user_profile.is_bot: + send_event( + user_profile.realm, + dict(type="realm_bot", op="update", bot=payload), + bot_owner_user_ids(user_profile), + ) + + +def check_change_full_name( + user_profile: UserProfile, full_name_raw: str, acting_user: Optional[UserProfile] +) -> str: + """Verifies that the user's proposed full name is valid. The caller + is responsible for checking check permissions. Returns the new + full name, which may differ from what was passed in (because this + function strips whitespace).""" + new_full_name = check_full_name(full_name_raw) + do_change_full_name(user_profile, new_full_name, acting_user) + return new_full_name + + +def check_change_bot_full_name( + user_profile: UserProfile, full_name_raw: str, acting_user: UserProfile +) -> None: + new_full_name = check_full_name(full_name_raw) + + if new_full_name == user_profile.full_name: + # Our web app will try to patch full_name even if the user didn't + # modify the name in the form. We just silently ignore those + # situations. + return + + check_bot_name_available( + realm_id=user_profile.realm_id, + full_name=new_full_name, + ) + do_change_full_name(user_profile, new_full_name, acting_user) + + +@transaction.atomic(durable=True) +def do_change_tos_version(user_profile: UserProfile, tos_version: str) -> None: + user_profile.tos_version = tos_version + user_profile.save(update_fields=["tos_version"]) + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=user_profile.realm, + acting_user=user_profile, + modified_user=user_profile, + event_type=RealmAuditLog.USER_TERMS_OF_SERVICE_VERSION_CHANGED, + event_time=event_time, + ) + + +def do_regenerate_api_key(user_profile: UserProfile, acting_user: UserProfile) -> str: + old_api_key = user_profile.api_key + new_api_key = generate_api_key() + user_profile.api_key = new_api_key + user_profile.save(update_fields=["api_key"]) + + # We need to explicitly delete the old API key from our caches, + # because the on-save handler for flushing the UserProfile object + # in zerver/lib/cache.py only has access to the new API key. + cache_delete(user_profile_by_api_key_cache_key(old_api_key)) + + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=user_profile.realm, + acting_user=acting_user, + modified_user=user_profile, + event_type=RealmAuditLog.USER_API_KEY_CHANGED, + event_time=event_time, + ) + + if user_profile.is_bot: + send_event( + user_profile.realm, + dict( + type="realm_bot", + op="update", + bot=dict( + user_id=user_profile.id, + api_key=new_api_key, + ), + ), + bot_owner_user_ids(user_profile), + ) + + event = {"type": "clear_push_device_tokens", "user_profile_id": user_profile.id} + queue_json_publish("deferred_work", event) + + return new_api_key + + +def notify_avatar_url_change(user_profile: UserProfile) -> None: + if user_profile.is_bot: + bot_event = dict( + type="realm_bot", + op="update", + bot=dict( + user_id=user_profile.id, + avatar_url=avatar_url(user_profile), + ), + ) + transaction.on_commit( + lambda: send_event( + user_profile.realm, + bot_event, + bot_owner_user_ids(user_profile), + ) + ) + + payload = dict( + avatar_source=user_profile.avatar_source, + avatar_url=avatar_url(user_profile), + avatar_url_medium=avatar_url(user_profile, medium=True), + avatar_version=user_profile.avatar_version, + # Even clients using client_gravatar don't need the email, + # since we're sending the URL anyway. + user_id=user_profile.id, + ) + + event = dict(type="realm_user", op="update", person=payload) + transaction.on_commit( + lambda: send_event( + user_profile.realm, + event, + active_user_ids(user_profile.realm_id), + ) + ) + + +@transaction.atomic(savepoint=False) +def do_change_avatar_fields( + user_profile: UserProfile, + avatar_source: str, + skip_notify: bool = False, + *, + acting_user: Optional[UserProfile], +) -> None: + user_profile.avatar_source = avatar_source + user_profile.avatar_version += 1 + user_profile.save(update_fields=["avatar_source", "avatar_version"]) + event_time = timezone_now() + RealmAuditLog.objects.create( + realm=user_profile.realm, + modified_user=user_profile, + event_type=RealmAuditLog.USER_AVATAR_SOURCE_CHANGED, + extra_data={"avatar_source": avatar_source}, + event_time=event_time, + acting_user=acting_user, + ) + + if not skip_notify: + notify_avatar_url_change(user_profile) + + +def do_delete_avatar_image(user: UserProfile, *, acting_user: Optional[UserProfile]) -> None: + do_change_avatar_fields(user, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=acting_user) + delete_avatar_image(user) + + +def update_scheduled_email_notifications_time( + user_profile: UserProfile, old_batching_period: int, new_batching_period: int +) -> None: + existing_scheduled_emails = ScheduledMessageNotificationEmail.objects.filter( + user_profile=user_profile + ) + + scheduled_timestamp_change = datetime.timedelta( + seconds=new_batching_period + ) - datetime.timedelta(seconds=old_batching_period) + + existing_scheduled_emails.update( + scheduled_timestamp=F("scheduled_timestamp") + scheduled_timestamp_change + ) + + +@transaction.atomic(durable=True) +def do_change_user_setting( + user_profile: UserProfile, + setting_name: str, + setting_value: Union[bool, str, int], + *, + acting_user: Optional[UserProfile], +) -> None: + old_value = getattr(user_profile, setting_name) + event_time = timezone_now() + + if setting_name == "timezone": + assert isinstance(setting_value, str) + setting_value = canonicalize_timezone(setting_value) + else: + property_type = UserProfile.property_types[setting_name] + assert isinstance(setting_value, property_type) + setattr(user_profile, setting_name, setting_value) + + # TODO: Move these database actions into a transaction.atomic block. + user_profile.save(update_fields=[setting_name]) + + if setting_name in UserProfile.notification_setting_types: + # Prior to all personal settings being managed by property_types, + # these were only created for notification settings. + # + # TODO: Start creating these for all settings, and do a + # backfilled=True migration. + RealmAuditLog.objects.create( + realm=user_profile.realm, + event_type=RealmAuditLog.USER_SETTING_CHANGED, + event_time=event_time, + acting_user=acting_user, + modified_user=user_profile, + extra_data=orjson.dumps( + { + RealmAuditLog.OLD_VALUE: old_value, + RealmAuditLog.NEW_VALUE: setting_value, + "property": setting_name, + } + ).decode(), + ) + # Disabling digest emails should clear a user's email queue + if setting_name == "enable_digest_emails" and not setting_value: + clear_scheduled_emails(user_profile.id, ScheduledEmail.DIGEST) + + if setting_name == "email_notifications_batching_period_seconds": + assert isinstance(old_value, int) + assert isinstance(setting_value, int) + update_scheduled_email_notifications_time(user_profile, old_value, setting_value) + + event = { + "type": "user_settings", + "op": "update", + "property": setting_name, + "value": setting_value, + } + if setting_name == "default_language": + assert isinstance(setting_value, str) + event["language_name"] = get_language_name(setting_value) + + transaction.on_commit(lambda: send_event(user_profile.realm, event, [user_profile.id])) + + if setting_name in UserProfile.notification_settings_legacy: + # This legacy event format is for backwards-compatibility with + # clients that don't support the new user_settings event type. + # We only send this for settings added before Feature level 89. + legacy_event = { + "type": "update_global_notifications", + "user": user_profile.email, + "notification_name": setting_name, + "setting": setting_value, + } + transaction.on_commit( + lambda: send_event(user_profile.realm, legacy_event, [user_profile.id]) + ) + + if setting_name in UserProfile.display_settings_legacy or setting_name == "timezone": + # This legacy event format is for backwards-compatibility with + # clients that don't support the new user_settings event type. + # We only send this for settings added before Feature level 89. + legacy_event = { + "type": "update_display_settings", + "user": user_profile.email, + "setting_name": setting_name, + "setting": setting_value, + } + if setting_name == "default_language": + assert isinstance(setting_value, str) + legacy_event["language_name"] = get_language_name(setting_value) + + transaction.on_commit( + lambda: send_event(user_profile.realm, legacy_event, [user_profile.id]) + ) + + # Updates to the time zone display setting are sent to all users + if setting_name == "timezone": + payload = dict( + email=user_profile.email, + user_id=user_profile.id, + timezone=canonicalize_timezone(user_profile.timezone), + ) + timezone_event = dict(type="realm_user", op="update", person=payload) + transaction.on_commit( + lambda: send_event( + user_profile.realm, + timezone_event, + active_user_ids(user_profile.realm_id), + ) + ) + + if setting_name == "enable_drafts_synchronization" and setting_value is False: + # Delete all of the drafts from the backend but don't send delete events + # for them since all that's happened is that we stopped syncing changes, + # not deleted every previously synced draft - to do that use the DELETE + # endpoint. + Draft.objects.filter(user_profile=user_profile).delete() diff --git a/zerver/forms.py b/zerver/forms.py index 669e62f76d..b08d0c74fe 100644 --- a/zerver/forms.py +++ b/zerver/forms.py @@ -19,8 +19,9 @@ from markupsafe import Markup as mark_safe from two_factor.forms import AuthenticationTokenForm as TwoFactorAuthenticationTokenForm from two_factor.utils import totp_digits +from zerver.actions.user_settings import do_change_password from zerver.decorator import rate_limit_request_by_ip -from zerver.lib.actions import do_change_password, email_not_system_bot +from zerver.lib.actions import email_not_system_bot from zerver.lib.email_validation import email_allowed_for_realm from zerver.lib.exceptions import JsonableError, RateLimited from zerver.lib.name_restrictions import is_disposable_domain, is_reserved_subdomain diff --git a/zerver/lib/actions.py b/zerver/lib/actions.py index 5470d09e05..5356a45184 100644 --- a/zerver/lib/actions.py +++ b/zerver/lib/actions.py @@ -46,6 +46,7 @@ from zerver.actions.user_groups import ( do_send_user_group_members_update_event, 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.user_topics import do_mute_topic, do_unmute_topic from zerver.actions.users import change_user_is_active, get_service_dicts_for_bot from zerver.lib import retention as retention @@ -58,12 +59,10 @@ from zerver.lib.cache import ( cache_delete_many, cache_set, cache_with_key, - delete_user_profile_caches, display_recipient_cache_key, flush_user_profile, get_stream_cache_key, to_dict_cache_key_id, - user_profile_by_api_key_cache_key, user_profile_delivery_email_cache_key, ) from zerver.lib.create_user import create_user, get_display_email_address @@ -78,7 +77,6 @@ from zerver.lib.exceptions import ( StreamWithIDDoesNotExistError, ZephyrMessageAlreadySentException, ) -from zerver.lib.i18n import get_language_name from zerver.lib.markdown import MessageRenderingResult, topic_links from zerver.lib.markdown import version as markdown_version from zerver.lib.mention import MentionBackend, MentionData, silent_mention_syntax_for_user @@ -103,9 +101,7 @@ from zerver.lib.recipient_users import recipient_for_user_profiles from zerver.lib.retention import move_messages_to_archive from zerver.lib.send_email import ( FromAddress, - clear_scheduled_emails, clear_scheduled_invitation_emails, - send_email, send_email_to_admins, ) from zerver.lib.server_initialization import create_internal_realm, server_initialized @@ -140,7 +136,6 @@ from zerver.lib.streams import ( from zerver.lib.string_validation import check_stream_name, check_stream_topic from zerver.lib.subscription_info import get_subscribers_query from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime -from zerver.lib.timezone import canonicalize_timezone from zerver.lib.topic import ( ORIG_TOPIC, RESOLVED_TOPIC_PREFIX, @@ -154,7 +149,6 @@ from zerver.lib.topic import ( update_messages_for_topic_edit, ) from zerver.lib.types import EditHistoryEvent -from zerver.lib.upload import delete_avatar_image from zerver.lib.user_counts import realm_user_count, realm_user_count_by_role from zerver.lib.user_groups import ( create_system_user_groups_for_realm, @@ -163,14 +157,8 @@ from zerver.lib.user_groups import ( from zerver.lib.user_message import UserMessageLite, bulk_insert_ums from zerver.lib.user_mutes import add_user_mute, get_muting_users, get_user_mutes from zerver.lib.user_topics import get_users_muting_topic, remove_topic_mute -from zerver.lib.users import ( - check_bot_name_available, - check_full_name, - format_user_row, - get_api_key, - user_profile_to_user_row, -) -from zerver.lib.utils import generate_api_key, log_statsd_event +from zerver.lib.users import format_user_row, get_api_key, user_profile_to_user_row +from zerver.lib.utils import log_statsd_event from zerver.lib.validator import check_widget_content from zerver.lib.widget import do_widget_post_save_actions, is_widget_message from zerver.models import ( @@ -179,8 +167,6 @@ from zerver.models import ( Client, DefaultStream, DefaultStreamGroup, - Draft, - EmailChangeStatus, Message, MutedUser, PreregistrationUser, @@ -192,7 +178,6 @@ from zerver.models import ( Recipient, ScheduledEmail, ScheduledMessage, - ScheduledMessageNotificationEmail, Stream, Subscription, UserGroup, @@ -1198,87 +1183,6 @@ def do_deactivate_stream( ) -def send_user_email_update_event(user_profile: UserProfile) -> None: - payload = dict(user_id=user_profile.id, new_email=user_profile.email) - event = dict(type="realm_user", op="update", person=payload) - transaction.on_commit( - lambda: send_event( - user_profile.realm, - event, - active_user_ids(user_profile.realm_id), - ) - ) - - -@transaction.atomic(savepoint=False) -def do_change_user_delivery_email(user_profile: UserProfile, new_email: str) -> None: - delete_user_profile_caches([user_profile]) - - user_profile.delivery_email = new_email - if user_profile.email_address_is_realm_public(): - user_profile.email = new_email - user_profile.save(update_fields=["email", "delivery_email"]) - else: - user_profile.save(update_fields=["delivery_email"]) - - # We notify just the target user (and eventually org admins, only - # when email_address_visibility=EMAIL_ADDRESS_VISIBILITY_ADMINS) - # about their new delivery email, since that field is private. - payload = dict(user_id=user_profile.id, delivery_email=new_email) - event = dict(type="realm_user", op="update", person=payload) - transaction.on_commit(lambda: send_event(user_profile.realm, event, [user_profile.id])) - - if user_profile.avatar_source == UserProfile.AVATAR_FROM_GRAVATAR: - # If the user is using Gravatar to manage their email address, - # their Gravatar just changed, and we need to notify other - # clients. - notify_avatar_url_change(user_profile) - - if user_profile.email_address_is_realm_public(): - # Additionally, if we're also changing the publicly visible - # email, we send a new_email event as well. - send_user_email_update_event(user_profile) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=user_profile.realm, - acting_user=user_profile, - modified_user=user_profile, - event_type=RealmAuditLog.USER_EMAIL_CHANGED, - event_time=event_time, - ) - - -def do_start_email_change_process(user_profile: UserProfile, new_email: str) -> None: - old_email = user_profile.delivery_email - obj = EmailChangeStatus.objects.create( - new_email=new_email, - old_email=old_email, - user_profile=user_profile, - realm=user_profile.realm, - ) - - activation_url = create_confirmation_link(obj, Confirmation.EMAIL_CHANGE) - from zerver.context_processors import common_context - - context = common_context(user_profile) - context.update( - old_email=old_email, - new_email=new_email, - activate_url=activation_url, - ) - language = user_profile.default_language - send_email( - "zerver/emails/confirm_new_email", - to_emails=[new_email], - from_name=FromAddress.security_email_from_name(language=language), - from_address=FromAddress.tokenized_no_reply_address(), - language=language, - context=context, - realm=user_profile.realm, - ) - - def compute_irc_user_fullname(email: str) -> str: return email.split("@")[0] + " (IRC)" @@ -3708,84 +3612,6 @@ def do_change_subscription_property( send_event(user_profile.realm, event, [user_profile.id]) -def do_change_password(user_profile: UserProfile, password: str, commit: bool = True) -> None: - user_profile.set_password(password) - if commit: - user_profile.save(update_fields=["password"]) - - # Imported here to prevent import cycles - from zproject.backends import RateLimitedAuthenticationByUsername - - RateLimitedAuthenticationByUsername(user_profile.delivery_email).clear_history() - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=user_profile.realm, - acting_user=user_profile, - modified_user=user_profile, - event_type=RealmAuditLog.USER_PASSWORD_CHANGED, - event_time=event_time, - ) - - -def do_change_full_name( - user_profile: UserProfile, full_name: str, acting_user: Optional[UserProfile] -) -> None: - old_name = user_profile.full_name - user_profile.full_name = full_name - user_profile.save(update_fields=["full_name"]) - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=user_profile.realm, - acting_user=acting_user, - modified_user=user_profile, - event_type=RealmAuditLog.USER_FULL_NAME_CHANGED, - event_time=event_time, - extra_data=old_name, - ) - payload = dict(user_id=user_profile.id, full_name=user_profile.full_name) - send_event( - user_profile.realm, - dict(type="realm_user", op="update", person=payload), - active_user_ids(user_profile.realm_id), - ) - if user_profile.is_bot: - send_event( - user_profile.realm, - dict(type="realm_bot", op="update", bot=payload), - bot_owner_user_ids(user_profile), - ) - - -def check_change_full_name( - user_profile: UserProfile, full_name_raw: str, acting_user: Optional[UserProfile] -) -> str: - """Verifies that the user's proposed full name is valid. The caller - is responsible for checking check permissions. Returns the new - full name, which may differ from what was passed in (because this - function strips whitespace).""" - new_full_name = check_full_name(full_name_raw) - do_change_full_name(user_profile, new_full_name, acting_user) - return new_full_name - - -def check_change_bot_full_name( - user_profile: UserProfile, full_name_raw: str, acting_user: UserProfile -) -> None: - new_full_name = check_full_name(full_name_raw) - - if new_full_name == user_profile.full_name: - # Our web app will try to patch full_name even if the user didn't - # modify the name in the form. We just silently ignore those - # situations. - return - - check_bot_name_available( - realm_id=user_profile.realm_id, - full_name=new_full_name, - ) - do_change_full_name(user_profile, new_full_name, acting_user) - - @transaction.atomic(durable=True) def do_change_bot_owner( user_profile: UserProfile, bot_owner: UserProfile, acting_user: UserProfile @@ -3865,128 +3691,6 @@ def do_change_bot_owner( ) -@transaction.atomic(durable=True) -def do_change_tos_version(user_profile: UserProfile, tos_version: str) -> None: - user_profile.tos_version = tos_version - user_profile.save(update_fields=["tos_version"]) - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=user_profile.realm, - acting_user=user_profile, - modified_user=user_profile, - event_type=RealmAuditLog.USER_TERMS_OF_SERVICE_VERSION_CHANGED, - event_time=event_time, - ) - - -def do_regenerate_api_key(user_profile: UserProfile, acting_user: UserProfile) -> str: - old_api_key = user_profile.api_key - new_api_key = generate_api_key() - user_profile.api_key = new_api_key - user_profile.save(update_fields=["api_key"]) - - # We need to explicitly delete the old API key from our caches, - # because the on-save handler for flushing the UserProfile object - # in zerver/lib/cache.py only has access to the new API key. - cache_delete(user_profile_by_api_key_cache_key(old_api_key)) - - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=user_profile.realm, - acting_user=acting_user, - modified_user=user_profile, - event_type=RealmAuditLog.USER_API_KEY_CHANGED, - event_time=event_time, - ) - - if user_profile.is_bot: - send_event( - user_profile.realm, - dict( - type="realm_bot", - op="update", - bot=dict( - user_id=user_profile.id, - api_key=new_api_key, - ), - ), - bot_owner_user_ids(user_profile), - ) - - event = {"type": "clear_push_device_tokens", "user_profile_id": user_profile.id} - queue_json_publish("deferred_work", event) - - return new_api_key - - -def notify_avatar_url_change(user_profile: UserProfile) -> None: - if user_profile.is_bot: - bot_event = dict( - type="realm_bot", - op="update", - bot=dict( - user_id=user_profile.id, - avatar_url=avatar_url(user_profile), - ), - ) - transaction.on_commit( - lambda: send_event( - user_profile.realm, - bot_event, - bot_owner_user_ids(user_profile), - ) - ) - - payload = dict( - avatar_source=user_profile.avatar_source, - avatar_url=avatar_url(user_profile), - avatar_url_medium=avatar_url(user_profile, medium=True), - avatar_version=user_profile.avatar_version, - # Even clients using client_gravatar don't need the email, - # since we're sending the URL anyway. - user_id=user_profile.id, - ) - - event = dict(type="realm_user", op="update", person=payload) - transaction.on_commit( - lambda: send_event( - user_profile.realm, - event, - active_user_ids(user_profile.realm_id), - ) - ) - - -@transaction.atomic(savepoint=False) -def do_change_avatar_fields( - user_profile: UserProfile, - avatar_source: str, - skip_notify: bool = False, - *, - acting_user: Optional[UserProfile], -) -> None: - user_profile.avatar_source = avatar_source - user_profile.avatar_version += 1 - user_profile.save(update_fields=["avatar_source", "avatar_version"]) - event_time = timezone_now() - RealmAuditLog.objects.create( - realm=user_profile.realm, - modified_user=user_profile, - event_type=RealmAuditLog.USER_AVATAR_SOURCE_CHANGED, - extra_data={"avatar_source": avatar_source}, - event_time=event_time, - acting_user=acting_user, - ) - - if not skip_notify: - notify_avatar_url_change(user_profile) - - -def do_delete_avatar_image(user: UserProfile, *, acting_user: Optional[UserProfile]) -> None: - do_change_avatar_fields(user, UserProfile.AVATAR_FROM_GRAVATAR, acting_user=acting_user) - delete_avatar_image(user) - - @transaction.atomic(durable=True) def do_change_realm_org_type( realm: Realm, @@ -4828,141 +4532,6 @@ def do_create_realm( return realm -def update_scheduled_email_notifications_time( - user_profile: UserProfile, old_batching_period: int, new_batching_period: int -) -> None: - existing_scheduled_emails = ScheduledMessageNotificationEmail.objects.filter( - user_profile=user_profile - ) - - scheduled_timestamp_change = datetime.timedelta( - seconds=new_batching_period - ) - datetime.timedelta(seconds=old_batching_period) - - existing_scheduled_emails.update( - scheduled_timestamp=F("scheduled_timestamp") + scheduled_timestamp_change - ) - - -@transaction.atomic(durable=True) -def do_change_user_setting( - user_profile: UserProfile, - setting_name: str, - setting_value: Union[bool, str, int], - *, - acting_user: Optional[UserProfile], -) -> None: - old_value = getattr(user_profile, setting_name) - event_time = timezone_now() - - if setting_name == "timezone": - assert isinstance(setting_value, str) - setting_value = canonicalize_timezone(setting_value) - else: - property_type = UserProfile.property_types[setting_name] - assert isinstance(setting_value, property_type) - setattr(user_profile, setting_name, setting_value) - - # TODO: Move these database actions into a transaction.atomic block. - user_profile.save(update_fields=[setting_name]) - - if setting_name in UserProfile.notification_setting_types: - # Prior to all personal settings being managed by property_types, - # these were only created for notification settings. - # - # TODO: Start creating these for all settings, and do a - # backfilled=True migration. - RealmAuditLog.objects.create( - realm=user_profile.realm, - event_type=RealmAuditLog.USER_SETTING_CHANGED, - event_time=event_time, - acting_user=acting_user, - modified_user=user_profile, - extra_data=orjson.dumps( - { - RealmAuditLog.OLD_VALUE: old_value, - RealmAuditLog.NEW_VALUE: setting_value, - "property": setting_name, - } - ).decode(), - ) - # Disabling digest emails should clear a user's email queue - if setting_name == "enable_digest_emails" and not setting_value: - clear_scheduled_emails(user_profile.id, ScheduledEmail.DIGEST) - - if setting_name == "email_notifications_batching_period_seconds": - assert isinstance(old_value, int) - assert isinstance(setting_value, int) - update_scheduled_email_notifications_time(user_profile, old_value, setting_value) - - event = { - "type": "user_settings", - "op": "update", - "property": setting_name, - "value": setting_value, - } - if setting_name == "default_language": - assert isinstance(setting_value, str) - event["language_name"] = get_language_name(setting_value) - - transaction.on_commit(lambda: send_event(user_profile.realm, event, [user_profile.id])) - - if setting_name in UserProfile.notification_settings_legacy: - # This legacy event format is for backwards-compatibility with - # clients that don't support the new user_settings event type. - # We only send this for settings added before Feature level 89. - legacy_event = { - "type": "update_global_notifications", - "user": user_profile.email, - "notification_name": setting_name, - "setting": setting_value, - } - transaction.on_commit( - lambda: send_event(user_profile.realm, legacy_event, [user_profile.id]) - ) - - if setting_name in UserProfile.display_settings_legacy or setting_name == "timezone": - # This legacy event format is for backwards-compatibility with - # clients that don't support the new user_settings event type. - # We only send this for settings added before Feature level 89. - legacy_event = { - "type": "update_display_settings", - "user": user_profile.email, - "setting_name": setting_name, - "setting": setting_value, - } - if setting_name == "default_language": - assert isinstance(setting_value, str) - legacy_event["language_name"] = get_language_name(setting_value) - - transaction.on_commit( - lambda: send_event(user_profile.realm, legacy_event, [user_profile.id]) - ) - - # Updates to the time zone display setting are sent to all users - if setting_name == "timezone": - payload = dict( - email=user_profile.email, - user_id=user_profile.id, - timezone=canonicalize_timezone(user_profile.timezone), - ) - timezone_event = dict(type="realm_user", op="update", person=payload) - transaction.on_commit( - lambda: send_event( - user_profile.realm, - timezone_event, - active_user_ids(user_profile.realm_id), - ) - ) - - if setting_name == "enable_drafts_synchronization" and setting_value is False: - # Delete all of the drafts from the backend but don't send delete events - # for them since all that's happened is that we stopped syncing changes, - # not deleted every previously synced draft - to do that use the DELETE - # endpoint. - Draft.objects.filter(user_profile=user_profile).delete() - - 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. diff --git a/zerver/lib/create_user.py b/zerver/lib/create_user.py index a1be5de566..f154bd1988 100644 --- a/zerver/lib/create_user.py +++ b/zerver/lib/create_user.py @@ -47,7 +47,7 @@ def copy_default_settings( target_profile.save() if settings_source.avatar_source == UserProfile.AVATAR_FROM_USER: - from zerver.lib.actions import do_change_avatar_fields + from zerver.actions.user_settings import do_change_avatar_fields do_change_avatar_fields( target_profile, diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index c332a91c10..fd8714d30e 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -16,7 +16,8 @@ from psycopg2.extras import execute_values from psycopg2.sql import SQL, Identifier from analytics.models import RealmCount, StreamCount, UserCount -from zerver.lib.actions import do_change_avatar_fields, 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/scim.py b/zerver/lib/scim.py index c19f438ed9..b45f07b7f6 100644 --- a/zerver/lib/scim.py +++ b/zerver/lib/scim.py @@ -9,13 +9,9 @@ from django.http import HttpRequest from django_scim.adapters import SCIMUser from scim2_filter_parser.attr_paths import AttrPath +from zerver.actions.user_settings import check_change_full_name, do_change_user_delivery_email from zerver.actions.users import do_deactivate_user -from zerver.lib.actions import ( - check_change_full_name, - do_change_user_delivery_email, - do_create_user, - do_reactivate_user, -) +from zerver.lib.actions import do_create_user, do_reactivate_user from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm from zerver.lib.request import RequestNotes from zerver.lib.subdomains import get_subdomain diff --git a/zerver/lib/zcommand.py b/zerver/lib/zcommand.py index a80f44b356..8041f1e5da 100644 --- a/zerver/lib/zcommand.py +++ b/zerver/lib/zcommand.py @@ -2,7 +2,7 @@ from typing import Any, Dict from django.utils.translation import gettext as _ -from zerver.lib.actions import do_change_user_setting +from zerver.actions.user_settings import do_change_user_setting from zerver.lib.exceptions import JsonableError from zerver.models import UserProfile diff --git a/zerver/management/commands/bulk_change_user_name.py b/zerver/management/commands/bulk_change_user_name.py index e7e2afef55..34bcf8d584 100644 --- a/zerver/management/commands/bulk_change_user_name.py +++ b/zerver/management/commands/bulk_change_user_name.py @@ -3,7 +3,7 @@ from typing import Any from django.core.management.base import CommandError -from zerver.lib.actions import do_change_full_name +from zerver.actions.user_settings import do_change_full_name from zerver.lib.management import ZulipBaseCommand diff --git a/zerver/management/commands/change_user_email.py b/zerver/management/commands/change_user_email.py index 84170430e6..59b1b3195b 100644 --- a/zerver/management/commands/change_user_email.py +++ b/zerver/management/commands/change_user_email.py @@ -1,7 +1,7 @@ from argparse import ArgumentParser from typing import Any -from zerver.lib.actions import do_change_user_delivery_email +from zerver.actions.user_settings import do_change_user_delivery_email from zerver.lib.management import ZulipBaseCommand diff --git a/zerver/tests/test_audit_log.py b/zerver/tests/test_audit_log.py index c9734a6b8e..2ff85c4fd9 100644 --- a/zerver/tests/test_audit_log.py +++ b/zerver/tests/test_audit_log.py @@ -8,29 +8,31 @@ from django.utils.timezone import now as timezone_now from analytics.models import StreamCount 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.user_settings import ( + do_change_avatar_fields, + do_change_password, + do_change_tos_version, + do_change_user_delivery_email, + do_change_user_setting, + do_regenerate_api_key, +) from zerver.actions.users import do_change_user_role, do_deactivate_user from zerver.lib.actions import ( bulk_add_subscriptions, bulk_remove_subscriptions, do_activate_mirror_dummy_user, do_add_realm_domain, - 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_password, do_change_realm_domain, do_change_subscription_property, - do_change_tos_version, - do_change_user_delivery_email, - do_change_user_setting, do_create_user, do_deactivate_realm, do_deactivate_stream, do_reactivate_realm, do_reactivate_user, - do_regenerate_api_key, do_remove_realm_domain, do_rename_stream, do_set_realm_authentication_methods, diff --git a/zerver/tests/test_auth_backends.py b/zerver/tests/test_auth_backends.py index 0fcf389517..fa766bb3b8 100644 --- a/zerver/tests/test_auth_backends.py +++ b/zerver/tests/test_auth_backends.py @@ -41,9 +41,9 @@ from social_django.strategy import DjangoStrategy from confirmation.models import Confirmation, create_confirmation_link from zerver.actions.invites import do_invite_users +from zerver.actions.user_settings import do_change_password from zerver.actions.users import change_user_is_active, do_deactivate_user from zerver.lib.actions import ( - do_change_password, do_create_realm, do_create_user, do_deactivate_realm, @@ -6122,7 +6122,7 @@ class TestZulipLDAPUserPopulator(ZulipLDAPTestCase): self.assertEqual(hamlet.full_name, "Full Name") def test_same_full_name(self) -> None: - with mock.patch("zerver.lib.actions.do_change_full_name") as fn: + with mock.patch("zerver.actions.user_settings.do_change_full_name") as fn: self.perform_ldap_sync(self.example_user("hamlet")) fn.assert_not_called() @@ -6239,7 +6239,7 @@ class TestZulipLDAPUserPopulator(ZulipLDAPTestCase): self.change_ldap_user_attr("hamlet", "cn", "Second Hamlet") expected_call_args = [hamlet2, "Second Hamlet", None] with self.settings(AUTH_LDAP_USER_ATTR_MAP={"full_name": "cn"}): - with mock.patch("zerver.lib.actions.do_change_full_name") as f: + with mock.patch("zerver.actions.user_settings.do_change_full_name") as f: self.perform_ldap_sync(hamlet2) f.assert_called_once_with(*expected_call_args) diff --git a/zerver/tests/test_email_change.py b/zerver/tests/test_email_change.py index 71032028b8..2e842f6a90 100644 --- a/zerver/tests/test_email_change.py +++ b/zerver/tests/test_email_change.py @@ -11,12 +11,9 @@ from confirmation.models import ( create_confirmation_link, generate_key, ) +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, - do_start_email_change_process, -) +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_notifications.py b/zerver/tests/test_email_notifications.py index 6d44c49622..274c8a3a72 100644 --- a/zerver/tests/test_email_notifications.py +++ b/zerver/tests/test_email_notifications.py @@ -15,8 +15,8 @@ from django.test import override_settings from django.utils.timezone import now as timezone_now from django_auth_ldap.config import LDAPSearch +from zerver.actions.user_settings import do_change_user_setting from zerver.actions.users import do_change_user_role -from zerver.lib.actions import do_change_user_setting from zerver.lib.email_notifications import ( enqueue_welcome_emails, fix_emojis, diff --git a/zerver/tests/test_events.py b/zerver/tests/test_events.py index 48b4ad1ab6..9e6b5fd0e2 100644 --- a/zerver/tests/test_events.py +++ b/zerver/tests/test_events.py @@ -58,6 +58,13 @@ from zerver.actions.user_groups import ( do_update_user_group_name, remove_members_from_user_group, ) +from zerver.actions.user_settings import ( + do_change_avatar_fields, + do_change_full_name, + do_change_user_delivery_email, + do_change_user_setting, + do_regenerate_api_key, +) from zerver.actions.user_topics import do_mute_topic, do_unmute_topic from zerver.actions.users import ( do_change_user_role, @@ -71,12 +78,10 @@ from zerver.lib.actions import ( bulk_remove_subscriptions, do_add_reaction, do_add_realm_domain, - 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_full_name, do_change_realm_domain, do_change_realm_plan_type, do_change_stream_description, @@ -84,15 +89,12 @@ from zerver.lib.actions import ( do_change_stream_permission, do_change_stream_post_policy, do_change_subscription_property, - do_change_user_delivery_email, - do_change_user_setting, do_create_user, do_deactivate_realm, do_deactivate_stream, do_delete_messages, do_mute_user, do_reactivate_user, - do_regenerate_api_key, do_remove_reaction, do_remove_realm_domain, do_rename_stream, diff --git a/zerver/tests/test_markdown.py b/zerver/tests/test_markdown.py index 5f745883e3..97de45ee67 100644 --- a/zerver/tests/test_markdown.py +++ b/zerver/tests/test_markdown.py @@ -12,8 +12,9 @@ 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.user_settings import do_change_user_setting from zerver.actions.users import change_user_is_active -from zerver.lib.actions import do_change_user_setting, do_create_realm, do_set_realm_property +from zerver.lib.actions import do_create_realm, do_set_realm_property 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_new_users.py b/zerver/tests/test_new_users.py index 76786ee74a..56ebe33c7c 100644 --- a/zerver/tests/test_new_users.py +++ b/zerver/tests/test_new_users.py @@ -8,7 +8,8 @@ from django.core import mail from django.test import override_settings from corporate.lib.stripe import get_latest_seat_count -from zerver.lib.actions import do_change_user_setting, notify_new_user +from zerver.actions.user_settings import do_change_user_setting +from zerver.lib.actions import notify_new_user from zerver.lib.initial_password import initial_password from zerver.lib.streams import create_stream_if_needed from zerver.lib.test_classes import ZulipTestCase diff --git a/zerver/tests/test_push_notifications.py b/zerver/tests/test_push_notifications.py index b993d9d083..4d962ff247 100644 --- a/zerver/tests/test_push_notifications.py +++ b/zerver/tests/test_push_notifications.py @@ -23,10 +23,10 @@ from requests.models import PreparedRequest from analytics.lib.counts import CountStat, LoggingCountStat from analytics.models import InstallationCount, RealmCount +from zerver.actions.user_settings import do_regenerate_api_key from zerver.lib.actions import ( do_delete_messages, do_mark_stream_messages_as_read, - do_regenerate_api_key, do_update_message_flags, ) from zerver.lib.avatar import absolute_avatar_url diff --git a/zerver/tests/test_queue_worker.py b/zerver/tests/test_queue_worker.py index e56899ec21..eb7751ae63 100644 --- a/zerver/tests/test_queue_worker.py +++ b/zerver/tests/test_queue_worker.py @@ -639,7 +639,7 @@ class WorkerTest(ZulipTestCase): with simulated_queue_client(fake_client): worker = queue_processors.ConfirmationEmailWorker() worker.setup() - with patch("zerver.lib.actions.send_email"), patch( + with patch("zerver.actions.user_settings.send_email"), patch( "zerver.worker.queue_processors.send_future_email" ) as send_mock: worker.start() diff --git a/zerver/tests/test_scim.py b/zerver/tests/test_scim.py index 48368d7f80..0a6d6c43dc 100644 --- a/zerver/tests/test_scim.py +++ b/zerver/tests/test_scim.py @@ -7,7 +7,7 @@ import orjson from django.conf import settings from django.http import HttpResponse -from zerver.lib.actions import do_change_full_name +from zerver.actions.user_settings import do_change_full_name from zerver.lib.test_classes import ZulipTestCase from zerver.models import SCIMClient, UserProfile, get_realm diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index 162dbb79fe..ca055df7c7 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -35,13 +35,13 @@ from zerver.actions.invites import ( do_get_invites_controlled_by_user, do_invite_users, ) +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 ( add_new_user_history, - do_change_full_name, do_change_realm_subdomain, do_create_realm, do_create_user, diff --git a/zerver/tests/test_upload.py b/zerver/tests/test_upload.py index 441e86d360..8393afc938 100644 --- a/zerver/tests/test_upload.py +++ b/zerver/tests/test_upload.py @@ -22,6 +22,7 @@ import zerver.lib.upload from zerver.actions.realm_icon import do_change_icon_source from zerver.actions.realm_logo import do_change_logo_source 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, @@ -1287,7 +1288,7 @@ class AvatarTest(UploadSerializeMixin, ZulipTestCase): self.assertTrue(os.path.isfile(avatar_original_path_id)) self.assertTrue(os.path.isfile(avatar_medium_path_id)) - zerver.lib.actions.do_delete_avatar_image(user, acting_user=user) + do_delete_avatar_image(user, acting_user=user) self.assertEqual(user.avatar_source, UserProfile.AVATAR_FROM_GRAVATAR) self.assertFalse(os.path.isfile(avatar_path_id)) @@ -2124,7 +2125,7 @@ class S3Test(ZulipTestCase): self.assertIsNotNone(bucket.Object(avatar_original_image_path_id)) self.assertIsNotNone(bucket.Object(avatar_medium_path_id)) - zerver.lib.actions.do_delete_avatar_image(user, acting_user=user) + do_delete_avatar_image(user, acting_user=user) self.assertEqual(user.avatar_source, UserProfile.AVATAR_FROM_GRAVATAR) diff --git a/zerver/views/development/email_log.py b/zerver/views/development/email_log.py index 213b938f0d..286fd10bfd 100755 --- a/zerver/views/development/email_log.py +++ b/zerver/views/development/email_log.py @@ -9,8 +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.user_settings import do_change_user_delivery_email from zerver.actions.users import change_user_is_active -from zerver.lib.actions import do_change_user_delivery_email, do_send_realm_reactivation_email +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/home.py b/zerver/views/home.py index 0c9b6b3893..882397d982 100644 --- a/zerver/views/home.py +++ b/zerver/views/home.py @@ -7,10 +7,10 @@ from django.http import HttpRequest, HttpResponse from django.shortcuts import redirect, render from django.utils.cache import patch_cache_control +from zerver.actions.user_settings import do_change_tos_version from zerver.context_processors import get_valid_realm_from_request from zerver.decorator import web_public_view, zulip_login_required from zerver.forms import ToSForm -from zerver.lib.actions import do_change_tos_version from zerver.lib.compatibility import is_outdated_desktop_app, is_unsupported_browser from zerver.lib.home import build_page_params_for_home_page_load, get_user_permission_info from zerver.lib.request import RequestNotes diff --git a/zerver/views/registration.py b/zerver/views/registration.py index fa4549fe47..6d0572d2ff 100644 --- a/zerver/views/registration.py +++ b/zerver/views/registration.py @@ -26,6 +26,11 @@ from confirmation.models import ( validate_key, ) from zerver.actions.default_streams import lookup_default_stream_groups +from zerver.actions.user_settings import ( + do_change_full_name, + do_change_password, + do_change_user_setting, +) 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 ( @@ -35,14 +40,7 @@ from zerver.forms import ( RealmRedirectForm, RegistrationForm, ) -from zerver.lib.actions import ( - do_activate_mirror_dummy_user, - do_change_full_name, - do_change_password, - do_change_user_setting, - do_create_realm, - do_create_user, -) +from zerver.lib.actions import do_activate_mirror_dummy_user, do_create_realm, do_create_user from zerver.lib.email_validation import email_allowed_for_realm, validate_email_not_already_in_realm from zerver.lib.exceptions import RateLimited from zerver.lib.pysa import mark_sanitized diff --git a/zerver/views/unsubscribe.py b/zerver/views/unsubscribe.py index 80da41a522..e1b0c31a76 100644 --- a/zerver/views/unsubscribe.py +++ b/zerver/views/unsubscribe.py @@ -5,8 +5,8 @@ from django.shortcuts import render from django.views.decorators.csrf import csrf_exempt from confirmation.models import Confirmation, ConfirmationKeyException, get_object_from_key +from zerver.actions.user_settings import do_change_user_setting from zerver.context_processors import common_context -from zerver.lib.actions import do_change_user_setting from zerver.lib.send_email import clear_scheduled_emails from zerver.models import ScheduledEmail, UserProfile diff --git a/zerver/views/user_settings.py b/zerver/views/user_settings.py index e4fb19f75d..0b92dca7e3 100644 --- a/zerver/views/user_settings.py +++ b/zerver/views/user_settings.py @@ -17,8 +17,7 @@ from confirmation.models import ( get_object_from_key, render_confirmation_key_error, ) -from zerver.decorator import human_users_only -from zerver.lib.actions import ( +from zerver.actions.user_settings import ( check_change_full_name, do_change_avatar_fields, do_change_password, @@ -27,6 +26,7 @@ from zerver.lib.actions import ( do_regenerate_api_key, do_start_email_change_process, ) +from zerver.decorator import human_users_only from zerver.lib.avatar import avatar_url from zerver.lib.email_validation import ( get_realm_email_validator, diff --git a/zerver/views/users.py b/zerver/views/users.py index c9d8cc64b7..c81e99780b 100644 --- a/zerver/views/users.py +++ b/zerver/views/users.py @@ -10,6 +10,12 @@ from zerver.actions.custom_profile_fields import ( check_remove_custom_profile_field_value, do_update_user_custom_profile_data_if_changed, ) +from zerver.actions.user_settings import ( + check_change_bot_full_name, + check_change_full_name, + do_change_avatar_fields, + do_regenerate_api_key, +) from zerver.actions.users import ( do_change_user_role, do_deactivate_user, @@ -20,16 +26,12 @@ from zerver.context_processors import get_valid_realm_from_request from zerver.decorator import require_member_or_admin, require_realm_admin from zerver.forms import PASSWORD_TOO_WEAK_ERROR, CreateUserForm from zerver.lib.actions import ( - check_change_bot_full_name, - check_change_full_name, - 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_create_user, do_reactivate_user, - do_regenerate_api_key, notify_created_bot, ) from zerver.lib.avatar import avatar_url, get_gravatar_url diff --git a/zilencer/management/commands/add_mock_conversation.py b/zilencer/management/commands/add_mock_conversation.py index fe451ab1bc..9455bb9f0e 100644 --- a/zilencer/management/commands/add_mock_conversation.py +++ b/zilencer/management/commands/add_mock_conversation.py @@ -2,10 +2,10 @@ from typing import Any, Dict, List from django.core.management.base import BaseCommand +from zerver.actions.user_settings import do_change_avatar_fields from zerver.lib.actions import ( bulk_add_subscriptions, do_add_reaction, - do_change_avatar_fields, do_create_user, do_send_messages, internal_prep_stream_message, diff --git a/zproject/backends.py b/zproject/backends.py index b96921b058..344b1d3af1 100644 --- a/zproject/backends.py +++ b/zproject/backends.py @@ -59,9 +59,10 @@ from typing_extensions import TypedDict from zxcvbn import zxcvbn from zerver.actions.custom_profile_fields import do_update_user_custom_profile_data_if_changed +from zerver.actions.user_settings import do_regenerate_api_key from zerver.actions.users import do_deactivate_user from zerver.decorator import client_is_exempt_from_rate_limiting -from zerver.lib.actions import do_create_user, do_reactivate_user, do_regenerate_api_key +from zerver.lib.actions import do_create_user, do_reactivate_user from zerver.lib.avatar import avatar_url, is_avatar_new from zerver.lib.avatar_hash import user_avatar_content_hash from zerver.lib.dev_ldap_directory import init_fakeldap @@ -704,7 +705,7 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend): # We do local imports here to avoid import loops from io import BytesIO - from zerver.lib.actions import do_change_avatar_fields + from zerver.actions.user_settings import do_change_avatar_fields from zerver.lib.upload import upload_avatar_image avatar_attr_name = settings.AUTH_LDAP_USER_ATTR_MAP["avatar"] @@ -818,7 +819,7 @@ class ZulipLDAPAuthBackendBase(ZulipAuthMixin, LDAPBackend): return full_name def sync_full_name_from_ldap(self, user_profile: UserProfile, ldap_user: _LDAPUser) -> None: - from zerver.lib.actions import do_change_full_name + from zerver.actions.user_settings import do_change_full_name full_name = self.get_mapped_name(ldap_user) if full_name != user_profile.full_name: