def notify_linkifiers(realm: Realm) -> None: realm_linkifiers = linkifiers_for_realm(realm.id) event: Dict[str, object] = dict(type="realm_linkifiers", realm_linkifiers=realm_linkifiers) send_event(realm, event, active_user_ids(realm.id)) # Below is code for backwards compatibility. The now deprecated # "realm_filters" event-type is used by older clients, and uses # tuples. realm_filters = realm_filters_for_realm(realm.id) event = dict(type="realm_filters", realm_filters=realm_filters) send_event(realm, event, active_user_ids(realm.id))
def do_change_icon_source( realm: Realm, icon_source: str, *, acting_user: Optional[UserProfile] ) -> None: realm.icon_source = icon_source realm.icon_version += 1 realm.save(update_fields=["icon_source", "icon_version"]) event_time = timezone_now() RealmAuditLog.objects.create( realm=realm, event_type=RealmAuditLog.REALM_ICON_SOURCE_CHANGED, extra_data={"icon_source": icon_source, "icon_version": realm.icon_version}, event_time=event_time, acting_user=acting_user, ) event = dict( type="realm", op="update_dict", property="icon", data=dict(icon_source=realm.icon_source, icon_url=realm_icon_url(realm)), ) transaction.on_commit( lambda: send_event( realm, event, active_user_ids(realm.id), ) )
def do_remove_realm_domain(realm_domain: RealmDomain, *, acting_user: Optional[UserProfile]) -> None: realm = realm_domain.realm domain = realm_domain.domain realm_domain.delete() removed_domain = RealmDomainDict( domain=realm_domain.domain, allow_subdomains=realm_domain.allow_subdomains, ) RealmAuditLog.objects.create( realm=realm, acting_user=acting_user, event_type=RealmAuditLog.REALM_DOMAIN_REMOVED, event_time=timezone_now(), extra_data=orjson.dumps({ "realm_domains": get_realm_domains(realm), "removed_domain": removed_domain, }).decode(), ) if RealmDomain.objects.filter( realm=realm).count() == 0 and realm.emails_restricted_to_domains: # If this was the last realm domain, we mark the realm as no # longer restricted to domain, because the feature doesn't do # anything if there are no domains, and this is probably less # confusing than the alternative. do_set_realm_property(realm, "emails_restricted_to_domains", False, acting_user=acting_user) event = dict(type="realm_domains", op="remove", domain=domain) transaction.on_commit( lambda: send_event(realm, event, active_user_ids(realm.id)))
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 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), ))
def do_change_logo_source(realm: Realm, logo_source: str, night: bool, *, acting_user: Optional[UserProfile]) -> None: if not night: realm.logo_source = logo_source realm.logo_version += 1 realm.save(update_fields=["logo_source", "logo_version"]) else: realm.night_logo_source = logo_source realm.night_logo_version += 1 realm.save(update_fields=["night_logo_source", "night_logo_version"]) RealmAuditLog.objects.create( event_type=RealmAuditLog.REALM_LOGO_CHANGED, realm=realm, event_time=timezone_now(), acting_user=acting_user, ) event = dict( type="realm", op="update_dict", property="night_logo" if night else "logo", data=get_realm_logo_data(realm, night), ) transaction.on_commit( lambda: send_event(realm, event, active_user_ids(realm.id)))
def do_change_realm_domain(realm_domain: RealmDomain, allow_subdomains: bool, *, acting_user: Optional[UserProfile]) -> None: realm_domain.allow_subdomains = allow_subdomains realm_domain.save(update_fields=["allow_subdomains"]) changed_domain = RealmDomainDict( domain=realm_domain.domain, allow_subdomains=realm_domain.allow_subdomains, ) RealmAuditLog.objects.create( realm=realm_domain.realm, acting_user=acting_user, event_type=RealmAuditLog.REALM_DOMAIN_CHANGED, event_time=timezone_now(), extra_data=orjson.dumps({ "realm_domains": get_realm_domains(realm_domain.realm), "changed_domain": changed_domain, }).decode(), ) event = dict( type="realm_domains", op="change", realm_domain=dict(domain=realm_domain.domain, allow_subdomains=realm_domain.allow_subdomains), ) transaction.on_commit(lambda: send_event( realm_domain.realm, event, active_user_ids(realm_domain.realm_id)))
def send_presence_changed(user_profile: UserProfile, presence: UserPresence) -> None: # Most presence data is sent to clients in the main presence # endpoint in response to the user's own presence; this results # data that is 1-2 minutes stale for who is online. The flaw with # this plan is when a user comes back online and then immediately # sends a message, recipients may still see that user as offline! # We solve that by sending an immediate presence update clients. # # See https://zulip.readthedocs.io/en/latest/subsystems/presence.html for # internals documentation on presence. user_ids = active_user_ids(user_profile.realm_id) if len(user_ids) > settings.USER_LIMIT_FOR_SENDING_PRESENCE_UPDATE_EVENTS: # These immediate presence generate quadratic work for Tornado # (linear number of users in each event and the frequency of # users coming online grows linearly with userbase too). In # organizations with thousands of users, this can overload # Tornado, especially if much of the realm comes online at the # same time. # # The utility of these live-presence updates goes down as # organizations get bigger (since one is much less likely to # be paying attention to the sidebar); so beyond a limit, we # stop sending them at all. return presence_dict = presence.to_dict() event = dict( type="presence", email=user_profile.email, user_id=user_profile.id, server_timestamp=time.time(), presence={presence_dict["client"]: presence_dict}, ) send_event(user_profile.realm, event, user_ids)
def do_make_user_billing_admin(user_profile: UserProfile) -> None: user_profile.is_billing_admin = True user_profile.save(update_fields=["is_billing_admin"]) event = dict( type="realm_user", op="update", person=dict(user_id=user_profile.id, is_billing_admin=True) ) send_event(user_profile.realm, event, active_user_ids(user_profile.realm_id))
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 }, ) event = dict(type="realm", op="update", property="org_type", value=org_type) transaction.on_commit( lambda: send_event(realm, event, active_user_ids(realm.id)))
def do_add_realm_domain(realm: Realm, domain: str, allow_subdomains: bool, *, acting_user: Optional[UserProfile]) -> (RealmDomain): realm_domain = RealmDomain.objects.create( realm=realm, domain=domain, allow_subdomains=allow_subdomains) added_domain = RealmDomainDict(domain=domain, allow_subdomains=allow_subdomains) RealmAuditLog.objects.create( realm=realm, acting_user=acting_user, event_type=RealmAuditLog.REALM_DOMAIN_ADDED, event_time=timezone_now(), extra_data=orjson.dumps({ "realm_domains": get_realm_domains(realm), "added_domain": added_domain, }).decode(), ) event = dict( type="realm_domains", op="add", realm_domain=RealmDomainDict( domain=realm_domain.domain, allow_subdomains=realm_domain.allow_subdomains), ) transaction.on_commit( lambda: send_event(realm, event, active_user_ids(realm.id))) return realm_domain
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_send_user_group_update_event(user_group: UserGroup, data: Dict[str, str]) -> None: event = dict(type="user_group", op="update", group_id=user_group.id, data=data) send_event(user_group.realm, event, active_user_ids(user_group.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_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 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), ))
def do_send_subgroups_update_event(event_name: str, user_group: UserGroup, subgroup_ids: List[int]) -> None: event = dict(type="user_group", op=event_name, group_id=user_group.id, subgroup_ids=subgroup_ids) transaction.on_commit(lambda: send_event( user_group.realm, event, active_user_ids(user_group.realm_id)))
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_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] # https://github.com/python/mypy/issues/3004 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 notify_user_update_custom_profile_data( user_profile: UserProfile, field: Dict[str, Union[int, str, List[int], None]] ) -> None: data = dict(id=field["id"], value=field["value"]) if field["rendered_value"]: data["rendered_value"] = field["rendered_value"] payload = dict(user_id=user_profile.id, custom_profile_field=data) event = dict(type="realm_user", op="update", person=payload) send_event(user_profile.realm, event, active_user_ids(user_profile.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_send_create_user_group_event( user_group: UserGroup, members: List[UserProfile], subgroups: Sequence[UserGroup] = []) -> None: event = dict( type="user_group", op="add", group=dict( name=user_group.name, members=[member.id for member in members], description=user_group.description, id=user_group.id, is_system_group=user_group.is_system_group, subgroups=[subgroup.id for subgroup in subgroups], ), ) send_event(user_group.realm, event, active_user_ids(user_group.realm_id))
def do_update_user_status( user_profile: UserProfile, away: Optional[bool], status_text: Optional[str], client_id: int, emoji_name: Optional[str], emoji_code: Optional[str], reaction_type: Optional[str], ) -> None: if away is None: status = None elif away: status = UserStatus.AWAY else: status = UserStatus.NORMAL realm = user_profile.realm update_user_status( user_profile_id=user_profile.id, status=status, status_text=status_text, client_id=client_id, emoji_name=emoji_name, emoji_code=emoji_code, reaction_type=reaction_type, ) event = dict( type="user_status", user_id=user_profile.id, ) if away is not None: event["away"] = away if status_text is not None: event["status_text"] = status_text if emoji_name is not None: event["emoji_name"] = emoji_name event["emoji_code"] = emoji_code event["reaction_type"] = reaction_type send_event(realm, event, active_user_ids(realm.id))
def do_set_realm_stream( realm: Realm, field: Literal["notifications_stream", "signup_notifications_stream"], stream: Optional[Stream], stream_id: int, *, acting_user: Optional[UserProfile], ) -> None: # We could calculate more of these variables from `field`, but # it's probably more readable to not do so. if field == "notifications_stream": old_value = realm.notifications_stream_id realm.notifications_stream = stream property = "notifications_stream_id" elif field == "signup_notifications_stream": old_value = realm.signup_notifications_stream_id realm.signup_notifications_stream = stream property = "signup_notifications_stream_id" else: raise AssertionError("Invalid realm stream field.") with transaction.atomic(): realm.save(update_fields=[field]) 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": field, }).decode(), ) event = dict( type="realm", op="update", property=property, value=stream_id, ) 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_change_user_role( user_profile: UserProfile, value: int, *, acting_user: Optional[UserProfile] ) -> None: old_value = user_profile.role old_system_group = get_system_user_group_for_user(user_profile) user_profile.role = value user_profile.save(update_fields=["role"]) RealmAuditLog.objects.create( realm=user_profile.realm, modified_user=user_profile, acting_user=acting_user, event_type=RealmAuditLog.USER_ROLE_CHANGED, event_time=timezone_now(), extra_data=orjson.dumps( { RealmAuditLog.OLD_VALUE: old_value, RealmAuditLog.NEW_VALUE: value, RealmAuditLog.ROLE_COUNT: realm_user_count_by_role(user_profile.realm), } ).decode(), ) event = dict( type="realm_user", op="update", person=dict(user_id=user_profile.id, role=user_profile.role) ) transaction.on_commit( lambda: send_event(user_profile.realm, event, active_user_ids(user_profile.realm_id)) ) UserGroupMembership.objects.filter( user_profile=user_profile, user_group=old_system_group ).delete() system_group = get_system_user_group_for_user(user_profile) UserGroupMembership.objects.create(user_profile=user_profile, user_group=system_group) do_send_user_group_members_update_event("remove_members", old_system_group, [user_profile.id]) do_send_user_group_members_update_event("add_members", system_group, [user_profile.id]) if UserProfile.ROLE_MEMBER in [old_value, value]: update_users_in_full_members_system_group(user_profile.realm, [user_profile.id])
def notify_realm_playgrounds(realm: Realm, realm_playgrounds: List[RealmPlaygroundDict]) -> None: event = dict(type="realm_playgrounds", realm_playgrounds=realm_playgrounds) transaction.on_commit(lambda: send_event(realm, event, active_user_ids(realm.id)))
def do_send_delete_user_group_event(realm: Realm, user_group_id: int, realm_id: int) -> None: event = dict(type="user_group", op="remove", group_id=user_group_id) send_event(realm, event, active_user_ids(realm_id))
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 notify_realm_custom_profile_fields(realm: Realm) -> None: fields = custom_profile_fields_for_realm(realm.id) event = dict(type="custom_profile_fields", fields=[f.as_dict() for f in fields]) send_event(realm, event, active_user_ids(realm.id))