zulip/zerver/lib/bulk_create.py

256 lines
9.8 KiB
Python

from collections.abc import Collection, Iterable
from typing import Any
from django.db.models import Model, QuerySet
from django.utils.timezone import now as timezone_now
from zerver.lib.create_user import create_user_profile, get_display_email_address
from zerver.lib.initial_password import initial_password
from zerver.lib.streams import render_stream_description
from zerver.models import (
NamedUserGroup,
Realm,
RealmAuditLog,
RealmUserDefault,
Recipient,
Stream,
Subscription,
UserGroupMembership,
UserProfile,
)
from zerver.models.groups import SystemGroups
from zerver.models.realm_audit_logs import AuditLogEventType
def bulk_create_users(
realm: Realm,
users_raw: set[tuple[str, str, bool]],
bot_type: int | None = None,
bot_owner: UserProfile | None = None,
tos_version: str | None = None,
timezone: str = "",
) -> None:
"""
Creates and saves a UserProfile with the given email.
Has some code based off of UserManage.create_user, but doesn't .save()
"""
existing_users = frozenset(
UserProfile.objects.filter(realm=realm).values_list("email", flat=True)
)
users = sorted(user_raw for user_raw in users_raw if user_raw[0] not in existing_users)
realm_user_default = RealmUserDefault.objects.get(realm=realm)
if bot_type is None:
email_address_visibility = realm_user_default.email_address_visibility
else:
# There is no privacy motivation for limiting access to bot email addresses,
# so we hardcode them to EMAIL_ADDRESS_VISIBILITY_EVERYONE.
email_address_visibility = UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE
# Now create user_profiles
profiles_to_create: list[UserProfile] = []
for email, full_name, active in users:
profile = create_user_profile(
realm,
email,
initial_password(email),
active,
bot_type,
full_name,
bot_owner,
False,
tos_version,
timezone,
default_language=realm.default_language,
email_address_visibility=email_address_visibility,
)
if bot_type is None:
# This block simulates copy_default_settings from
# zerver/lib/create_user.py.
#
# We cannot use 'copy_default_settings' directly here
# because it calls '.save' after copying the settings, and
# we are bulk creating the objects here instead.
for settings_name in RealmUserDefault.property_types:
if settings_name in ["default_language", "enable_login_emails"]:
continue
value = getattr(realm_user_default, settings_name)
setattr(profile, settings_name, value)
profiles_to_create.append(profile)
if email_address_visibility == UserProfile.EMAIL_ADDRESS_VISIBILITY_EVERYONE:
UserProfile.objects.bulk_create(profiles_to_create)
else:
for user_profile in profiles_to_create:
user_profile.email = user_profile.delivery_email
UserProfile.objects.bulk_create(profiles_to_create)
for user_profile in profiles_to_create:
user_profile.email = get_display_email_address(user_profile)
UserProfile.objects.bulk_update(profiles_to_create, ["email"])
user_ids = {user.id for user in profiles_to_create}
RealmAuditLog.objects.bulk_create(
RealmAuditLog(
realm=realm,
modified_user=profile_,
event_type=AuditLogEventType.USER_CREATED,
event_time=profile_.date_joined,
)
for profile_ in profiles_to_create
)
recipients_to_create = [
Recipient(type_id=user_id, type=Recipient.PERSONAL) for user_id in user_ids
]
Recipient.objects.bulk_create(recipients_to_create)
bulk_set_users_or_streams_recipient_fields(
UserProfile, profiles_to_create, recipients_to_create
)
recipients_by_user_id: dict[int, Recipient] = {}
for recipient in recipients_to_create:
recipients_by_user_id[recipient.type_id] = recipient
subscriptions_to_create = [
Subscription(
user_profile_id=user_profile.id,
recipient=recipients_by_user_id[user_profile.id],
is_user_active=user_profile.is_active,
)
for user_profile in profiles_to_create
]
Subscription.objects.bulk_create(subscriptions_to_create)
full_members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.FULL_MEMBERS, realm=realm, is_system_group=True
)
members_system_group = NamedUserGroup.objects.get(
name=SystemGroups.MEMBERS, realm=realm, is_system_group=True
)
group_memberships_to_create: list[UserGroupMembership] = []
for user_profile in profiles_to_create:
# All users are members since this function is only used to create bots
# and test and development environment users.
assert user_profile.role == UserProfile.ROLE_MEMBER
group_memberships_to_create.append(
UserGroupMembership(user_profile=user_profile, user_group=members_system_group)
)
if not user_profile.is_provisional_member:
group_memberships_to_create.append(
UserGroupMembership(user_profile=user_profile, user_group=full_members_system_group)
)
UserGroupMembership.objects.bulk_create(group_memberships_to_create)
now = timezone_now()
RealmAuditLog.objects.bulk_create(
RealmAuditLog(
realm=realm,
modified_user=membership.user_profile,
modified_user_group=membership.user_group.named_user_group,
event_type=AuditLogEventType.USER_GROUP_DIRECT_USER_MEMBERSHIP_ADDED,
event_time=now,
acting_user=None,
)
for membership in group_memberships_to_create
)
def bulk_set_users_or_streams_recipient_fields(
model: type[Model],
objects: Collection[UserProfile]
| QuerySet[UserProfile]
| Collection[Stream]
| QuerySet[Stream],
recipients: Iterable[Recipient] | None = None,
) -> None:
assert model in [UserProfile, Stream]
for obj in objects:
assert isinstance(obj, model)
if model == UserProfile:
recipient_type = Recipient.PERSONAL
elif model == Stream:
recipient_type = Recipient.STREAM
if recipients is None:
object_ids = [obj.id for obj in objects]
recipients = Recipient.objects.filter(type=recipient_type, type_id__in=object_ids)
objects_dict = {obj.id: obj for obj in objects}
objects_to_update = set()
for recipient in recipients:
assert recipient.type == recipient_type
result = objects_dict.get(recipient.type_id)
if result is not None:
result.recipient = recipient
objects_to_update.add(result)
model._default_manager.bulk_update(objects_to_update, ["recipient"])
# This is only sed in populate_db, so doesn't really need tests
def bulk_create_streams(realm: Realm, stream_dict: dict[str, dict[str, Any]]) -> None: # nocoverage
existing_streams = {
name.lower() for name in Stream.objects.filter(realm=realm).values_list("name", flat=True)
}
administrators_user_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, is_system_group=True, realm=realm
)
streams_to_create: list[Stream] = []
for name, options in stream_dict.items():
if "history_public_to_subscribers" not in options:
options["history_public_to_subscribers"] = (
not options.get("invite_only", False) and not realm.is_zephyr_mirror_realm
)
if name.lower() not in existing_streams:
streams_to_create.append(
Stream(
realm=realm,
name=name,
description=options["description"],
rendered_description=render_stream_description(options["description"], realm),
invite_only=options.get("invite_only", False),
stream_post_policy=options.get(
"stream_post_policy", Stream.STREAM_POST_POLICY_EVERYONE
),
history_public_to_subscribers=options["history_public_to_subscribers"],
is_web_public=options.get("is_web_public", False),
is_in_zephyr_realm=realm.is_zephyr_mirror_realm,
can_remove_subscribers_group=administrators_user_group,
creator=options.get("creator", None),
),
)
# Sort streams by name before creating them so that we can have a
# reliable ordering of `stream_id` across different python versions.
# This is required for test fixtures which contain `stream_id`. Prior
# to python 3.3 hashes were not randomized but after a security fix
# hash randomization was enabled in python 3.3 which made iteration
# of dictionaries and sets completely unpredictable. Here the order
# of elements while iterating `stream_dict` will be completely random
# for python 3.3 and later versions.
streams_to_create.sort(key=lambda x: x.name)
Stream.objects.bulk_create(streams_to_create)
recipients_to_create = [
Recipient(type_id=stream["id"], type=Recipient.STREAM)
for stream in Stream.objects.filter(realm=realm).values("id", "name")
if stream["name"].lower() not in existing_streams
]
Recipient.objects.bulk_create(recipients_to_create)
bulk_set_users_or_streams_recipient_fields(Stream, streams_to_create, recipients_to_create)
def create_users(
realm: Realm, name_list: Iterable[tuple[str, str]], bot_type: int | None = None
) -> None:
user_set = {(email, full_name, True) for full_name, email in name_list}
bulk_create_users(realm, user_set, bot_type)