user_groups: Add API support to add subgroups during group creation.

This commit adds support to add subgroups to a group while
creating it.

User can add the subgroups to group irrespective of permissions
like user can add members during creating it.
This commit is contained in:
Sahil Batra 2024-10-15 21:37:38 +05:30 committed by Tim Abbott
parent 1e818c4708
commit e5043b991a
5 changed files with 82 additions and 5 deletions

View File

@ -25,6 +25,8 @@ format used by the Zulip server that they are interacting with.
* [`POST /user_groups/{user_group_id}/members`](/api/update-user-group-members): * [`POST /user_groups/{user_group_id}/members`](/api/update-user-group-members):
Added `add_subgroups` and `delete_subgroups` parameters to support updating Added `add_subgroups` and `delete_subgroups` parameters to support updating
subgroups of a user group using this endpoint. subgroups of a user group using this endpoint.
* [`POST /user_groups/create`](/api/create-user-group): Added `subgroups`
parameter to support setting subgroups of a user group during its creation.
**Feature level 310** **Feature level 310**

View File

@ -275,7 +275,8 @@ def lock_subgroups_with_respect_to_supergroup(
potential_supergroup_id: int, potential_supergroup_id: int,
acting_user: UserProfile, acting_user: UserProfile,
*, *,
permission_setting: str, permission_setting: str | None,
creating_group: bool = False,
) -> Iterator[LockedUserGroupContext]: ) -> Iterator[LockedUserGroupContext]:
"""This locks the user groups with the given potential_subgroup_ids, as well """This locks the user groups with the given potential_subgroup_ids, as well
as their indirect subgroups, followed by the potential supergroup. It as their indirect subgroups, followed by the potential supergroup. It
@ -305,6 +306,14 @@ def lock_subgroups_with_respect_to_supergroup(
# the transaction with a JsonableError by handling the DatabaseError. # the transaction with a JsonableError by handling the DatabaseError.
# But at the current scale of concurrent requests, we rely on # But at the current scale of concurrent requests, we rely on
# Postgres's deadlock detection when it occurs. # Postgres's deadlock detection when it occurs.
if creating_group:
# User can add subgroups to the group while creating it irrespective
# of whether the user has other permissions for that group.
potential_supergroup = get_user_group_by_id_in_realm(
potential_supergroup_id, acting_user.realm, for_read=False
)
else:
assert permission_setting is not None
potential_supergroup = access_user_group_for_update( potential_supergroup = access_user_group_for_update(
potential_supergroup_id, acting_user, permission_setting=permission_setting potential_supergroup_id, acting_user, permission_setting=permission_setting
) )

View File

@ -20279,6 +20279,19 @@ paths:
items: items:
type: integer type: integer
example: [1, 2, 3, 4] example: [1, 2, 3, 4]
subgroups:
description: |
An array containing the IDs of the initial subgroups for the new
user group.
User can add subgroups to the new group irrespective of other
permissions for the new group.
**Changes**: New in Zulip 10.0 (feature level 311).
type: array
items:
type: integer
example: [11]
can_add_members_group: can_add_members_group:
allOf: allOf:
- description: | - description: |
@ -20362,6 +20375,8 @@ paths:
encoding: encoding:
members: members:
contentType: application/json contentType: application/json
subgroups:
contentType: application/json
can_add_members_group: can_add_members_group:
contentType: application/json contentType: application/json
can_join_group: can_join_group:

View File

@ -531,6 +531,47 @@ class UserGroupAPITestCase(UserGroupTestCase):
self.assert_json_error(result, "User group name cannot start with 'channel:'.") self.assert_json_error(result, "User group name cannot start with 'channel:'.")
self.assert_length(NamedUserGroup.objects.filter(realm=hamlet.realm), 10) self.assert_length(NamedUserGroup.objects.filter(realm=hamlet.realm), 10)
def test_creating_groups_with_subgroups(self) -> None:
realm = get_realm("zulip")
hamlet = self.example_user("hamlet")
subgroup = check_add_user_group(realm, "support", [hamlet], acting_user=hamlet)
self.login("desdemona")
othello = self.example_user("othello")
params = {
"name": "Troubleshooting",
"members": orjson.dumps([othello.id]).decode(),
"description": "Troubleshooting team",
"subgroups": orjson.dumps([subgroup.id]).decode(),
}
result = self.client_post("/json/user_groups/create", info=params)
self.assert_json_success(result)
self.assert_length(NamedUserGroup.objects.filter(realm=hamlet.realm), 11)
user_group = NamedUserGroup.objects.get(name="Troubleshooting", realm=hamlet.realm)
self.assert_subgroup_membership(user_group, [subgroup])
# User can add subgroups to a group while creating it even if
# settings are set to not allow adding subgroups after creating
# the group.
self.login("othello")
self.assertEqual(realm.can_manage_all_groups.named_user_group.name, SystemGroups.OWNERS)
admins_group = NamedUserGroup.objects.get(
name=SystemGroups.ADMINISTRATORS, realm=realm, is_system_group=True
)
params = {
"name": "Backend",
"members": orjson.dumps([othello.id]).decode(),
"description": "Backend team",
"subgroups": orjson.dumps([subgroup.id]).decode(),
"can_manage_group": orjson.dumps(admins_group.id).decode(),
"can_add_members_group": orjson.dumps(admins_group.id).decode(),
}
result = self.client_post("/json/user_groups/create", info=params)
self.assert_json_success(result)
user_group = NamedUserGroup.objects.get(name="Troubleshooting", realm=realm)
self.assert_subgroup_membership(user_group, [subgroup])
def do_test_set_group_setting_during_user_group_creation(self, setting_name: str) -> None: def do_test_set_group_setting_during_user_group_creation(self, setting_name: str) -> None:
self.login("hamlet") self.login("hamlet")
hamlet = self.example_user("hamlet") hamlet = self.example_user("hamlet")

View File

@ -57,6 +57,7 @@ def add_user_group(
name: str, name: str,
members: Json[list[int]], members: Json[list[int]],
description: str, description: str,
subgroups: Json[list[int]] | None = None,
can_add_members_group: Json[int | AnonymousSettingGroupDict] | None = None, can_add_members_group: Json[int | AnonymousSettingGroupDict] | None = None,
can_join_group: Json[int | AnonymousSettingGroupDict] | None = None, can_join_group: Json[int | AnonymousSettingGroupDict] | None = None,
can_leave_group: Json[int | AnonymousSettingGroupDict] | None = None, can_leave_group: Json[int | AnonymousSettingGroupDict] | None = None,
@ -84,7 +85,7 @@ def add_user_group(
) )
group_settings_map[setting_name] = setting_value_group group_settings_map[setting_name] = setting_value_group
check_add_user_group( user_group = check_add_user_group(
user_profile.realm, user_profile.realm,
name, name,
user_profiles, user_profiles,
@ -92,6 +93,15 @@ def add_user_group(
group_settings_map=group_settings_map, group_settings_map=group_settings_map,
acting_user=user_profile, acting_user=user_profile,
) )
if subgroups is not None and len(subgroups) != 0:
with lock_subgroups_with_respect_to_supergroup(
subgroups, user_group.id, user_profile, permission_setting=None, creating_group=True
) as context:
add_subgroups_to_user_group(
context.supergroup, context.direct_subgroups, acting_user=user_profile
)
return json_success(request) return json_success(request)