mirror of https://github.com/zulip/zulip.git
streams: Allow setting can_remove_subscribers_group_id while creating streams.
This commit adds API support to set can_remove_subscribers_group setting when creating streams.
This commit is contained in:
parent
c3759814be
commit
73f11853ec
|
@ -25,6 +25,10 @@ format used by the Zulip server that they are interacting with.
|
||||||
* [`PATCH /streams/{stream_id}`](/api/update-stream): Added
|
* [`PATCH /streams/{stream_id}`](/api/update-stream): Added
|
||||||
`can_remove_subscribers_group_id` parameter to support
|
`can_remove_subscribers_group_id` parameter to support
|
||||||
changing `can_remove_subscribers_group` setting.
|
changing `can_remove_subscribers_group` setting.
|
||||||
|
* [`POST /users/me/subscriptions`](/api/subscribe): Added
|
||||||
|
`can_remove_subscribers_group_id` parameter to set
|
||||||
|
`can_remove_subscribers_group` setting while creating
|
||||||
|
streams.
|
||||||
|
|
||||||
**Feature level 160**
|
**Feature level 160**
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||||
# Changes should be accompanied by documentation explaining what the
|
# Changes should be accompanied by documentation explaining what the
|
||||||
# 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 = 159
|
API_FEATURE_LEVEL = 161
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -62,6 +62,7 @@ class StreamDict(TypedDict, total=False):
|
||||||
stream_post_policy: int
|
stream_post_policy: int
|
||||||
history_public_to_subscribers: Optional[bool]
|
history_public_to_subscribers: Optional[bool]
|
||||||
message_retention_days: Optional[int]
|
message_retention_days: Optional[int]
|
||||||
|
can_remove_subscribers_group: Optional[UserGroup]
|
||||||
|
|
||||||
|
|
||||||
def get_stream_permission_policy_name(
|
def get_stream_permission_policy_name(
|
||||||
|
@ -125,15 +126,19 @@ def create_stream_if_needed(
|
||||||
history_public_to_subscribers: Optional[bool] = None,
|
history_public_to_subscribers: Optional[bool] = None,
|
||||||
stream_description: str = "",
|
stream_description: str = "",
|
||||||
message_retention_days: Optional[int] = None,
|
message_retention_days: Optional[int] = None,
|
||||||
|
can_remove_subscribers_group: Optional[UserGroup] = None,
|
||||||
acting_user: Optional[UserProfile] = None,
|
acting_user: Optional[UserProfile] = None,
|
||||||
) -> Tuple[Stream, bool]:
|
) -> Tuple[Stream, bool]:
|
||||||
history_public_to_subscribers = get_default_value_for_history_public_to_subscribers(
|
history_public_to_subscribers = get_default_value_for_history_public_to_subscribers(
|
||||||
realm, invite_only, history_public_to_subscribers
|
realm, invite_only, history_public_to_subscribers
|
||||||
)
|
)
|
||||||
administrators_user_group = UserGroup.objects.get(
|
|
||||||
name=UserGroup.ADMINISTRATORS_GROUP_NAME, is_system_group=True, realm=realm
|
|
||||||
)
|
|
||||||
|
|
||||||
|
if can_remove_subscribers_group is None:
|
||||||
|
can_remove_subscribers_group = UserGroup.objects.get(
|
||||||
|
name=UserGroup.ADMINISTRATORS_GROUP_NAME, is_system_group=True, realm=realm
|
||||||
|
)
|
||||||
|
|
||||||
|
assert can_remove_subscribers_group is not None
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
(stream, created) = Stream.objects.get_or_create(
|
(stream, created) = Stream.objects.get_or_create(
|
||||||
realm=realm,
|
realm=realm,
|
||||||
|
@ -147,7 +152,7 @@ def create_stream_if_needed(
|
||||||
history_public_to_subscribers=history_public_to_subscribers,
|
history_public_to_subscribers=history_public_to_subscribers,
|
||||||
is_in_zephyr_realm=realm.is_zephyr_mirror_realm,
|
is_in_zephyr_realm=realm.is_zephyr_mirror_realm,
|
||||||
message_retention_days=message_retention_days,
|
message_retention_days=message_retention_days,
|
||||||
can_remove_subscribers_group=administrators_user_group,
|
can_remove_subscribers_group=can_remove_subscribers_group,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -196,6 +201,7 @@ def create_streams_if_needed(
|
||||||
history_public_to_subscribers=stream_dict.get("history_public_to_subscribers"),
|
history_public_to_subscribers=stream_dict.get("history_public_to_subscribers"),
|
||||||
stream_description=stream_dict.get("description", ""),
|
stream_description=stream_dict.get("description", ""),
|
||||||
message_retention_days=stream_dict.get("message_retention_days", None),
|
message_retention_days=stream_dict.get("message_retention_days", None),
|
||||||
|
can_remove_subscribers_group=stream_dict.get("can_remove_subscribers_group", None),
|
||||||
acting_user=acting_user,
|
acting_user=acting_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -7855,6 +7855,7 @@ paths:
|
||||||
- $ref: "#/components/parameters/HistoryPublicToSubscribers"
|
- $ref: "#/components/parameters/HistoryPublicToSubscribers"
|
||||||
- $ref: "#/components/parameters/StreamPostPolicy"
|
- $ref: "#/components/parameters/StreamPostPolicy"
|
||||||
- $ref: "#/components/parameters/MessageRetentionDays"
|
- $ref: "#/components/parameters/MessageRetentionDays"
|
||||||
|
- $ref: "#/components/parameters/CanRemoveSubscribersGroupId"
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Success.
|
description: Success.
|
||||||
|
|
|
@ -269,6 +269,9 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
self.assertTrue(self.example_user("desdemona").id in events[0]["users"])
|
self.assertTrue(self.example_user("desdemona").id in events[0]["users"])
|
||||||
self.assertEqual(events[0]["event"]["streams"][0]["name"], "Private stream")
|
self.assertEqual(events[0]["event"]["streams"][0]["name"], "Private stream")
|
||||||
|
|
||||||
|
moderators_system_group = UserGroup.objects.get(
|
||||||
|
name="@role:moderators", realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
new_streams, existing_streams = create_streams_if_needed(
|
new_streams, existing_streams = create_streams_if_needed(
|
||||||
realm,
|
realm,
|
||||||
[
|
[
|
||||||
|
@ -278,6 +281,7 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
"invite_only": True,
|
"invite_only": True,
|
||||||
"stream_post_policy": Stream.STREAM_POST_POLICY_ADMINS,
|
"stream_post_policy": Stream.STREAM_POST_POLICY_ADMINS,
|
||||||
"message_retention_days": -1,
|
"message_retention_days": -1,
|
||||||
|
"can_remove_subscribers_group": moderators_system_group,
|
||||||
}
|
}
|
||||||
for (stream_name, stream_description) in zip(stream_names, stream_descriptions)
|
for (stream_name, stream_description) in zip(stream_names, stream_descriptions)
|
||||||
],
|
],
|
||||||
|
@ -294,6 +298,7 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
self.assertTrue(stream.invite_only)
|
self.assertTrue(stream.invite_only)
|
||||||
self.assertTrue(stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS)
|
self.assertTrue(stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS)
|
||||||
self.assertTrue(stream.message_retention_days == -1)
|
self.assertTrue(stream.message_retention_days == -1)
|
||||||
|
self.assertEqual(stream.can_remove_subscribers_group.id, moderators_system_group.id)
|
||||||
|
|
||||||
new_streams, existing_streams = create_streams_if_needed(
|
new_streams, existing_streams = create_streams_if_needed(
|
||||||
realm,
|
realm,
|
||||||
|
@ -466,6 +471,78 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
# But it should be marked as read for Iago, the stream creator.
|
# But it should be marked as read for Iago, the stream creator.
|
||||||
self.assert_length(iago_unread_messages, 0)
|
self.assert_length(iago_unread_messages, 0)
|
||||||
|
|
||||||
|
def test_can_remove_subscribers_group_on_stream_creation(self) -> None:
|
||||||
|
user = self.example_user("hamlet")
|
||||||
|
realm = user.realm
|
||||||
|
self.login_user(user)
|
||||||
|
moderators_system_group = UserGroup.objects.get(
|
||||||
|
name="@role:moderators", realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
admins_system_group = UserGroup.objects.get(
|
||||||
|
name="@role:administrators", realm=realm, is_system_group=True
|
||||||
|
)
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
"subscriptions": orjson.dumps(
|
||||||
|
[{"name": "new_stream1", "description": "First new stream"}]
|
||||||
|
).decode(),
|
||||||
|
"can_remove_subscribers_group_id": orjson.dumps(moderators_system_group.id).decode(),
|
||||||
|
}
|
||||||
|
result = self.api_post(user, "/api/v1/users/me/subscriptions", post_data, subdomain="zulip")
|
||||||
|
self.assert_json_success(result)
|
||||||
|
stream = get_stream("new_stream1", realm)
|
||||||
|
self.assertEqual(stream.can_remove_subscribers_group.id, moderators_system_group.id)
|
||||||
|
|
||||||
|
post_data = {
|
||||||
|
"subscriptions": orjson.dumps(
|
||||||
|
[{"name": "new_stream2", "description": "Second new stream"}]
|
||||||
|
).decode(),
|
||||||
|
}
|
||||||
|
result = self.api_post(user, "/api/v1/users/me/subscriptions", post_data, subdomain="zulip")
|
||||||
|
self.assert_json_success(result)
|
||||||
|
stream = get_stream("new_stream2", realm)
|
||||||
|
self.assertEqual(stream.can_remove_subscribers_group.id, admins_system_group.id)
|
||||||
|
|
||||||
|
hamletcharacters_group = UserGroup.objects.get(name="hamletcharacters", realm=realm)
|
||||||
|
post_data = {
|
||||||
|
"subscriptions": orjson.dumps(
|
||||||
|
[{"name": "new_stream3", "description": "Third new stream"}]
|
||||||
|
).decode(),
|
||||||
|
"can_remove_subscribers_group_id": orjson.dumps(hamletcharacters_group.id).decode(),
|
||||||
|
}
|
||||||
|
result = self.api_post(user, "/api/v1/users/me/subscriptions", post_data, subdomain="zulip")
|
||||||
|
self.assert_json_error(
|
||||||
|
result, "'can_remove_subscribers_group' must be a system user group."
|
||||||
|
)
|
||||||
|
|
||||||
|
internet_group = UserGroup.objects.get(
|
||||||
|
name="@role:internet", is_system_group=True, realm=realm
|
||||||
|
)
|
||||||
|
post_data = {
|
||||||
|
"subscriptions": orjson.dumps(
|
||||||
|
[{"name": "new_stream3", "description": "Third new stream"}]
|
||||||
|
).decode(),
|
||||||
|
"can_remove_subscribers_group_id": orjson.dumps(internet_group.id).decode(),
|
||||||
|
}
|
||||||
|
result = self.api_post(user, "/api/v1/users/me/subscriptions", post_data, subdomain="zulip")
|
||||||
|
self.assert_json_error(
|
||||||
|
result,
|
||||||
|
"'can_remove_subscribers_group' setting cannot be set to '@role:internet' group.",
|
||||||
|
)
|
||||||
|
|
||||||
|
owners_group = UserGroup.objects.get(name="@role:owners", is_system_group=True, realm=realm)
|
||||||
|
post_data = {
|
||||||
|
"subscriptions": orjson.dumps(
|
||||||
|
[{"name": "new_stream3", "description": "Third new stream"}]
|
||||||
|
).decode(),
|
||||||
|
"can_remove_subscribers_group_id": orjson.dumps(owners_group.id).decode(),
|
||||||
|
}
|
||||||
|
result = self.api_post(user, "/api/v1/users/me/subscriptions", post_data, subdomain="zulip")
|
||||||
|
self.assert_json_error(
|
||||||
|
result,
|
||||||
|
"'can_remove_subscribers_group' setting cannot be set to '@role:owners' group.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RecipientTest(ZulipTestCase):
|
class RecipientTest(ZulipTestCase):
|
||||||
def test_recipient(self) -> None:
|
def test_recipient(self) -> None:
|
||||||
|
@ -4325,7 +4402,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
|
|
||||||
# Now add ourselves
|
# Now add ourselves
|
||||||
with self.tornado_redirected_to_list(events, expected_num_events=2):
|
with self.tornado_redirected_to_list(events, expected_num_events=2):
|
||||||
with self.assert_database_query_count(12):
|
with self.assert_database_query_count(13):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
streams_to_sub,
|
streams_to_sub,
|
||||||
|
@ -4650,7 +4727,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
# any tornado subscription events
|
# any tornado subscription events
|
||||||
events: List[Mapping[str, Any]] = []
|
events: List[Mapping[str, Any]] = []
|
||||||
with self.tornado_redirected_to_list(events, expected_num_events=0):
|
with self.tornado_redirected_to_list(events, expected_num_events=0):
|
||||||
with self.assert_database_query_count(4):
|
with self.assert_database_query_count(5):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
mit_user,
|
mit_user,
|
||||||
stream_names,
|
stream_names,
|
||||||
|
@ -4698,7 +4775,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
|
|
||||||
test_user_ids = [user.id for user in test_users]
|
test_user_ids = [user.id for user in test_users]
|
||||||
|
|
||||||
with self.assert_database_query_count(19):
|
with self.assert_database_query_count(20):
|
||||||
with cache_tries_captured() as cache_tries:
|
with cache_tries_captured() as cache_tries:
|
||||||
with mock.patch("zerver.views.streams.send_messages_for_new_subscribers"):
|
with mock.patch("zerver.views.streams.send_messages_for_new_subscribers"):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
|
@ -5611,7 +5688,7 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
polonius.id,
|
polonius.id,
|
||||||
]
|
]
|
||||||
|
|
||||||
with self.assert_database_query_count(46):
|
with self.assert_database_query_count(47):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.user_profile,
|
self.user_profile,
|
||||||
streams,
|
streams,
|
||||||
|
|
|
@ -99,6 +99,7 @@ from zerver.lib.validator import (
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
Realm,
|
Realm,
|
||||||
Stream,
|
Stream,
|
||||||
|
UserGroup,
|
||||||
UserMessage,
|
UserMessage,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
get_active_user,
|
get_active_user,
|
||||||
|
@ -555,6 +556,7 @@ def add_subscriptions_backend(
|
||||||
message_retention_days: Union[str, int] = REQ(
|
message_retention_days: Union[str, int] = REQ(
|
||||||
json_validator=check_string_or_int, default=RETENTION_DEFAULT
|
json_validator=check_string_or_int, default=RETENTION_DEFAULT
|
||||||
),
|
),
|
||||||
|
can_remove_subscribers_group_id: Optional[int] = REQ(json_validator=check_int, default=None),
|
||||||
announce: bool = REQ(json_validator=check_bool, default=False),
|
announce: bool = REQ(json_validator=check_bool, default=False),
|
||||||
principals: Union[Sequence[str], Sequence[int]] = REQ(
|
principals: Union[Sequence[str], Sequence[int]] = REQ(
|
||||||
json_validator=check_principals,
|
json_validator=check_principals,
|
||||||
|
@ -565,6 +567,19 @@ def add_subscriptions_backend(
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
stream_dicts = []
|
stream_dicts = []
|
||||||
color_map = {}
|
color_map = {}
|
||||||
|
|
||||||
|
if can_remove_subscribers_group_id is not None:
|
||||||
|
can_remove_subscribers_group = access_user_group_for_setting(
|
||||||
|
can_remove_subscribers_group_id,
|
||||||
|
user_profile,
|
||||||
|
setting_name="can_remove_subscribers_group",
|
||||||
|
require_system_group=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
can_remove_subscribers_group = UserGroup.objects.get(
|
||||||
|
name="@role:administrators", realm=user_profile.realm, is_system_group=True
|
||||||
|
)
|
||||||
|
|
||||||
for stream_dict in streams_raw:
|
for stream_dict in streams_raw:
|
||||||
# 'color' field is optional
|
# 'color' field is optional
|
||||||
# check for its presence in the streams_raw first
|
# check for its presence in the streams_raw first
|
||||||
|
@ -585,6 +600,7 @@ def add_subscriptions_backend(
|
||||||
stream_dict_copy["message_retention_days"] = parse_message_retention_days(
|
stream_dict_copy["message_retention_days"] = parse_message_retention_days(
|
||||||
message_retention_days, Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP
|
message_retention_days, Stream.MESSAGE_RETENTION_SPECIAL_VALUES_MAP
|
||||||
)
|
)
|
||||||
|
stream_dict_copy["can_remove_subscribers_group"] = can_remove_subscribers_group
|
||||||
|
|
||||||
stream_dicts.append(stream_dict_copy)
|
stream_dicts.append(stream_dict_copy)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue