mirror of https://github.com/zulip/zulip.git
user_groups: Add support to update can_manage_group setting.
This commit adds API support to update can_manage_group setting of a user group.
This commit is contained in:
parent
b0b36e884c
commit
5d613ce98d
|
@ -28,6 +28,9 @@ format used by the Zulip server that they are interacting with.
|
||||||
* [`POST /user_groups/create`](/api/create-user-group): Added `can_manage_group`
|
* [`POST /user_groups/create`](/api/create-user-group): Added `can_manage_group`
|
||||||
parameter to support setting the user group whose members can manage the user
|
parameter to support setting the user group whose members can manage the user
|
||||||
group.
|
group.
|
||||||
|
* [`PATCH /user_groups/{user_group_id}`](/api/update-user-group): Added
|
||||||
|
`can_manage_group` parameter to support changing the user group whose
|
||||||
|
members can manage the specified user group.
|
||||||
|
|
||||||
**Feature level 282**
|
**Feature level 282**
|
||||||
|
|
||||||
|
|
|
@ -34,8 +34,8 @@ DESKTOP_WARNING_VERSION = "5.9.3"
|
||||||
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
# new level means in api_docs/changelog.md, as well as "**Changes**"
|
||||||
# entries in the endpoint's documentation in `zulip.yaml`.
|
# entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
|
|
||||||
API_FEATURE_LEVEL = 282 # Last bumped for removing "POST users/me/tutorial_status"
|
|
||||||
|
|
||||||
|
API_FEATURE_LEVEL = 283 # Last bumped for can_manage_group
|
||||||
|
|
||||||
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
# Bump the minor PROVISION_VERSION to indicate that folks should provision
|
||||||
# only when going from an old version of the code to a newer version. Bump
|
# only when going from an old version of the code to a newer version. Bump
|
||||||
|
|
|
@ -19720,6 +19720,41 @@ paths:
|
||||||
a required field.
|
a required field.
|
||||||
type: string
|
type: string
|
||||||
example: The marketing team.
|
example: The marketing team.
|
||||||
|
can_manage_group:
|
||||||
|
description: |
|
||||||
|
The set of users who have permission to [manage this user group][manage-user-groups]
|
||||||
|
expressed as an [update to a group-setting value][update-group-setting].
|
||||||
|
|
||||||
|
This setting cannot be set to `"role:internet"` and `"role:everyone"`
|
||||||
|
[system groups][system-groups].
|
||||||
|
|
||||||
|
**Changes**: New in Zulip 10.0 (feature level 283).
|
||||||
|
|
||||||
|
[update-group-setting]: /api/group-setting-values#updating-group-setting-values
|
||||||
|
[system-groups]: /api/group-setting-values#system-groups
|
||||||
|
[manage-user-groups]: /help/manage-user-groups
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
new:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
The new [group-setting value](/api/group-setting-values) for who would
|
||||||
|
have the permission to manage the group.
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
old:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
The expected current [group-setting value](/api/group-setting-values)
|
||||||
|
for who has the permission to manage the group.
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
required:
|
||||||
|
- new
|
||||||
|
example:
|
||||||
|
{
|
||||||
|
"new": {"direct_members": [10], "direct_subgroups": [11]},
|
||||||
|
"old": 11,
|
||||||
|
}
|
||||||
can_mention_group:
|
can_mention_group:
|
||||||
description: |
|
description: |
|
||||||
The set of users who have permission to [mention this group][mentions],
|
The set of users who have permission to [mention this group][mentions],
|
||||||
|
@ -19769,6 +19804,8 @@ paths:
|
||||||
"old": 11,
|
"old": 11,
|
||||||
}
|
}
|
||||||
encoding:
|
encoding:
|
||||||
|
can_manage_group:
|
||||||
|
contentType: application/json
|
||||||
can_mention_group:
|
can_mention_group:
|
||||||
contentType: application/json
|
contentType: application/json
|
||||||
responses:
|
responses:
|
||||||
|
|
|
@ -747,56 +747,50 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
result = self.client_patch(f"/json/user_groups/{user_group.id}", info=params)
|
result = self.client_patch(f"/json/user_groups/{user_group.id}", info=params)
|
||||||
self.assert_json_error(result, "User group name cannot start with 'channel:'.")
|
self.assert_json_error(result, "User group name cannot start with 'channel:'.")
|
||||||
|
|
||||||
def test_update_can_mention_group_setting(self) -> None:
|
def do_test_update_user_group_permission_settings(self, setting_name: str) -> None:
|
||||||
hamlet = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
support_group = check_add_user_group(hamlet.realm, "support", [hamlet], acting_user=None)
|
permission_configuration = NamedUserGroup.GROUP_PERMISSION_SETTINGS[setting_name]
|
||||||
marketing_group = check_add_user_group(
|
|
||||||
hamlet.realm, "marketing", [hamlet], acting_user=None
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
)
|
marketing_group = NamedUserGroup.objects.get(name="marketing", realm=hamlet.realm)
|
||||||
|
|
||||||
moderators_group = NamedUserGroup.objects.get(
|
moderators_group = NamedUserGroup.objects.get(
|
||||||
name="role:moderators", realm=hamlet.realm, is_system_group=True
|
name="role:moderators", realm=hamlet.realm, is_system_group=True
|
||||||
)
|
)
|
||||||
|
|
||||||
self.login("hamlet")
|
self.login("hamlet")
|
||||||
params = {
|
params = {}
|
||||||
"can_mention_group": orjson.dumps(
|
params[setting_name] = orjson.dumps(
|
||||||
{
|
{
|
||||||
"new": moderators_group.id,
|
"new": moderators_group.id,
|
||||||
}
|
}
|
||||||
).decode(),
|
).decode()
|
||||||
}
|
|
||||||
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 = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
self.assertEqual(support_group.can_mention_group, moderators_group.usergroup_ptr)
|
self.assertEqual(getattr(support_group, setting_name), moderators_group.usergroup_ptr)
|
||||||
|
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": marketing_group.id,
|
"new": marketing_group.id,
|
||||||
}
|
}
|
||||||
).decode(),
|
).decode()
|
||||||
}
|
|
||||||
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 = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
self.assertEqual(support_group.can_mention_group, marketing_group.usergroup_ptr)
|
self.assertEqual(getattr(support_group, setting_name), marketing_group.usergroup_ptr)
|
||||||
|
|
||||||
nobody_group = NamedUserGroup.objects.get(
|
nobody_group = NamedUserGroup.objects.get(
|
||||||
name="role:nobody", realm=hamlet.realm, is_system_group=True
|
name="role:nobody", realm=hamlet.realm, is_system_group=True
|
||||||
)
|
)
|
||||||
params = {
|
params[setting_name] = orjson.dumps({"new": nobody_group.id}).decode()
|
||||||
"can_mention_group": orjson.dumps({"new": nobody_group.id}).decode(),
|
|
||||||
}
|
|
||||||
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 = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
self.assertEqual(support_group.can_mention_group, nobody_group.usergroup_ptr)
|
self.assertEqual(getattr(support_group, setting_name), nobody_group.usergroup_ptr)
|
||||||
|
|
||||||
othello = self.example_user("othello")
|
othello = self.example_user("othello")
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": {
|
"new": {
|
||||||
"direct_members": [othello.id],
|
"direct_members": [othello.id],
|
||||||
|
@ -804,22 +798,20 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).decode()
|
).decode()
|
||||||
}
|
|
||||||
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 = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
list(support_group.can_mention_group.direct_members.all()),
|
list(getattr(support_group, setting_name).direct_members.all()),
|
||||||
[othello],
|
[othello],
|
||||||
)
|
)
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
list(support_group.can_mention_group.direct_subgroups.all()),
|
list(getattr(support_group, setting_name).direct_subgroups.all()),
|
||||||
[marketing_group, moderators_group],
|
[marketing_group, moderators_group],
|
||||||
)
|
)
|
||||||
|
|
||||||
prospero = self.example_user("prospero")
|
prospero = self.example_user("prospero")
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": {
|
"new": {
|
||||||
"direct_members": [othello.id, prospero.id],
|
"direct_members": [othello.id, prospero.id],
|
||||||
|
@ -827,63 +819,60 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).decode()
|
).decode()
|
||||||
}
|
previous_setting_id = getattr(support_group, setting_name).id
|
||||||
previous_can_mention_group_id = support_group.can_mention_group_id
|
|
||||||
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 = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
|
|
||||||
# Test that the existing UserGroup object is updated.
|
# Test that the existing UserGroup object is updated.
|
||||||
self.assertEqual(support_group.can_mention_group_id, previous_can_mention_group_id)
|
self.assertEqual(getattr(support_group, setting_name).id, previous_setting_id)
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
list(support_group.can_mention_group.direct_members.all()),
|
list(getattr(support_group, setting_name).direct_members.all()),
|
||||||
[othello, prospero],
|
[othello, prospero],
|
||||||
)
|
)
|
||||||
self.assertCountEqual(
|
self.assertCountEqual(
|
||||||
list(support_group.can_mention_group.direct_subgroups.all()),
|
list(getattr(support_group, setting_name).direct_subgroups.all()),
|
||||||
[marketing_group, moderators_group],
|
[marketing_group, moderators_group],
|
||||||
)
|
)
|
||||||
|
|
||||||
params = {"can_mention_group": orjson.dumps({"new": marketing_group.id}).decode()}
|
params[setting_name] = orjson.dumps({"new": marketing_group.id}).decode()
|
||||||
previous_can_mention_group_id = support_group.can_mention_group_id
|
previous_setting_id = getattr(support_group, setting_name).id
|
||||||
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 = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
|
|
||||||
# Test that the previous UserGroup object is deleted.
|
# Test that the previous UserGroup object is deleted.
|
||||||
self.assertFalse(UserGroup.objects.filter(id=previous_can_mention_group_id).exists())
|
self.assertFalse(UserGroup.objects.filter(id=previous_setting_id).exists())
|
||||||
self.assertEqual(support_group.can_mention_group_id, marketing_group.id)
|
self.assertEqual(getattr(support_group, setting_name).id, marketing_group.id)
|
||||||
|
|
||||||
owners_group = NamedUserGroup.objects.get(
|
owners_group = NamedUserGroup.objects.get(
|
||||||
name="role:owners", realm=hamlet.realm, is_system_group=True
|
name="role:owners", realm=hamlet.realm, is_system_group=True
|
||||||
)
|
)
|
||||||
params = {
|
params[setting_name] = orjson.dumps({"new": owners_group.id}).decode()
|
||||||
"can_mention_group": orjson.dumps({"new": owners_group.id}).decode(),
|
|
||||||
}
|
|
||||||
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)
|
||||||
|
if not permission_configuration.allow_owners_group:
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result, "'can_mention_group' setting cannot be set to 'role:owners' group."
|
result, f"'{setting_name}' setting cannot be set to 'role:owners' group."
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
self.assert_json_success(result)
|
||||||
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
|
self.assertEqual(getattr(support_group, setting_name).id, owners_group.id)
|
||||||
|
|
||||||
internet_group = NamedUserGroup.objects.get(
|
internet_group = NamedUserGroup.objects.get(
|
||||||
name="role:internet", realm=hamlet.realm, is_system_group=True
|
name="role:internet", realm=hamlet.realm, is_system_group=True
|
||||||
)
|
)
|
||||||
params = {
|
params[setting_name] = orjson.dumps({"new": internet_group.id}).decode()
|
||||||
"can_mention_group": orjson.dumps({"new": internet_group.id}).decode(),
|
|
||||||
}
|
|
||||||
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_error(
|
self.assert_json_error(
|
||||||
result, "'can_mention_group' setting cannot be set to 'role:internet' group."
|
result, f"'{setting_name}' setting cannot be set to 'role:internet' group."
|
||||||
)
|
)
|
||||||
|
|
||||||
params = {
|
params[setting_name] = orjson.dumps({"new": 1111}).decode()
|
||||||
"can_mention_group": orjson.dumps({"new": 1111}).decode(),
|
|
||||||
}
|
|
||||||
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_error(result, "Invalid user group")
|
self.assert_json_error(result, "Invalid user group")
|
||||||
|
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": {
|
"new": {
|
||||||
"direct_members": [1111, othello.id],
|
"direct_members": [1111, othello.id],
|
||||||
|
@ -891,12 +880,10 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).decode()
|
).decode()
|
||||||
}
|
|
||||||
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_error(result, "Invalid user ID: 1111")
|
self.assert_json_error(result, "Invalid user ID: 1111")
|
||||||
|
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": {
|
"new": {
|
||||||
"direct_members": [prospero.id, othello.id],
|
"direct_members": [prospero.id, othello.id],
|
||||||
|
@ -904,14 +891,12 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).decode()
|
).decode()
|
||||||
}
|
|
||||||
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_error(result, "Invalid user group ID: 1111")
|
self.assert_json_error(result, "Invalid user group ID: 1111")
|
||||||
|
|
||||||
# Test case when ALLOW_GROUP_VALUED_SETTINGS is False.
|
# Test case when ALLOW_GROUP_VALUED_SETTINGS is False.
|
||||||
with self.settings(ALLOW_GROUP_VALUED_SETTINGS=False):
|
with self.settings(ALLOW_GROUP_VALUED_SETTINGS=False):
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": {
|
"new": {
|
||||||
"direct_members": [othello.id],
|
"direct_members": [othello.id],
|
||||||
|
@ -919,12 +904,10 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).decode()
|
).decode()
|
||||||
}
|
|
||||||
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_error(result, "'can_mention_group' must be a system user group.")
|
self.assert_json_error(result, f"'{setting_name}' must be a system user group.")
|
||||||
|
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": {
|
"new": {
|
||||||
"direct_members": [],
|
"direct_members": [],
|
||||||
|
@ -932,23 +915,32 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
).decode()
|
).decode()
|
||||||
}
|
|
||||||
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 = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
self.assertEqual(support_group.can_mention_group_id, moderators_group.id)
|
self.assertEqual(getattr(support_group, setting_name).id, moderators_group.id)
|
||||||
|
|
||||||
params = {
|
params[setting_name] = orjson.dumps(
|
||||||
"can_mention_group": orjson.dumps(
|
|
||||||
{
|
{
|
||||||
"new": marketing_group.id,
|
"new": marketing_group.id,
|
||||||
}
|
}
|
||||||
).decode()
|
).decode()
|
||||||
}
|
|
||||||
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)
|
||||||
|
|
||||||
|
if setting_name == "can_mention_group":
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
support_group = NamedUserGroup.objects.get(name="support", realm=hamlet.realm)
|
||||||
self.assertEqual(support_group.can_mention_group_id, marketing_group.id)
|
self.assertEqual(getattr(support_group, setting_name).id, marketing_group.id)
|
||||||
|
else:
|
||||||
|
self.assert_json_error(result, f"'{setting_name}' must be a system user group.")
|
||||||
|
|
||||||
|
def test_update_user_group_permission_settings(self) -> None:
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
check_add_user_group(hamlet.realm, "support", [hamlet], acting_user=None)
|
||||||
|
check_add_user_group(hamlet.realm, "marketing", [hamlet], acting_user=None)
|
||||||
|
|
||||||
|
for setting_name in NamedUserGroup.GROUP_PERMISSION_SETTINGS:
|
||||||
|
self.do_test_update_user_group_permission_settings(setting_name)
|
||||||
|
|
||||||
def test_user_group_update_to_already_existing_name(self) -> None:
|
def test_user_group_update_to_already_existing_name(self) -> None:
|
||||||
hamlet = self.example_user("hamlet")
|
hamlet = self.example_user("hamlet")
|
||||||
|
|
|
@ -107,9 +107,15 @@ def edit_user_group(
|
||||||
user_group_id: PathOnly[int],
|
user_group_id: PathOnly[int],
|
||||||
name: str | None = None,
|
name: str | None = None,
|
||||||
description: str | None = None,
|
description: str | None = None,
|
||||||
|
can_manage_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
can_mention_group: Json[GroupSettingChangeRequest] | None = None,
|
can_mention_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
if name is None and description is None and can_mention_group is None:
|
if (
|
||||||
|
name is None
|
||||||
|
and description is None
|
||||||
|
and can_manage_group is None
|
||||||
|
and can_mention_group is None
|
||||||
|
):
|
||||||
raise JsonableError(_("No new data supplied"))
|
raise JsonableError(_("No new data supplied"))
|
||||||
|
|
||||||
user_group = access_user_group_by_id(user_group_id, user_profile, for_read=False)
|
user_group = access_user_group_by_id(user_group_id, user_profile, for_read=False)
|
||||||
|
|
Loading…
Reference in New Issue