mirror of https://github.com/zulip/zulip.git
groups: Allow setting anonymous group to settings while creation.
This commit adds support to set can_mention_group setting to anonymous group while group creation.
This commit is contained in:
parent
206014f263
commit
23fdb73353
|
@ -26,6 +26,9 @@ format used by the Zulip server that they are interacting with.
|
||||||
/register`](/api/register-queue): `can_mention_group` field can now
|
/register`](/api/register-queue): `can_mention_group` field can now
|
||||||
either be an ID of a named user group with the permission, or an
|
either be an ID of a named user group with the permission, or an
|
||||||
object describing the set of users and groups with the permission.
|
object describing the set of users and groups with the permission.
|
||||||
|
* [`POST /user_groups/create`](/api/create-user-group): The
|
||||||
|
`can_mention_group` parameter can now either be an ID of a named
|
||||||
|
user group or an object describing a set of users and groups.
|
||||||
|
|
||||||
**Feature level 257**:
|
**Feature level 257**:
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from zerver.lib.exceptions import JsonableError
|
from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.user_groups import (
|
from zerver.lib.user_groups import (
|
||||||
|
get_group_setting_value_for_api,
|
||||||
get_role_based_system_groups_dict,
|
get_role_based_system_groups_dict,
|
||||||
set_defaults_for_group_settings,
|
set_defaults_for_group_settings,
|
||||||
)
|
)
|
||||||
|
@ -175,7 +176,7 @@ def do_send_create_user_group_event(
|
||||||
id=user_group.id,
|
id=user_group.id,
|
||||||
is_system_group=user_group.is_system_group,
|
is_system_group=user_group.is_system_group,
|
||||||
direct_subgroup_ids=[direct_subgroup.id for direct_subgroup in direct_subgroups],
|
direct_subgroup_ids=[direct_subgroup.id for direct_subgroup in direct_subgroups],
|
||||||
can_mention_group=user_group.can_mention_group_id,
|
can_mention_group=get_group_setting_value_for_api(user_group.can_mention_group),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
send_event(user_group.realm, event, active_user_ids(user_group.realm_id))
|
send_event(user_group.realm, event, active_user_ids(user_group.realm_id))
|
||||||
|
|
|
@ -1790,6 +1790,18 @@ update_message_flags_remove_event = event_dict_type(
|
||||||
check_update_message_flags_remove = make_checker(update_message_flags_remove_event)
|
check_update_message_flags_remove = make_checker(update_message_flags_remove_event)
|
||||||
|
|
||||||
|
|
||||||
|
group_setting_type = UnionType(
|
||||||
|
[
|
||||||
|
int,
|
||||||
|
DictType(
|
||||||
|
required_keys=[
|
||||||
|
("direct_members", ListType(int)),
|
||||||
|
("direct_subgroups", ListType(int)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
group_type = DictType(
|
group_type = DictType(
|
||||||
required_keys=[
|
required_keys=[
|
||||||
("id", int),
|
("id", int),
|
||||||
|
@ -1798,7 +1810,7 @@ group_type = DictType(
|
||||||
("direct_subgroup_ids", ListType(int)),
|
("direct_subgroup_ids", ListType(int)),
|
||||||
("description", str),
|
("description", str),
|
||||||
("is_system_group", bool),
|
("is_system_group", bool),
|
||||||
("can_mention_group", int),
|
("can_mention_group", group_setting_type),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -241,8 +241,10 @@ def update_or_create_user_group_for_setting(
|
||||||
direct_members: List[int],
|
direct_members: List[int],
|
||||||
direct_subgroups: List[int],
|
direct_subgroups: List[int],
|
||||||
current_setting_value: Optional[UserGroup],
|
current_setting_value: Optional[UserGroup],
|
||||||
) -> UserGroup: # nocoverage
|
) -> UserGroup:
|
||||||
if current_setting_value is not None and not hasattr(current_setting_value, "named_user_group"):
|
if current_setting_value is not None and not hasattr(
|
||||||
|
current_setting_value, "named_user_group"
|
||||||
|
): # nocoverage
|
||||||
# We do not create a new group if the setting was already set
|
# We do not create a new group if the setting was already set
|
||||||
# to an anonymous group. The memberships of existing group
|
# to an anonymous group. The memberships of existing group
|
||||||
# itself are updated.
|
# itself are updated.
|
||||||
|
@ -287,16 +289,16 @@ def access_user_group_for_setting(
|
||||||
|
|
||||||
# The API would not allow passing the setting parameter as a Dict
|
# The API would not allow passing the setting parameter as a Dict
|
||||||
# if require_system_group is true for a setting.
|
# if require_system_group is true for a setting.
|
||||||
assert permission_configuration.require_system_group is False # nocoverage
|
assert permission_configuration.require_system_group is False
|
||||||
|
|
||||||
user_group = update_or_create_user_group_for_setting(
|
user_group = update_or_create_user_group_for_setting(
|
||||||
user_profile.realm,
|
user_profile.realm,
|
||||||
setting_user_group.direct_members,
|
setting_user_group.direct_members,
|
||||||
setting_user_group.direct_subgroups,
|
setting_user_group.direct_subgroups,
|
||||||
current_setting_value,
|
current_setting_value,
|
||||||
) # nocoverage
|
)
|
||||||
|
|
||||||
return user_group # nocoverage
|
return user_group
|
||||||
|
|
||||||
|
|
||||||
def check_user_group_name(group_name: str) -> str:
|
def check_user_group_name(group_name: str) -> str:
|
||||||
|
|
|
@ -18389,19 +18389,24 @@ paths:
|
||||||
type: integer
|
type: integer
|
||||||
example: [1, 2, 3, 4]
|
example: [1, 2, 3, 4]
|
||||||
can_mention_group:
|
can_mention_group:
|
||||||
description: |
|
allOf:
|
||||||
ID of the user group whose members are allowed to mention the new
|
- description: |
|
||||||
user group.
|
Either the ID of a named user group that has permission to
|
||||||
|
mention the group, or an object describing the set of users
|
||||||
|
and groups who have permission mention the new group.
|
||||||
|
|
||||||
This setting cannot be set to `"role:internet"` and `"role:owners"`
|
This setting cannot be set to `"role:internet"` and
|
||||||
system groups.
|
`"role:owners"` system groups.
|
||||||
|
|
||||||
**Changes**: Before Zulip 8.0 (feature level 198),
|
**Changes**: Before Zulip 9.0 (feature level 258), the
|
||||||
the `can_mention_group` setting was named `can_mention_group_id`.
|
`can_mention_group` setting could only be set to an integer.
|
||||||
|
|
||||||
New in Zulip 8.0 (feature level 191). Previously, groups
|
**Changes**: Before Zulip 8.0 (feature level 198),
|
||||||
could be mentioned if and only if they were not system groups.
|
the `can_mention_group` setting was named `can_mention_group_id`.
|
||||||
type: integer
|
|
||||||
|
New in Zulip 8.0 (feature level 191). Previously, groups
|
||||||
|
could be mentioned if and only if they were not system groups.
|
||||||
|
- $ref: "#/components/schemas/CanMentionGroup"
|
||||||
example: 11
|
example: 11
|
||||||
required:
|
required:
|
||||||
- name
|
- name
|
||||||
|
|
|
@ -216,6 +216,7 @@ from zerver.lib.test_helpers import (
|
||||||
from zerver.lib.timestamp import convert_to_UTC, datetime_to_timestamp
|
from zerver.lib.timestamp import convert_to_UTC, datetime_to_timestamp
|
||||||
from zerver.lib.topic import TOPIC_NAME
|
from zerver.lib.topic import TOPIC_NAME
|
||||||
from zerver.lib.types import ProfileDataElementUpdateDict
|
from zerver.lib.types import ProfileDataElementUpdateDict
|
||||||
|
from zerver.lib.user_groups import AnonymousSettingGroupDict
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
Attachment,
|
Attachment,
|
||||||
CustomProfileField,
|
CustomProfileField,
|
||||||
|
@ -231,6 +232,7 @@ from zerver.models import (
|
||||||
RealmUserDefault,
|
RealmUserDefault,
|
||||||
Service,
|
Service,
|
||||||
Stream,
|
Stream,
|
||||||
|
UserGroup,
|
||||||
UserMessage,
|
UserMessage,
|
||||||
UserPresence,
|
UserPresence,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
|
@ -1696,6 +1698,34 @@ class NormalActionsTest(BaseAction):
|
||||||
self.user_profile.realm, "backend", [othello], "Backend team", acting_user=None
|
self.user_profile.realm, "backend", [othello], "Backend team", acting_user=None
|
||||||
)
|
)
|
||||||
check_user_group_add("events[0]", events[0])
|
check_user_group_add("events[0]", events[0])
|
||||||
|
everyone_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.EVERYONE, realm=self.user_profile.realm, is_system_group=True
|
||||||
|
)
|
||||||
|
self.assertEqual(events[0]["group"]["can_mention_group"], everyone_group.id)
|
||||||
|
|
||||||
|
moderators_group = NamedUserGroup.objects.get(
|
||||||
|
name=SystemGroups.MODERATORS, realm=self.user_profile.realm, is_system_group=True
|
||||||
|
)
|
||||||
|
user_group = UserGroup.objects.create(realm=self.user_profile.realm)
|
||||||
|
user_group.direct_members.set([othello])
|
||||||
|
user_group.direct_subgroups.set([moderators_group])
|
||||||
|
|
||||||
|
with self.verify_action() as events:
|
||||||
|
check_add_user_group(
|
||||||
|
self.user_profile.realm,
|
||||||
|
"frontend",
|
||||||
|
[othello],
|
||||||
|
"",
|
||||||
|
{"can_mention_group": user_group},
|
||||||
|
acting_user=None,
|
||||||
|
)
|
||||||
|
check_user_group_add("events[0]", events[0])
|
||||||
|
self.assertEqual(
|
||||||
|
events[0]["group"]["can_mention_group"],
|
||||||
|
AnonymousSettingGroupDict(
|
||||||
|
direct_members=[othello.id], direct_subgroups=[moderators_group.id]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Test name update
|
# Test name update
|
||||||
backend = NamedUserGroup.objects.get(name="backend")
|
backend = NamedUserGroup.objects.get(name="backend")
|
||||||
|
@ -1710,9 +1740,6 @@ class NormalActionsTest(BaseAction):
|
||||||
check_user_group_update("events[0]", events[0], "description")
|
check_user_group_update("events[0]", events[0], "description")
|
||||||
|
|
||||||
# Test can_mention_group setting update
|
# Test can_mention_group setting update
|
||||||
moderators_group = NamedUserGroup.objects.get(
|
|
||||||
name="role:moderators", realm=self.user_profile.realm, is_system_group=True
|
|
||||||
)
|
|
||||||
with self.verify_action() as events:
|
with self.verify_action() as events:
|
||||||
do_change_user_group_permission_setting(
|
do_change_user_group_permission_setting(
|
||||||
backend, "can_mention_group", moderators_group, acting_user=None
|
backend, "can_mention_group", moderators_group, acting_user=None
|
||||||
|
|
|
@ -434,6 +434,47 @@ class UserGroupAPITestCase(UserGroupTestCase):
|
||||||
marketing_group = NamedUserGroup.objects.get(name="marketing", realm=hamlet.realm)
|
marketing_group = NamedUserGroup.objects.get(name="marketing", realm=hamlet.realm)
|
||||||
self.assertEqual(marketing_group.can_mention_group, nobody_group.usergroup_ptr)
|
self.assertEqual(marketing_group.can_mention_group, nobody_group.usergroup_ptr)
|
||||||
|
|
||||||
|
othello = self.example_user("othello")
|
||||||
|
params = {
|
||||||
|
"name": "backend",
|
||||||
|
"members": orjson.dumps([hamlet.id]).decode(),
|
||||||
|
"description": "Backend team",
|
||||||
|
"can_mention_group": orjson.dumps(
|
||||||
|
{
|
||||||
|
"direct_members": [othello.id],
|
||||||
|
"direct_subgroups": [leadership_group.id, moderators_group.id],
|
||||||
|
}
|
||||||
|
).decode(),
|
||||||
|
}
|
||||||
|
result = self.client_post("/json/user_groups/create", info=params)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
backend_group = NamedUserGroup.objects.get(name="backend", realm=hamlet.realm)
|
||||||
|
self.assertCountEqual(
|
||||||
|
list(backend_group.can_mention_group.direct_members.all()),
|
||||||
|
[othello],
|
||||||
|
)
|
||||||
|
self.assertCountEqual(
|
||||||
|
list(backend_group.can_mention_group.direct_subgroups.all()),
|
||||||
|
[leadership_group, moderators_group],
|
||||||
|
)
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"name": "help",
|
||||||
|
"members": orjson.dumps([hamlet.id]).decode(),
|
||||||
|
"description": "Troubleshooting team",
|
||||||
|
"can_mention_group": orjson.dumps(
|
||||||
|
{
|
||||||
|
"direct_members": [],
|
||||||
|
"direct_subgroups": [moderators_group.id],
|
||||||
|
}
|
||||||
|
).decode(),
|
||||||
|
}
|
||||||
|
result = self.client_post("/json/user_groups/create", info=params)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
help_group = NamedUserGroup.objects.get(name="help", realm=hamlet.realm)
|
||||||
|
# We do not create a new UserGroup object in such case.
|
||||||
|
self.assertEqual(help_group.can_mention_group_id, moderators_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
|
||||||
)
|
)
|
||||||
|
@ -471,6 +512,34 @@ 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_error(result, "Invalid user group")
|
self.assert_json_error(result, "Invalid user group")
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"name": "frontend",
|
||||||
|
"members": orjson.dumps([hamlet.id]).decode(),
|
||||||
|
"description": "Frontend team",
|
||||||
|
"can_mention_group": orjson.dumps(
|
||||||
|
{
|
||||||
|
"direct_members": [1111],
|
||||||
|
"direct_subgroups": [leadership_group.id, moderators_group.id],
|
||||||
|
}
|
||||||
|
).decode(),
|
||||||
|
}
|
||||||
|
result = self.client_post("/json/user_groups/create", info=params)
|
||||||
|
self.assert_json_error(result, "Invalid user ID: 1111")
|
||||||
|
|
||||||
|
params = {
|
||||||
|
"name": "frontend",
|
||||||
|
"members": orjson.dumps([hamlet.id]).decode(),
|
||||||
|
"description": "Frontend team",
|
||||||
|
"can_mention_group": orjson.dumps(
|
||||||
|
{
|
||||||
|
"direct_members": [othello.id],
|
||||||
|
"direct_subgroups": [1111, moderators_group.id],
|
||||||
|
}
|
||||||
|
).decode(),
|
||||||
|
}
|
||||||
|
result = self.client_post("/json/user_groups/create", info=params)
|
||||||
|
self.assert_json_error(result, "Invalid user group ID: 1111")
|
||||||
|
|
||||||
def test_user_group_get(self) -> None:
|
def test_user_group_get(self) -> None:
|
||||||
# Test success
|
# Test success
|
||||||
user_profile = self.example_user("hamlet")
|
user_profile = self.example_user("hamlet")
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from typing import List, Optional, Sequence
|
from typing import Dict, List, Optional, Sequence, Union
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
|
@ -23,7 +23,9 @@ from zerver.lib.exceptions import JsonableError
|
||||||
from zerver.lib.mention import MentionBackend, silent_mention_syntax_for_user
|
from zerver.lib.mention import MentionBackend, silent_mention_syntax_for_user
|
||||||
from zerver.lib.request import REQ, has_request_variables
|
from zerver.lib.request import REQ, has_request_variables
|
||||||
from zerver.lib.response import json_success
|
from zerver.lib.response import json_success
|
||||||
|
from zerver.lib.types import Validator
|
||||||
from zerver.lib.user_groups import (
|
from zerver.lib.user_groups import (
|
||||||
|
AnonymousSettingGroupDict,
|
||||||
access_user_group_by_id,
|
access_user_group_by_id,
|
||||||
access_user_group_for_setting,
|
access_user_group_for_setting,
|
||||||
check_user_group_name,
|
check_user_group_name,
|
||||||
|
@ -36,12 +38,40 @@ from zerver.lib.user_groups import (
|
||||||
user_groups_in_realm_serialized,
|
user_groups_in_realm_serialized,
|
||||||
)
|
)
|
||||||
from zerver.lib.users import access_user_by_id, user_ids_to_users
|
from zerver.lib.users import access_user_by_id, user_ids_to_users
|
||||||
from zerver.lib.validator import check_bool, check_int, check_list
|
from zerver.lib.validator import check_bool, check_dict_only, check_int, check_list, check_union
|
||||||
from zerver.models import NamedUserGroup, UserProfile
|
from zerver.models import NamedUserGroup, UserProfile
|
||||||
from zerver.models.users import get_system_bot
|
from zerver.models.users import get_system_bot
|
||||||
from zerver.views.streams import compose_views
|
from zerver.views.streams import compose_views
|
||||||
|
|
||||||
|
|
||||||
|
def parse_group_setting_value(
|
||||||
|
setting_value: Union[int, Dict[str, List[int]]],
|
||||||
|
) -> Union[int, AnonymousSettingGroupDict]:
|
||||||
|
if isinstance(setting_value, int):
|
||||||
|
return setting_value
|
||||||
|
|
||||||
|
if len(setting_value["direct_members"]) == 0 and len(setting_value["direct_subgroups"]) == 1:
|
||||||
|
return setting_value["direct_subgroups"][0]
|
||||||
|
|
||||||
|
return AnonymousSettingGroupDict(
|
||||||
|
direct_members=setting_value["direct_members"],
|
||||||
|
direct_subgroups=setting_value["direct_subgroups"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
check_group_setting: Validator[Union[int, Dict[str, List[int]]]] = check_union(
|
||||||
|
[
|
||||||
|
check_int,
|
||||||
|
check_dict_only(
|
||||||
|
[
|
||||||
|
("direct_members", check_list(check_int)),
|
||||||
|
("direct_subgroups", check_list(check_int)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@require_user_group_edit_permission
|
@require_user_group_edit_permission
|
||||||
@has_request_variables
|
@has_request_variables
|
||||||
def add_user_group(
|
def add_user_group(
|
||||||
|
@ -50,8 +80,8 @@ def add_user_group(
|
||||||
name: str = REQ(),
|
name: str = REQ(),
|
||||||
members: Sequence[int] = REQ(json_validator=check_list(check_int), default=[]),
|
members: Sequence[int] = REQ(json_validator=check_list(check_int), default=[]),
|
||||||
description: str = REQ(),
|
description: str = REQ(),
|
||||||
can_mention_group_id: Optional[int] = REQ(
|
can_mention_group: Optional[Union[Dict[str, List[int]], int]] = REQ(
|
||||||
"can_mention_group", json_validator=check_int, default=None
|
json_validator=check_group_setting, default=None
|
||||||
),
|
),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
user_profiles = user_ids_to_users(members, user_profile.realm)
|
user_profiles = user_ids_to_users(members, user_profile.realm)
|
||||||
|
@ -60,15 +90,13 @@ def add_user_group(
|
||||||
group_settings_map = {}
|
group_settings_map = {}
|
||||||
request_settings_dict = locals()
|
request_settings_dict = locals()
|
||||||
for setting_name, permission_config in NamedUserGroup.GROUP_PERMISSION_SETTINGS.items():
|
for setting_name, permission_config in NamedUserGroup.GROUP_PERMISSION_SETTINGS.items():
|
||||||
setting_group_id_name = permission_config.id_field_name
|
if setting_name not in request_settings_dict: # nocoverage
|
||||||
|
|
||||||
if setting_group_id_name not in request_settings_dict: # nocoverage
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if request_settings_dict[setting_group_id_name] is not None:
|
if request_settings_dict[setting_name] is not None:
|
||||||
setting_value_group_id = request_settings_dict[setting_group_id_name]
|
setting_value = parse_group_setting_value(request_settings_dict[setting_name])
|
||||||
setting_value_group = access_user_group_for_setting(
|
setting_value_group = access_user_group_for_setting(
|
||||||
setting_value_group_id,
|
setting_value,
|
||||||
user_profile,
|
user_profile,
|
||||||
setting_name=setting_name,
|
setting_name=setting_name,
|
||||||
permission_configuration=permission_config,
|
permission_configuration=permission_config,
|
||||||
|
|
Loading…
Reference in New Issue