mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-03 21:43:21 +00:00 
			
		
		
		
	user_groups: Create NamedUserGroup objects when creating new groups.
This commit is contained in:
		@@ -13,6 +13,7 @@ from zerver.lib.user_groups import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
from zerver.models import (
 | 
					from zerver.models import (
 | 
				
			||||||
    GroupGroupMembership,
 | 
					    GroupGroupMembership,
 | 
				
			||||||
 | 
					    NamedUserGroup,
 | 
				
			||||||
    Realm,
 | 
					    Realm,
 | 
				
			||||||
    RealmAuditLog,
 | 
					    RealmAuditLog,
 | 
				
			||||||
    UserGroup,
 | 
					    UserGroup,
 | 
				
			||||||
@@ -40,13 +41,22 @@ def create_user_group_in_database(
 | 
				
			|||||||
    description: str = "",
 | 
					    description: str = "",
 | 
				
			||||||
    group_settings_map: Mapping[str, UserGroup] = {},
 | 
					    group_settings_map: Mapping[str, UserGroup] = {},
 | 
				
			||||||
    is_system_group: bool = False,
 | 
					    is_system_group: bool = False,
 | 
				
			||||||
) -> UserGroup:
 | 
					) -> NamedUserGroup:
 | 
				
			||||||
    user_group = UserGroup(
 | 
					    user_group = NamedUserGroup(
 | 
				
			||||||
        name=name, realm=realm, description=description, is_system_group=is_system_group
 | 
					        name=name,
 | 
				
			||||||
 | 
					        realm=realm,
 | 
				
			||||||
 | 
					        description=description,
 | 
				
			||||||
 | 
					        is_system_group=is_system_group,
 | 
				
			||||||
 | 
					        named_group_name=name,
 | 
				
			||||||
 | 
					        named_group_description=description,
 | 
				
			||||||
 | 
					        named_group_is_system_group=is_system_group,
 | 
				
			||||||
 | 
					        realm_for_sharding=realm,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for setting_name, setting_value in group_settings_map.items():
 | 
					    for setting_name, setting_value in group_settings_map.items():
 | 
				
			||||||
        setattr(user_group, setting_name, setting_value)
 | 
					        setattr(user_group, setting_name, setting_value)
 | 
				
			||||||
 | 
					        named_group_setting_name = "named_group_" + setting_name
 | 
				
			||||||
 | 
					        setattr(user_group, named_group_setting_name, setting_value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    system_groups_name_dict = get_role_based_system_groups_dict(realm)
 | 
					    system_groups_name_dict = get_role_based_system_groups_dict(realm)
 | 
				
			||||||
    user_group = set_defaults_for_group_settings(
 | 
					    user_group = set_defaults_for_group_settings(
 | 
				
			||||||
@@ -156,7 +166,9 @@ def promote_new_full_members() -> None:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def do_send_create_user_group_event(
 | 
					def do_send_create_user_group_event(
 | 
				
			||||||
    user_group: UserGroup, members: List[UserProfile], direct_subgroups: Sequence[UserGroup] = []
 | 
					    user_group: NamedUserGroup,
 | 
				
			||||||
 | 
					    members: List[UserProfile],
 | 
				
			||||||
 | 
					    direct_subgroups: Sequence[UserGroup] = [],
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    event = dict(
 | 
					    event = dict(
 | 
				
			||||||
        type="user_group",
 | 
					        type="user_group",
 | 
				
			||||||
@@ -182,7 +194,7 @@ def check_add_user_group(
 | 
				
			|||||||
    group_settings_map: Mapping[str, UserGroup] = {},
 | 
					    group_settings_map: Mapping[str, UserGroup] = {},
 | 
				
			||||||
    *,
 | 
					    *,
 | 
				
			||||||
    acting_user: Optional[UserProfile],
 | 
					    acting_user: Optional[UserProfile],
 | 
				
			||||||
) -> UserGroup:
 | 
					) -> NamedUserGroup:
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        user_group = create_user_group_in_database(
 | 
					        user_group = create_user_group_in_database(
 | 
				
			||||||
            name,
 | 
					            name,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,7 @@ from zerver.models import (
 | 
				
			|||||||
    Huddle,
 | 
					    Huddle,
 | 
				
			||||||
    Message,
 | 
					    Message,
 | 
				
			||||||
    MutedUser,
 | 
					    MutedUser,
 | 
				
			||||||
 | 
					    NamedUserGroup,
 | 
				
			||||||
    OnboardingStep,
 | 
					    OnboardingStep,
 | 
				
			||||||
    Reaction,
 | 
					    Reaction,
 | 
				
			||||||
    Realm,
 | 
					    Realm,
 | 
				
			||||||
@@ -1040,7 +1041,7 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # We expect Zulip server exports to contain these system groups,
 | 
					        # We expect Zulip server exports to contain these system groups,
 | 
				
			||||||
        # this logic here is needed to handle the imports from other services.
 | 
					        # this logic here is needed to handle the imports from other services.
 | 
				
			||||||
        role_system_groups_dict: Optional[Dict[int, UserGroup]] = None
 | 
					        role_system_groups_dict: Optional[Dict[int, NamedUserGroup]] = None
 | 
				
			||||||
        if "zerver_usergroup" not in data:
 | 
					        if "zerver_usergroup" not in data:
 | 
				
			||||||
            role_system_groups_dict = create_system_user_groups_for_realm(realm)
 | 
					            role_system_groups_dict = create_system_user_groups_for_realm(realm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1736,7 +1737,9 @@ def import_analytics_data(realm: Realm, import_dir: Path, crossrealm_user_ids: S
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def add_users_to_system_user_groups(
 | 
					def add_users_to_system_user_groups(
 | 
				
			||||||
    realm: Realm, user_profiles: List[UserProfile], role_system_groups_dict: Dict[int, UserGroup]
 | 
					    realm: Realm,
 | 
				
			||||||
 | 
					    user_profiles: List[UserProfile],
 | 
				
			||||||
 | 
					    role_system_groups_dict: Dict[int, NamedUserGroup],
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    full_members_system_group = UserGroup.objects.get(
 | 
					    full_members_system_group = UserGroup.objects.get(
 | 
				
			||||||
        name=SystemGroups.FULL_MEMBERS,
 | 
					        name=SystemGroups.FULL_MEMBERS,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,17 +2,19 @@ from contextlib import contextmanager
 | 
				
			|||||||
from dataclasses import dataclass
 | 
					from dataclasses import dataclass
 | 
				
			||||||
from typing import Collection, Dict, Iterable, Iterator, List, Mapping, TypedDict
 | 
					from typing import Collection, Dict, Iterable, Iterator, List, Mapping, TypedDict
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from django.db import transaction
 | 
					from django.db import connection, transaction
 | 
				
			||||||
from django.db.models import F, QuerySet
 | 
					from django.db.models import F, QuerySet
 | 
				
			||||||
from django.utils.timezone import now as timezone_now
 | 
					from django.utils.timezone import now as timezone_now
 | 
				
			||||||
from django.utils.translation import gettext as _
 | 
					from django.utils.translation import gettext as _
 | 
				
			||||||
from django_cte import With
 | 
					from django_cte import With
 | 
				
			||||||
from django_stubs_ext import ValuesQuerySet
 | 
					from django_stubs_ext import ValuesQuerySet
 | 
				
			||||||
 | 
					from psycopg2.sql import SQL, Literal
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from zerver.lib.exceptions import JsonableError
 | 
					from zerver.lib.exceptions import JsonableError
 | 
				
			||||||
from zerver.lib.types import GroupPermissionSetting, ServerSupportedPermissionSettings
 | 
					from zerver.lib.types import GroupPermissionSetting, ServerSupportedPermissionSettings
 | 
				
			||||||
from zerver.models import (
 | 
					from zerver.models import (
 | 
				
			||||||
    GroupGroupMembership,
 | 
					    GroupGroupMembership,
 | 
				
			||||||
 | 
					    NamedUserGroup,
 | 
				
			||||||
    Realm,
 | 
					    Realm,
 | 
				
			||||||
    RealmAuditLog,
 | 
					    RealmAuditLog,
 | 
				
			||||||
    Stream,
 | 
					    Stream,
 | 
				
			||||||
@@ -394,8 +396,8 @@ def get_recursive_subgroups_for_groups(
 | 
				
			|||||||
    return recursive_subgroups
 | 
					    return recursive_subgroups
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_role_based_system_groups_dict(realm: Realm) -> Dict[str, UserGroup]:
 | 
					def get_role_based_system_groups_dict(realm: Realm) -> Dict[str, NamedUserGroup]:
 | 
				
			||||||
    system_groups = UserGroup.objects.filter(realm=realm, is_system_group=True)
 | 
					    system_groups = NamedUserGroup.objects.filter(realm=realm, is_system_group=True)
 | 
				
			||||||
    system_groups_name_dict = {}
 | 
					    system_groups_name_dict = {}
 | 
				
			||||||
    for group in system_groups:
 | 
					    for group in system_groups:
 | 
				
			||||||
        system_groups_name_dict[group.name] = group
 | 
					        system_groups_name_dict[group.name] = group
 | 
				
			||||||
@@ -404,10 +406,10 @@ def get_role_based_system_groups_dict(realm: Realm) -> Dict[str, UserGroup]:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def set_defaults_for_group_settings(
 | 
					def set_defaults_for_group_settings(
 | 
				
			||||||
    user_group: UserGroup,
 | 
					    user_group: NamedUserGroup,
 | 
				
			||||||
    group_settings_map: Mapping[str, UserGroup],
 | 
					    group_settings_map: Mapping[str, UserGroup],
 | 
				
			||||||
    system_groups_name_dict: Dict[str, UserGroup],
 | 
					    system_groups_name_dict: Dict[str, NamedUserGroup],
 | 
				
			||||||
) -> UserGroup:
 | 
					) -> NamedUserGroup:
 | 
				
			||||||
    for setting_name, permission_config in UserGroup.GROUP_PERMISSION_SETTINGS.items():
 | 
					    for setting_name, permission_config in UserGroup.GROUP_PERMISSION_SETTINGS.items():
 | 
				
			||||||
        if setting_name in group_settings_map:
 | 
					        if setting_name in group_settings_map:
 | 
				
			||||||
            # We skip the settings for which a value is passed
 | 
					            # We skip the settings for which a value is passed
 | 
				
			||||||
@@ -419,73 +421,121 @@ def set_defaults_for_group_settings(
 | 
				
			|||||||
        else:
 | 
					        else:
 | 
				
			||||||
            default_group_name = permission_config.default_group_name
 | 
					            default_group_name = permission_config.default_group_name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        default_group = system_groups_name_dict[default_group_name]
 | 
					        default_group = system_groups_name_dict[default_group_name].usergroup_ptr
 | 
				
			||||||
        setattr(user_group, setting_name, default_group)
 | 
					        setattr(user_group, setting_name, default_group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        setting_name_for_named_object = "named_group_" + setting_name
 | 
				
			||||||
 | 
					        setattr(user_group, setting_name_for_named_object, default_group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return user_group
 | 
					    return user_group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@transaction.atomic(savepoint=False)
 | 
					def bulk_create_system_user_groups(groups: List[Dict[str, str]], realm: Realm) -> None:
 | 
				
			||||||
def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]:
 | 
					 | 
				
			||||||
    """Any changes to this function likely require a migration to adjust
 | 
					 | 
				
			||||||
    existing realms.  See e.g. migration 0382_create_role_based_system_groups.py,
 | 
					 | 
				
			||||||
    which is a copy of this function from when we introduced system groups.
 | 
					 | 
				
			||||||
    """
 | 
					 | 
				
			||||||
    role_system_groups_dict: Dict[int, UserGroup] = {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # This value will be used to set the temporary initial value for different
 | 
					    # This value will be used to set the temporary initial value for different
 | 
				
			||||||
    # settings since we can only set them to the correct values after the groups
 | 
					    # settings since we can only set them to the correct values after the groups
 | 
				
			||||||
    # are created.
 | 
					    # are created.
 | 
				
			||||||
    initial_group_setting_value = -1
 | 
					    initial_group_setting_value = -1
 | 
				
			||||||
 | 
					    rows = [
 | 
				
			||||||
    for role in UserGroup.SYSTEM_USER_GROUP_ROLE_MAP:
 | 
					        SQL("({},{},{},{},{})").format(
 | 
				
			||||||
        user_group_params = UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[role]
 | 
					            Literal(realm.id),
 | 
				
			||||||
        user_group = UserGroup(
 | 
					            Literal(group["name"]),
 | 
				
			||||||
            name=user_group_params["name"],
 | 
					            Literal(group["description"]),
 | 
				
			||||||
            description=user_group_params["description"],
 | 
					            Literal(True),
 | 
				
			||||||
            realm=realm,
 | 
					            Literal(initial_group_setting_value),
 | 
				
			||||||
            is_system_group=True,
 | 
					 | 
				
			||||||
            can_mention_group_id=initial_group_setting_value,
 | 
					 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        role_system_groups_dict[role] = user_group
 | 
					        for group in groups
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    query = SQL(
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        INSERT INTO zerver_usergroup (realm_id, name, description, is_system_group, can_mention_group_id)
 | 
				
			||||||
 | 
					        VALUES {rows}
 | 
				
			||||||
 | 
					        RETURNING id
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					    ).format(rows=SQL(", ").join(rows))
 | 
				
			||||||
 | 
					    with connection.cursor() as cursor:
 | 
				
			||||||
 | 
					        cursor.execute(query)
 | 
				
			||||||
 | 
					        user_group_ids = [id for (id,) in cursor.fetchall()]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    rows = [
 | 
				
			||||||
 | 
					        SQL("({},{},{},{},{},{})").format(
 | 
				
			||||||
 | 
					            Literal(user_group_ids[idx]),
 | 
				
			||||||
 | 
					            Literal(realm.id),
 | 
				
			||||||
 | 
					            Literal(group["name"]),
 | 
				
			||||||
 | 
					            Literal(group["description"]),
 | 
				
			||||||
 | 
					            Literal(True),
 | 
				
			||||||
 | 
					            Literal(initial_group_setting_value),
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        for idx, group in enumerate(groups)
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					    query = SQL(
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        INSERT INTO zerver_namedusergroup (usergroup_ptr_id, realm_id, name, description, is_system_group, can_mention_group_id)
 | 
				
			||||||
 | 
					        VALUES {rows}
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					    ).format(rows=SQL(", ").join(rows))
 | 
				
			||||||
 | 
					    with connection.cursor() as cursor:
 | 
				
			||||||
 | 
					        cursor.execute(query)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@transaction.atomic(savepoint=False)
 | 
				
			||||||
 | 
					def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, NamedUserGroup]:
 | 
				
			||||||
 | 
					    """Any changes to this function likely require a migration to adjust
 | 
				
			||||||
 | 
					    existing realms.  See e.g. migration 0382_create_role_based_system_groups.py,
 | 
				
			||||||
 | 
					    which is a copy of this function from when we introduced system groups.
 | 
				
			||||||
 | 
					    """
 | 
				
			||||||
 | 
					    role_system_groups_dict: Dict[int, NamedUserGroup] = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    system_groups_info_list: List[Dict[str, str]] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nobody_group_info = {
 | 
				
			||||||
 | 
					        "name": SystemGroups.NOBODY,
 | 
				
			||||||
 | 
					        "description": "Nobody",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    full_members_group_info = {
 | 
				
			||||||
 | 
					        "name": SystemGroups.FULL_MEMBERS,
 | 
				
			||||||
 | 
					        "description": "Members of this organization, not including new accounts and guests",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    everyone_on_internet_group_info = {
 | 
				
			||||||
 | 
					        "name": SystemGroups.EVERYONE_ON_INTERNET,
 | 
				
			||||||
 | 
					        "description": "Everyone on the Internet",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    system_groups_info_list = [
 | 
				
			||||||
 | 
					        nobody_group_info,
 | 
				
			||||||
 | 
					        UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[UserProfile.ROLE_REALM_OWNER],
 | 
				
			||||||
 | 
					        UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[UserProfile.ROLE_REALM_ADMINISTRATOR],
 | 
				
			||||||
 | 
					        UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[UserProfile.ROLE_MODERATOR],
 | 
				
			||||||
 | 
					        full_members_group_info,
 | 
				
			||||||
 | 
					        UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[UserProfile.ROLE_MEMBER],
 | 
				
			||||||
 | 
					        UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[UserProfile.ROLE_GUEST],
 | 
				
			||||||
 | 
					        everyone_on_internet_group_info,
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bulk_create_system_user_groups(system_groups_info_list, realm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    system_groups_name_dict: Dict[str, NamedUserGroup] = get_role_based_system_groups_dict(realm)
 | 
				
			||||||
 | 
					    for role in UserGroup.SYSTEM_USER_GROUP_ROLE_MAP:
 | 
				
			||||||
 | 
					        group_name = UserGroup.SYSTEM_USER_GROUP_ROLE_MAP[role]["name"]
 | 
				
			||||||
 | 
					        role_system_groups_dict[role] = system_groups_name_dict[group_name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    full_members_system_group = UserGroup(
 | 
					 | 
				
			||||||
        name=SystemGroups.FULL_MEMBERS,
 | 
					 | 
				
			||||||
        description="Members of this organization, not including new accounts and guests",
 | 
					 | 
				
			||||||
        realm=realm,
 | 
					 | 
				
			||||||
        is_system_group=True,
 | 
					 | 
				
			||||||
        can_mention_group_id=initial_group_setting_value,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    everyone_on_internet_system_group = UserGroup(
 | 
					 | 
				
			||||||
        name=SystemGroups.EVERYONE_ON_INTERNET,
 | 
					 | 
				
			||||||
        description="Everyone on the Internet",
 | 
					 | 
				
			||||||
        realm=realm,
 | 
					 | 
				
			||||||
        is_system_group=True,
 | 
					 | 
				
			||||||
        can_mention_group_id=initial_group_setting_value,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    nobody_system_group = UserGroup(
 | 
					 | 
				
			||||||
        name=SystemGroups.NOBODY,
 | 
					 | 
				
			||||||
        description="Nobody",
 | 
					 | 
				
			||||||
        realm=realm,
 | 
					 | 
				
			||||||
        is_system_group=True,
 | 
					 | 
				
			||||||
        can_mention_group_id=initial_group_setting_value,
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
    # Order of this list here is important to create correct GroupGroupMembership objects
 | 
					    # Order of this list here is important to create correct GroupGroupMembership objects
 | 
				
			||||||
    # Note that because we do not create user memberships here, no audit log entries for
 | 
					    # Note that because we do not create user memberships here, no audit log entries for
 | 
				
			||||||
    # user memberships are populated either.
 | 
					    # user memberships are populated either.
 | 
				
			||||||
    system_user_groups_list = [
 | 
					    system_user_groups_list = [
 | 
				
			||||||
        nobody_system_group,
 | 
					        system_groups_name_dict[SystemGroups.NOBODY],
 | 
				
			||||||
        role_system_groups_dict[UserProfile.ROLE_REALM_OWNER],
 | 
					        system_groups_name_dict[SystemGroups.OWNERS],
 | 
				
			||||||
        role_system_groups_dict[UserProfile.ROLE_REALM_ADMINISTRATOR],
 | 
					        system_groups_name_dict[SystemGroups.ADMINISTRATORS],
 | 
				
			||||||
        role_system_groups_dict[UserProfile.ROLE_MODERATOR],
 | 
					        system_groups_name_dict[SystemGroups.MODERATORS],
 | 
				
			||||||
        full_members_system_group,
 | 
					        system_groups_name_dict[SystemGroups.FULL_MEMBERS],
 | 
				
			||||||
        role_system_groups_dict[UserProfile.ROLE_MEMBER],
 | 
					        system_groups_name_dict[SystemGroups.MEMBERS],
 | 
				
			||||||
        role_system_groups_dict[UserProfile.ROLE_GUEST],
 | 
					        system_groups_name_dict[SystemGroups.EVERYONE],
 | 
				
			||||||
        everyone_on_internet_system_group,
 | 
					        system_groups_name_dict[SystemGroups.EVERYONE_ON_INTERNET],
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    creation_time = timezone_now()
 | 
					    creation_time = timezone_now()
 | 
				
			||||||
    UserGroup.objects.bulk_create(system_user_groups_list)
 | 
					 | 
				
			||||||
    realmauditlog_objects = [
 | 
					    realmauditlog_objects = [
 | 
				
			||||||
        RealmAuditLog(
 | 
					        RealmAuditLog(
 | 
				
			||||||
            realm=realm,
 | 
					            realm=realm,
 | 
				
			||||||
@@ -498,11 +548,12 @@ def create_system_user_groups_for_realm(realm: Realm) -> Dict[int, UserGroup]:
 | 
				
			|||||||
    ]
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    groups_with_updated_settings = []
 | 
					    groups_with_updated_settings = []
 | 
				
			||||||
    system_groups_name_dict = get_role_based_system_groups_dict(realm)
 | 
					 | 
				
			||||||
    for group in system_user_groups_list:
 | 
					    for group in system_user_groups_list:
 | 
				
			||||||
        user_group = set_defaults_for_group_settings(group, {}, system_groups_name_dict)
 | 
					        user_group = set_defaults_for_group_settings(group, {}, system_groups_name_dict)
 | 
				
			||||||
        groups_with_updated_settings.append(user_group)
 | 
					        groups_with_updated_settings.append(user_group)
 | 
				
			||||||
    UserGroup.objects.bulk_update(groups_with_updated_settings, ["can_mention_group"])
 | 
					    NamedUserGroup.objects.bulk_update(
 | 
				
			||||||
 | 
					        groups_with_updated_settings, ["can_mention_group", "named_group_can_mention_group"]
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    subgroup_objects: List[GroupGroupMembership] = []
 | 
					    subgroup_objects: List[GroupGroupMembership] = []
 | 
				
			||||||
    # "Nobody" system group is not a subgroup of any user group, since it is already empty.
 | 
					    # "Nobody" system group is not a subgroup of any user group, since it is already empty.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ from zerver.models.custom_profile_fields import CustomProfileField as CustomProf
 | 
				
			|||||||
from zerver.models.custom_profile_fields import CustomProfileFieldValue as CustomProfileFieldValue
 | 
					from zerver.models.custom_profile_fields import CustomProfileFieldValue as CustomProfileFieldValue
 | 
				
			||||||
from zerver.models.drafts import Draft as Draft
 | 
					from zerver.models.drafts import Draft as Draft
 | 
				
			||||||
from zerver.models.groups import GroupGroupMembership as GroupGroupMembership
 | 
					from zerver.models.groups import GroupGroupMembership as GroupGroupMembership
 | 
				
			||||||
 | 
					from zerver.models.groups import NamedUserGroup as NamedUserGroup
 | 
				
			||||||
from zerver.models.groups import UserGroup as UserGroup
 | 
					from zerver.models.groups import UserGroup as UserGroup
 | 
				
			||||||
from zerver.models.groups import UserGroupMembership as UserGroupMembership
 | 
					from zerver.models.groups import UserGroupMembership as UserGroupMembership
 | 
				
			||||||
from zerver.models.linkifiers import RealmFilter as RealmFilter
 | 
					from zerver.models.linkifiers import RealmFilter as RealmFilter
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1167,7 +1167,7 @@ class TestRealmAuditLog(ZulipTestCase):
 | 
				
			|||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assert_length(audit_log_entries, 1)
 | 
					        self.assert_length(audit_log_entries, 1)
 | 
				
			||||||
        self.assertIsNone(audit_log_entries[0].modified_user)
 | 
					        self.assertIsNone(audit_log_entries[0].modified_user)
 | 
				
			||||||
        self.assertEqual(audit_log_entries[0].modified_user_group, user_group)
 | 
					        self.assertEqual(audit_log_entries[0].modified_user_group, user_group.usergroup_ptr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        audit_log_entries = RealmAuditLog.objects.filter(
 | 
					        audit_log_entries = RealmAuditLog.objects.filter(
 | 
				
			||||||
            acting_user=hamlet,
 | 
					            acting_user=hamlet,
 | 
				
			||||||
@@ -1212,19 +1212,21 @@ class TestRealmAuditLog(ZulipTestCase):
 | 
				
			|||||||
        hamlet = self.example_user("hamlet")
 | 
					        hamlet = self.example_user("hamlet")
 | 
				
			||||||
        user_group = check_add_user_group(hamlet.realm, "main", [], acting_user=None)
 | 
					        user_group = check_add_user_group(hamlet.realm, "main", [], acting_user=None)
 | 
				
			||||||
        subgroups = [
 | 
					        subgroups = [
 | 
				
			||||||
            check_add_user_group(hamlet.realm, f"subgroup{num}", [], acting_user=hamlet)
 | 
					            check_add_user_group(
 | 
				
			||||||
 | 
					                hamlet.realm, f"subgroup{num}", [], acting_user=hamlet
 | 
				
			||||||
 | 
					            ).usergroup_ptr
 | 
				
			||||||
            for num in range(3)
 | 
					            for num in range(3)
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        now = timezone_now()
 | 
					        now = timezone_now()
 | 
				
			||||||
        add_subgroups_to_user_group(user_group, subgroups, acting_user=hamlet)
 | 
					        add_subgroups_to_user_group(user_group.usergroup_ptr, subgroups, acting_user=hamlet)
 | 
				
			||||||
        # Only one audit log entry for the subgroup membership is expected.
 | 
					        # Only one audit log entry for the subgroup membership is expected.
 | 
				
			||||||
        audit_log_entry = RealmAuditLog.objects.get(
 | 
					        audit_log_entry = RealmAuditLog.objects.get(
 | 
				
			||||||
            realm=hamlet.realm,
 | 
					            realm=hamlet.realm,
 | 
				
			||||||
            event_time__gte=now,
 | 
					            event_time__gte=now,
 | 
				
			||||||
            event_type=RealmAuditLog.USER_GROUP_DIRECT_SUBGROUP_MEMBERSHIP_ADDED,
 | 
					            event_type=RealmAuditLog.USER_GROUP_DIRECT_SUBGROUP_MEMBERSHIP_ADDED,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(audit_log_entry.modified_user_group, user_group)
 | 
					        self.assertEqual(audit_log_entry.modified_user_group, user_group.usergroup_ptr)
 | 
				
			||||||
        self.assertEqual(audit_log_entry.acting_user, hamlet)
 | 
					        self.assertEqual(audit_log_entry.acting_user, hamlet)
 | 
				
			||||||
        self.assertDictEqual(
 | 
					        self.assertDictEqual(
 | 
				
			||||||
            audit_log_entry.extra_data,
 | 
					            audit_log_entry.extra_data,
 | 
				
			||||||
@@ -1244,13 +1246,15 @@ class TestRealmAuditLog(ZulipTestCase):
 | 
				
			|||||||
                {"supergroup_ids": [user_group.id]},
 | 
					                {"supergroup_ids": [user_group.id]},
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        remove_subgroups_from_user_group(user_group, subgroups[:2], acting_user=hamlet)
 | 
					        remove_subgroups_from_user_group(
 | 
				
			||||||
 | 
					            user_group.usergroup_ptr, subgroups[:2], acting_user=hamlet
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        audit_log_entry = RealmAuditLog.objects.get(
 | 
					        audit_log_entry = RealmAuditLog.objects.get(
 | 
				
			||||||
            realm=hamlet.realm,
 | 
					            realm=hamlet.realm,
 | 
				
			||||||
            event_time__gte=now,
 | 
					            event_time__gte=now,
 | 
				
			||||||
            event_type=RealmAuditLog.USER_GROUP_DIRECT_SUBGROUP_MEMBERSHIP_REMOVED,
 | 
					            event_type=RealmAuditLog.USER_GROUP_DIRECT_SUBGROUP_MEMBERSHIP_REMOVED,
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertEqual(audit_log_entry.modified_user_group, user_group)
 | 
					        self.assertEqual(audit_log_entry.modified_user_group, user_group.usergroup_ptr)
 | 
				
			||||||
        self.assertEqual(audit_log_entry.acting_user, hamlet)
 | 
					        self.assertEqual(audit_log_entry.acting_user, hamlet)
 | 
				
			||||||
        self.assertDictEqual(
 | 
					        self.assertDictEqual(
 | 
				
			||||||
            audit_log_entry.extra_data,
 | 
					            audit_log_entry.extra_data,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,20 +113,29 @@ class UserGroupTestCase(ZulipTestCase):
 | 
				
			|||||||
        everyone_group = check_add_user_group(realm, "Everyone", [shiva], acting_user=None)
 | 
					        everyone_group = check_add_user_group(realm, "Everyone", [shiva], acting_user=None)
 | 
				
			||||||
        GroupGroupMembership.objects.create(supergroup=everyone_group, subgroup=staff_group)
 | 
					        GroupGroupMembership.objects.create(supergroup=everyone_group, subgroup=staff_group)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertCountEqual(list(get_recursive_subgroups(leadership_group)), [leadership_group])
 | 
					 | 
				
			||||||
        self.assertCountEqual(
 | 
					        self.assertCountEqual(
 | 
				
			||||||
            list(get_recursive_subgroups(staff_group)), [leadership_group, staff_group]
 | 
					            list(get_recursive_subgroups(leadership_group)), [leadership_group.usergroup_ptr]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        self.assertCountEqual(
 | 
				
			||||||
 | 
					            list(get_recursive_subgroups(staff_group)),
 | 
				
			||||||
 | 
					            [leadership_group.usergroup_ptr, staff_group.usergroup_ptr],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        self.assertCountEqual(
 | 
					        self.assertCountEqual(
 | 
				
			||||||
            list(get_recursive_subgroups(everyone_group)),
 | 
					            list(get_recursive_subgroups(everyone_group)),
 | 
				
			||||||
            [leadership_group, staff_group, everyone_group],
 | 
					            [
 | 
				
			||||||
 | 
					                leadership_group.usergroup_ptr,
 | 
				
			||||||
 | 
					                staff_group.usergroup_ptr,
 | 
				
			||||||
 | 
					                everyone_group.usergroup_ptr,
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertCountEqual(list(get_recursive_strict_subgroups(leadership_group)), [])
 | 
					        self.assertCountEqual(list(get_recursive_strict_subgroups(leadership_group)), [])
 | 
				
			||||||
        self.assertCountEqual(list(get_recursive_strict_subgroups(staff_group)), [leadership_group])
 | 
					        self.assertCountEqual(
 | 
				
			||||||
 | 
					            list(get_recursive_strict_subgroups(staff_group)), [leadership_group.usergroup_ptr]
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
        self.assertCountEqual(
 | 
					        self.assertCountEqual(
 | 
				
			||||||
            list(get_recursive_strict_subgroups(everyone_group)),
 | 
					            list(get_recursive_strict_subgroups(everyone_group)),
 | 
				
			||||||
            [leadership_group, staff_group],
 | 
					            [leadership_group.usergroup_ptr, staff_group.usergroup_ptr],
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertCountEqual(list(get_recursive_group_members(leadership_group)), [desdemona])
 | 
					        self.assertCountEqual(list(get_recursive_group_members(leadership_group)), [desdemona])
 | 
				
			||||||
@@ -135,14 +144,14 @@ class UserGroupTestCase(ZulipTestCase):
 | 
				
			|||||||
            list(get_recursive_group_members(everyone_group)), [desdemona, iago, shiva]
 | 
					            list(get_recursive_group_members(everyone_group)), [desdemona, iago, shiva]
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertIn(leadership_group, get_recursive_membership_groups(desdemona))
 | 
					        self.assertIn(leadership_group.usergroup_ptr, get_recursive_membership_groups(desdemona))
 | 
				
			||||||
        self.assertIn(staff_group, get_recursive_membership_groups(desdemona))
 | 
					        self.assertIn(staff_group.usergroup_ptr, get_recursive_membership_groups(desdemona))
 | 
				
			||||||
        self.assertIn(everyone_group, get_recursive_membership_groups(desdemona))
 | 
					        self.assertIn(everyone_group.usergroup_ptr, get_recursive_membership_groups(desdemona))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertIn(staff_group, get_recursive_membership_groups(iago))
 | 
					        self.assertIn(staff_group.usergroup_ptr, get_recursive_membership_groups(iago))
 | 
				
			||||||
        self.assertIn(everyone_group, get_recursive_membership_groups(iago))
 | 
					        self.assertIn(everyone_group.usergroup_ptr, get_recursive_membership_groups(iago))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.assertIn(everyone_group, get_recursive_membership_groups(shiva))
 | 
					        self.assertIn(everyone_group.usergroup_ptr, get_recursive_membership_groups(shiva))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_subgroups_of_role_based_system_groups(self) -> None:
 | 
					    def test_subgroups_of_role_based_system_groups(self) -> None:
 | 
				
			||||||
        realm = get_realm("zulip")
 | 
					        realm = get_realm("zulip")
 | 
				
			||||||
@@ -368,7 +377,7 @@ class UserGroupAPITestCase(UserGroupTestCase):
 | 
				
			|||||||
        result = self.client_post("/json/user_groups/create", info=params)
 | 
					        result = self.client_post("/json/user_groups/create", info=params)
 | 
				
			||||||
        self.assert_json_success(result)
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
        test_group = UserGroup.objects.get(name="test", realm=hamlet.realm)
 | 
					        test_group = UserGroup.objects.get(name="test", realm=hamlet.realm)
 | 
				
			||||||
        self.assertEqual(test_group.can_mention_group, leadership_group)
 | 
					        self.assertEqual(test_group.can_mention_group, leadership_group.usergroup_ptr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nobody_group = UserGroup.objects.get(
 | 
					        nobody_group = UserGroup.objects.get(
 | 
				
			||||||
            name="role:nobody", realm=hamlet.realm, is_system_group=True
 | 
					            name="role:nobody", realm=hamlet.realm, is_system_group=True
 | 
				
			||||||
@@ -538,7 +547,7 @@ class UserGroupAPITestCase(UserGroupTestCase):
 | 
				
			|||||||
        result = self.client_patch(f"/json/user_groups/{support_group.id}", info=params)
 | 
					        result = self.client_patch(f"/json/user_groups/{support_group.id}", info=params)
 | 
				
			||||||
        self.assert_json_success(result)
 | 
					        self.assert_json_success(result)
 | 
				
			||||||
        support_group = UserGroup.objects.get(name="support", realm=hamlet.realm)
 | 
					        support_group = UserGroup.objects.get(name="support", realm=hamlet.realm)
 | 
				
			||||||
        self.assertEqual(support_group.can_mention_group, marketing_group)
 | 
					        self.assertEqual(support_group.can_mention_group, marketing_group.usergroup_ptr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        nobody_group = UserGroup.objects.get(
 | 
					        nobody_group = UserGroup.objects.get(
 | 
				
			||||||
            name="role:nobody", realm=hamlet.realm, is_system_group=True
 | 
					            name="role:nobody", realm=hamlet.realm, is_system_group=True
 | 
				
			||||||
@@ -640,7 +649,7 @@ class UserGroupAPITestCase(UserGroupTestCase):
 | 
				
			|||||||
            for i in range(50)
 | 
					            for i in range(50)
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with self.assert_database_query_count(4):
 | 
					        with self.assert_database_query_count(5):
 | 
				
			||||||
            user_group = create_user_group_in_database(
 | 
					            user_group = create_user_group_in_database(
 | 
				
			||||||
                name="support",
 | 
					                name="support",
 | 
				
			||||||
                members=[hamlet, cordelia, *original_users],
 | 
					                members=[hamlet, cordelia, *original_users],
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ from zerver.lib.exceptions import JsonableError
 | 
				
			|||||||
from zerver.lib.test_classes import ZulipTransactionTestCase
 | 
					from zerver.lib.test_classes import ZulipTransactionTestCase
 | 
				
			||||||
from zerver.lib.test_helpers import HostRequestMock
 | 
					from zerver.lib.test_helpers import HostRequestMock
 | 
				
			||||||
from zerver.lib.user_groups import access_user_group_by_id
 | 
					from zerver.lib.user_groups import access_user_group_by_id
 | 
				
			||||||
from zerver.models import Realm, UserGroup, UserProfile
 | 
					from zerver.models import NamedUserGroup, Realm, UserGroup, UserProfile
 | 
				
			||||||
from zerver.models.realms import get_realm
 | 
					from zerver.models.realms import get_realm
 | 
				
			||||||
from zerver.views.user_groups import update_subgroups_of_user_group
 | 
					from zerver.views.user_groups import update_subgroups_of_user_group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -77,7 +77,7 @@ class UserGroupRaceConditionTestCase(ZulipTransactionTestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        super().tearDown()
 | 
					        super().tearDown()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def create_user_group_chain(self, realm: Realm) -> List[UserGroup]:
 | 
					    def create_user_group_chain(self, realm: Realm) -> List[NamedUserGroup]:
 | 
				
			||||||
        """Build a user groups forming a chain through group-group memberships
 | 
					        """Build a user groups forming a chain through group-group memberships
 | 
				
			||||||
        returning a list where each group is the supergroup of its subsequent group.
 | 
					        returning a list where each group is the supergroup of its subsequent group.
 | 
				
			||||||
        """
 | 
					        """
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user