mirror of https://github.com/zulip/zulip.git
streams: Backend changes to support anonymous groups.
can_remove_subscribers_group setting can now be set to anonymous user groups. Co-authored-by: Sahil Batra <sahil@zulip.com>
This commit is contained in:
parent
005a27a9cf
commit
b6ebf143cc
|
@ -20,6 +20,18 @@ format used by the Zulip server that they are interacting with.
|
||||||
|
|
||||||
## Changes in Zulip 10.0
|
## Changes in Zulip 10.0
|
||||||
|
|
||||||
|
**Feature level 320**
|
||||||
|
|
||||||
|
* [`GET /users/me/subscriptions`](/api/get-subscriptions),
|
||||||
|
[`GET /streams`](/api/get-streams), [`GET /events`](/api/get-events),
|
||||||
|
[`POST /register`](/api/register-queue): `can_remove_subscribers_group`
|
||||||
|
field can now 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.
|
||||||
|
* [`POST /users/me/subscriptions`](/api/subscribe),
|
||||||
|
[`PATCH /streams/{stream_id}`](/api/update-stream): The
|
||||||
|
`can_remove_subscribers_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 319**
|
**Feature level 319**
|
||||||
|
|
||||||
* [Markdown message
|
* [Markdown message
|
||||||
|
|
|
@ -34,7 +34,9 @@ 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 = 319 # Last bumped for message-link class
|
API_FEATURE_LEVEL = (
|
||||||
|
320 # Last bumped for supporting anonymous groups for can_remove_subscribers_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
|
||||||
|
|
|
@ -40,13 +40,18 @@ from zerver.lib.streams import (
|
||||||
can_access_stream_user_ids,
|
can_access_stream_user_ids,
|
||||||
check_basic_stream_access,
|
check_basic_stream_access,
|
||||||
get_occupied_streams,
|
get_occupied_streams,
|
||||||
|
get_setting_values_for_group_settings,
|
||||||
get_stream_permission_policy_name,
|
get_stream_permission_policy_name,
|
||||||
render_stream_description,
|
render_stream_description,
|
||||||
send_stream_creation_event,
|
send_stream_creation_event,
|
||||||
stream_to_dict,
|
stream_to_dict,
|
||||||
)
|
)
|
||||||
from zerver.lib.subscription_info import get_subscribers_query
|
from zerver.lib.subscription_info import get_subscribers_query
|
||||||
from zerver.lib.types import APISubscriptionDict
|
from zerver.lib.types import AnonymousSettingGroupDict, APISubscriptionDict
|
||||||
|
from zerver.lib.user_groups import (
|
||||||
|
get_group_setting_value_for_api,
|
||||||
|
get_group_setting_value_for_audit_log_data,
|
||||||
|
)
|
||||||
from zerver.lib.users import (
|
from zerver.lib.users import (
|
||||||
get_subscribers_of_target_user_subscriptions,
|
get_subscribers_of_target_user_subscriptions,
|
||||||
get_users_involved_in_dms_with_target_users,
|
get_users_involved_in_dms_with_target_users,
|
||||||
|
@ -349,13 +354,21 @@ def send_subscription_add_events(
|
||||||
subscribers = list(subscriber_dict[stream.id])
|
subscribers = list(subscriber_dict[stream.id])
|
||||||
stream_subscribers_dict[stream.id] = subscribers
|
stream_subscribers_dict[stream.id] = subscribers
|
||||||
|
|
||||||
|
setting_group_ids = set()
|
||||||
|
for sub_info in sub_info_list:
|
||||||
|
stream = sub_info.stream
|
||||||
|
for setting_name in Stream.stream_permission_group_settings:
|
||||||
|
setting_group_ids.add(getattr(stream, setting_name + "_id"))
|
||||||
|
|
||||||
|
setting_groups_dict = get_setting_values_for_group_settings(list(setting_group_ids))
|
||||||
|
|
||||||
for user_id, sub_infos in info_by_user.items():
|
for user_id, sub_infos in info_by_user.items():
|
||||||
sub_dicts: list[APISubscriptionDict] = []
|
sub_dicts: list[APISubscriptionDict] = []
|
||||||
for sub_info in sub_infos:
|
for sub_info in sub_infos:
|
||||||
stream = sub_info.stream
|
stream = sub_info.stream
|
||||||
stream_subscribers = stream_subscribers_dict[stream.id]
|
stream_subscribers = stream_subscribers_dict[stream.id]
|
||||||
subscription = sub_info.sub
|
subscription = sub_info.sub
|
||||||
stream_dict = stream_to_dict(stream, recent_traffic)
|
stream_dict = stream_to_dict(stream, recent_traffic, setting_groups_dict)
|
||||||
# This is verbose as we cannot unpack existing TypedDict
|
# This is verbose as we cannot unpack existing TypedDict
|
||||||
# to initialize another TypedDict while making mypy happy.
|
# to initialize another TypedDict while making mypy happy.
|
||||||
# https://github.com/python/mypy/issues/5382
|
# https://github.com/python/mypy/issues/5382
|
||||||
|
@ -447,6 +460,14 @@ def send_stream_creation_events_for_previously_inaccessible_streams(
|
||||||
stream_ids = set(altered_user_dict.keys())
|
stream_ids = set(altered_user_dict.keys())
|
||||||
recent_traffic = get_streams_traffic(stream_ids, realm)
|
recent_traffic = get_streams_traffic(stream_ids, realm)
|
||||||
|
|
||||||
|
setting_group_ids = set()
|
||||||
|
for stream_id in stream_ids:
|
||||||
|
stream = stream_dict[stream_id]
|
||||||
|
for setting_name in Stream.stream_permission_group_settings:
|
||||||
|
setting_group_ids.add(getattr(stream, setting_name + "_id"))
|
||||||
|
|
||||||
|
setting_groups_dict = get_setting_values_for_group_settings(list(setting_group_ids))
|
||||||
|
|
||||||
for stream_id, stream_users_ids in altered_user_dict.items():
|
for stream_id, stream_users_ids in altered_user_dict.items():
|
||||||
stream = stream_dict[stream_id]
|
stream = stream_dict[stream_id]
|
||||||
|
|
||||||
|
@ -467,7 +488,9 @@ def send_stream_creation_events_for_previously_inaccessible_streams(
|
||||||
notify_user_ids = list(stream_users_ids & altered_guests)
|
notify_user_ids = list(stream_users_ids & altered_guests)
|
||||||
|
|
||||||
if notify_user_ids:
|
if notify_user_ids:
|
||||||
send_stream_creation_event(realm, stream, notify_user_ids, recent_traffic)
|
send_stream_creation_event(
|
||||||
|
realm, stream, notify_user_ids, recent_traffic, setting_groups_dict
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def send_peer_subscriber_events(
|
def send_peer_subscriber_events(
|
||||||
|
@ -1561,16 +1584,30 @@ def do_change_stream_group_based_setting(
|
||||||
setting_name: str,
|
setting_name: str,
|
||||||
user_group: UserGroup,
|
user_group: UserGroup,
|
||||||
*,
|
*,
|
||||||
|
old_setting_api_value: int | AnonymousSettingGroupDict | None = None,
|
||||||
acting_user: UserProfile | None = None,
|
acting_user: UserProfile | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
old_user_group = getattr(stream, setting_name)
|
old_user_group = getattr(stream, setting_name)
|
||||||
old_user_group_id = None
|
|
||||||
if old_user_group is not None:
|
|
||||||
old_user_group_id = old_user_group.id
|
|
||||||
|
|
||||||
setattr(stream, setting_name, user_group)
|
setattr(stream, setting_name, user_group)
|
||||||
stream.save()
|
stream.save()
|
||||||
|
|
||||||
|
if old_setting_api_value is None:
|
||||||
|
# Most production callers will have computed this as part of
|
||||||
|
# verifying whether there's an actual change to make, but it
|
||||||
|
# feels quite clumsy to have to pass it from unit tests, so we
|
||||||
|
# compute it here if not provided by the caller.
|
||||||
|
old_setting_api_value = get_group_setting_value_for_api(old_user_group)
|
||||||
|
new_setting_api_value = get_group_setting_value_for_api(user_group)
|
||||||
|
|
||||||
|
if not hasattr(old_user_group, "named_user_group") and hasattr(user_group, "named_user_group"):
|
||||||
|
# We delete the UserGroup which the setting was set to
|
||||||
|
# previously if it does not have any linked NamedUserGroup
|
||||||
|
# object, as it is not used anywhere else. A new UserGroup
|
||||||
|
# object would be created if the setting is later set to
|
||||||
|
# a combination of users and groups.
|
||||||
|
old_user_group.delete()
|
||||||
|
|
||||||
RealmAuditLog.objects.create(
|
RealmAuditLog.objects.create(
|
||||||
realm=stream.realm,
|
realm=stream.realm,
|
||||||
acting_user=acting_user,
|
acting_user=acting_user,
|
||||||
|
@ -1578,8 +1615,12 @@ def do_change_stream_group_based_setting(
|
||||||
event_type=AuditLogEventType.CHANNEL_GROUP_BASED_SETTING_CHANGED,
|
event_type=AuditLogEventType.CHANNEL_GROUP_BASED_SETTING_CHANGED,
|
||||||
event_time=timezone_now(),
|
event_time=timezone_now(),
|
||||||
extra_data={
|
extra_data={
|
||||||
RealmAuditLog.OLD_VALUE: old_user_group_id,
|
RealmAuditLog.OLD_VALUE: get_group_setting_value_for_audit_log_data(
|
||||||
RealmAuditLog.NEW_VALUE: user_group.id,
|
old_setting_api_value
|
||||||
|
),
|
||||||
|
RealmAuditLog.NEW_VALUE: get_group_setting_value_for_audit_log_data(
|
||||||
|
new_setting_api_value
|
||||||
|
),
|
||||||
"property": setting_name,
|
"property": setting_name,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -1587,7 +1628,7 @@ def do_change_stream_group_based_setting(
|
||||||
op="update",
|
op="update",
|
||||||
type="stream",
|
type="stream",
|
||||||
property=setting_name,
|
property=setting_name,
|
||||||
value=user_group.id,
|
value=new_setting_api_value,
|
||||||
stream_id=stream.id,
|
stream_id=stream.id,
|
||||||
name=stream.name,
|
name=stream.name,
|
||||||
)
|
)
|
||||||
|
|
|
@ -45,13 +45,26 @@ from zerver.lib.data_types import (
|
||||||
make_checker,
|
make_checker,
|
||||||
)
|
)
|
||||||
from zerver.lib.topic import ORIG_TOPIC, TOPIC_LINKS, TOPIC_NAME
|
from zerver.lib.topic import ORIG_TOPIC, TOPIC_LINKS, TOPIC_NAME
|
||||||
|
from zerver.lib.types import AnonymousSettingGroupDict
|
||||||
from zerver.models import Realm, RealmUserDefault, Stream, UserProfile
|
from zerver.models import Realm, RealmUserDefault, Stream, UserProfile
|
||||||
|
|
||||||
|
group_setting_type = UnionType(
|
||||||
|
[
|
||||||
|
int,
|
||||||
|
DictType(
|
||||||
|
required_keys=[
|
||||||
|
("direct_members", ListType(int)),
|
||||||
|
("direct_subgroups", ListType(int)),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# These fields are used for "stream" events, and are included in the
|
# These fields are used for "stream" events, and are included in the
|
||||||
# larger "subscription" events that also contain personal settings.
|
# larger "subscription" events that also contain personal settings.
|
||||||
default_stream_fields = [
|
default_stream_fields = [
|
||||||
("is_archived", bool),
|
("is_archived", bool),
|
||||||
("can_remove_subscribers_group", int),
|
("can_remove_subscribers_group", group_setting_type),
|
||||||
("creator_id", OptionalType(int)),
|
("creator_id", OptionalType(int)),
|
||||||
("date_created", int),
|
("date_created", int),
|
||||||
("description", str),
|
("description", str),
|
||||||
|
@ -103,6 +116,12 @@ optional_value_type = UnionType(
|
||||||
bool,
|
bool,
|
||||||
int,
|
int,
|
||||||
str,
|
str,
|
||||||
|
DictType(
|
||||||
|
required_keys=[
|
||||||
|
("direct_members", ListType(int)),
|
||||||
|
("direct_subgroups", ListType(int)),
|
||||||
|
]
|
||||||
|
),
|
||||||
Equals(None),
|
Equals(None),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
@ -1046,18 +1065,6 @@ night_logo_data = DictType(
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
group_setting_type = UnionType(
|
|
||||||
[
|
|
||||||
int,
|
|
||||||
DictType(
|
|
||||||
required_keys=[
|
|
||||||
("direct_members", ListType(int)),
|
|
||||||
("direct_subgroups", ListType(int)),
|
|
||||||
]
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
group_setting_update_data_type = DictType(
|
group_setting_update_data_type = DictType(
|
||||||
required_keys=[],
|
required_keys=[],
|
||||||
optional_keys=[
|
optional_keys=[
|
||||||
|
@ -1442,7 +1449,7 @@ def check_stream_update(
|
||||||
assert value in Stream.STREAM_POST_POLICY_TYPES
|
assert value in Stream.STREAM_POST_POLICY_TYPES
|
||||||
elif prop == "can_remove_subscribers_group":
|
elif prop == "can_remove_subscribers_group":
|
||||||
assert extra_keys == set()
|
assert extra_keys == set()
|
||||||
assert isinstance(value, int)
|
assert isinstance(value, int | AnonymousSettingGroupDict)
|
||||||
elif prop == "first_message_id":
|
elif prop == "first_message_id":
|
||||||
assert extra_keys == set()
|
assert extra_keys == set()
|
||||||
assert isinstance(value, int)
|
assert isinstance(value, int)
|
||||||
|
|
|
@ -21,10 +21,14 @@ from zerver.lib.stream_subscription import (
|
||||||
from zerver.lib.stream_traffic import get_average_weekly_stream_traffic, get_streams_traffic
|
from zerver.lib.stream_traffic import get_average_weekly_stream_traffic, get_streams_traffic
|
||||||
from zerver.lib.string_validation import check_stream_name
|
from zerver.lib.string_validation import check_stream_name
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.types import APIStreamDict
|
from zerver.lib.types import AnonymousSettingGroupDict, APIStreamDict
|
||||||
from zerver.lib.user_groups import user_has_permission_for_group_setting
|
from zerver.lib.user_groups import (
|
||||||
|
get_group_setting_value_for_api,
|
||||||
|
user_has_permission_for_group_setting,
|
||||||
|
)
|
||||||
from zerver.models import (
|
from zerver.models import (
|
||||||
DefaultStreamGroup,
|
DefaultStreamGroup,
|
||||||
|
GroupGroupMembership,
|
||||||
NamedUserGroup,
|
NamedUserGroup,
|
||||||
Realm,
|
Realm,
|
||||||
RealmAuditLog,
|
RealmAuditLog,
|
||||||
|
@ -32,6 +36,7 @@ from zerver.models import (
|
||||||
Stream,
|
Stream,
|
||||||
Subscription,
|
Subscription,
|
||||||
UserGroup,
|
UserGroup,
|
||||||
|
UserGroupMembership,
|
||||||
UserProfile,
|
UserProfile,
|
||||||
)
|
)
|
||||||
from zerver.models.groups import SystemGroups
|
from zerver.models.groups import SystemGroups
|
||||||
|
@ -123,8 +128,13 @@ def send_stream_creation_event(
|
||||||
stream: Stream,
|
stream: Stream,
|
||||||
user_ids: list[int],
|
user_ids: list[int],
|
||||||
recent_traffic: dict[int, int] | None = None,
|
recent_traffic: dict[int, int] | None = None,
|
||||||
|
setting_groups_dict: dict[int, int | AnonymousSettingGroupDict] | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
event = dict(type="stream", op="create", streams=[stream_to_dict(stream, recent_traffic)])
|
event = dict(
|
||||||
|
type="stream",
|
||||||
|
op="create",
|
||||||
|
streams=[stream_to_dict(stream, recent_traffic, setting_groups_dict)],
|
||||||
|
)
|
||||||
send_event_on_commit(realm, event, user_ids)
|
send_event_on_commit(realm, event, user_ids)
|
||||||
|
|
||||||
|
|
||||||
|
@ -870,7 +880,11 @@ def get_occupied_streams(realm: Realm) -> QuerySet[Stream]:
|
||||||
return occupied_streams
|
return occupied_streams
|
||||||
|
|
||||||
|
|
||||||
def stream_to_dict(stream: Stream, recent_traffic: dict[int, int] | None = None) -> APIStreamDict:
|
def stream_to_dict(
|
||||||
|
stream: Stream,
|
||||||
|
recent_traffic: dict[int, int] | None = None,
|
||||||
|
setting_groups_dict: dict[int, int | AnonymousSettingGroupDict] | None = None,
|
||||||
|
) -> APIStreamDict:
|
||||||
if recent_traffic is not None:
|
if recent_traffic is not None:
|
||||||
stream_weekly_traffic = get_average_weekly_stream_traffic(
|
stream_weekly_traffic = get_average_weekly_stream_traffic(
|
||||||
stream.id, stream.date_created, recent_traffic
|
stream.id, stream.date_created, recent_traffic
|
||||||
|
@ -884,9 +898,16 @@ def stream_to_dict(stream: Stream, recent_traffic: dict[int, int] | None = None)
|
||||||
# passing stream data to spectators.
|
# passing stream data to spectators.
|
||||||
stream_weekly_traffic = None
|
stream_weekly_traffic = None
|
||||||
|
|
||||||
|
if setting_groups_dict is not None:
|
||||||
|
can_remove_subscribers_group = setting_groups_dict[stream.can_remove_subscribers_group_id]
|
||||||
|
else:
|
||||||
|
can_remove_subscribers_group = get_group_setting_value_for_api(
|
||||||
|
stream.can_remove_subscribers_group
|
||||||
|
)
|
||||||
|
|
||||||
return APIStreamDict(
|
return APIStreamDict(
|
||||||
is_archived=stream.deactivated,
|
is_archived=stream.deactivated,
|
||||||
can_remove_subscribers_group=stream.can_remove_subscribers_group_id,
|
can_remove_subscribers_group=can_remove_subscribers_group,
|
||||||
creator_id=stream.creator_id,
|
creator_id=stream.creator_id,
|
||||||
date_created=datetime_to_timestamp(stream.date_created),
|
date_created=datetime_to_timestamp(stream.date_created),
|
||||||
description=stream.description,
|
description=stream.description,
|
||||||
|
@ -978,6 +999,38 @@ def get_streams_for_user(
|
||||||
return list(streams)
|
return list(streams)
|
||||||
|
|
||||||
|
|
||||||
|
def get_setting_values_for_group_settings(
|
||||||
|
group_ids: list[int],
|
||||||
|
) -> dict[int, int | AnonymousSettingGroupDict]:
|
||||||
|
setting_value_dict = {}
|
||||||
|
setting_groups = UserGroup.objects.filter(id__in=group_ids).select_related("named_user_group")
|
||||||
|
anonymous_groups = []
|
||||||
|
for group in setting_groups:
|
||||||
|
if hasattr(group, "named_user_group"):
|
||||||
|
setting_value_dict[group.id] = group.id
|
||||||
|
else:
|
||||||
|
setting_value_dict[group.id] = AnonymousSettingGroupDict(
|
||||||
|
direct_members=[],
|
||||||
|
direct_subgroups=[],
|
||||||
|
)
|
||||||
|
anonymous_groups.append(group.id)
|
||||||
|
|
||||||
|
group_members = (
|
||||||
|
UserGroupMembership.objects.filter(user_group_id__in=anonymous_groups)
|
||||||
|
.exclude(user_profile__is_active=False)
|
||||||
|
.values_list("user_group_id", "user_profile_id")
|
||||||
|
)
|
||||||
|
group_subgroups = GroupGroupMembership.objects.filter(
|
||||||
|
supergroup_id__in=anonymous_groups
|
||||||
|
).values_list("supergroup_id", "subgroup_id")
|
||||||
|
for user_group_id, user_profile_id in group_members:
|
||||||
|
setting_value_dict[user_group_id].direct_members.append(user_profile_id)
|
||||||
|
for supergroup_id, subgroup_id in group_subgroups:
|
||||||
|
setting_value_dict[supergroup_id].direct_subgroups.append(subgroup_id)
|
||||||
|
|
||||||
|
return setting_value_dict
|
||||||
|
|
||||||
|
|
||||||
def do_get_streams(
|
def do_get_streams(
|
||||||
user_profile: UserProfile,
|
user_profile: UserProfile,
|
||||||
include_public: bool = True,
|
include_public: bool = True,
|
||||||
|
@ -1003,14 +1056,22 @@ def do_get_streams(
|
||||||
stream_ids = {stream.id for stream in streams}
|
stream_ids = {stream.id for stream in streams}
|
||||||
recent_traffic = get_streams_traffic(stream_ids, user_profile.realm)
|
recent_traffic = get_streams_traffic(stream_ids, user_profile.realm)
|
||||||
|
|
||||||
|
setting_group_ids = set()
|
||||||
|
for stream in streams:
|
||||||
|
for setting_name in Stream.stream_permission_group_settings:
|
||||||
|
setting_group_ids.add(getattr(stream, setting_name + "_id"))
|
||||||
|
|
||||||
|
setting_groups_dict = get_setting_values_for_group_settings(list(setting_group_ids))
|
||||||
|
|
||||||
stream_dicts = sorted(
|
stream_dicts = sorted(
|
||||||
(stream_to_dict(stream, recent_traffic) for stream in streams), key=lambda elt: elt["name"]
|
(stream_to_dict(stream, recent_traffic, setting_groups_dict) for stream in streams),
|
||||||
|
key=lambda elt: elt["name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
if include_default:
|
if include_default:
|
||||||
default_stream_ids = get_default_stream_ids_for_realm(user_profile.realm_id)
|
default_stream_ids = get_default_stream_ids_for_realm(user_profile.realm_id)
|
||||||
for stream in stream_dicts:
|
for stream_dict in stream_dicts:
|
||||||
stream["is_default"] = stream["stream_id"] in default_stream_ids
|
stream_dict["is_default"] = stream_dict["stream_id"] in default_stream_ids
|
||||||
|
|
||||||
return stream_dicts
|
return stream_dicts
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,14 @@ from zerver.lib.stream_subscription import (
|
||||||
get_stream_subscriptions_for_user,
|
get_stream_subscriptions_for_user,
|
||||||
)
|
)
|
||||||
from zerver.lib.stream_traffic import get_average_weekly_stream_traffic, get_streams_traffic
|
from zerver.lib.stream_traffic import get_average_weekly_stream_traffic, get_streams_traffic
|
||||||
from zerver.lib.streams import get_web_public_streams_queryset, subscribed_to_stream
|
from zerver.lib.streams import (
|
||||||
|
get_setting_values_for_group_settings,
|
||||||
|
get_web_public_streams_queryset,
|
||||||
|
subscribed_to_stream,
|
||||||
|
)
|
||||||
from zerver.lib.timestamp import datetime_to_timestamp
|
from zerver.lib.timestamp import datetime_to_timestamp
|
||||||
from zerver.lib.types import (
|
from zerver.lib.types import (
|
||||||
|
AnonymousSettingGroupDict,
|
||||||
APIStreamDict,
|
APIStreamDict,
|
||||||
NeverSubscribedStreamDict,
|
NeverSubscribedStreamDict,
|
||||||
RawStreamDict,
|
RawStreamDict,
|
||||||
|
@ -26,6 +31,7 @@ from zerver.lib.types import (
|
||||||
SubscriptionInfo,
|
SubscriptionInfo,
|
||||||
SubscriptionStreamDict,
|
SubscriptionStreamDict,
|
||||||
)
|
)
|
||||||
|
from zerver.lib.user_groups import get_group_setting_value_for_api
|
||||||
from zerver.models import Realm, Stream, Subscription, UserProfile
|
from zerver.models import Realm, Stream, Subscription, UserProfile
|
||||||
from zerver.models.streams import get_all_streams
|
from zerver.models.streams import get_all_streams
|
||||||
|
|
||||||
|
@ -43,7 +49,9 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo:
|
||||||
for stream in get_web_public_streams_queryset(realm):
|
for stream in get_web_public_streams_queryset(realm):
|
||||||
# Add Stream fields.
|
# Add Stream fields.
|
||||||
is_archived = stream.deactivated
|
is_archived = stream.deactivated
|
||||||
can_remove_subscribers_group_id = stream.can_remove_subscribers_group_id
|
can_remove_subscribers_group = get_group_setting_value_for_api(
|
||||||
|
stream.can_remove_subscribers_group
|
||||||
|
)
|
||||||
creator_id = stream.creator_id
|
creator_id = stream.creator_id
|
||||||
date_created = datetime_to_timestamp(stream.date_created)
|
date_created = datetime_to_timestamp(stream.date_created)
|
||||||
description = stream.description
|
description = stream.description
|
||||||
|
@ -76,7 +84,7 @@ def get_web_public_subs(realm: Realm) -> SubscriptionInfo:
|
||||||
sub = SubscriptionStreamDict(
|
sub = SubscriptionStreamDict(
|
||||||
is_archived=is_archived,
|
is_archived=is_archived,
|
||||||
audible_notifications=audible_notifications,
|
audible_notifications=audible_notifications,
|
||||||
can_remove_subscribers_group=can_remove_subscribers_group_id,
|
can_remove_subscribers_group=can_remove_subscribers_group,
|
||||||
color=color,
|
color=color,
|
||||||
creator_id=creator_id,
|
creator_id=creator_id,
|
||||||
date_created=date_created,
|
date_created=date_created,
|
||||||
|
@ -118,7 +126,9 @@ def build_unsubscribed_sub_from_stream_dict(
|
||||||
|
|
||||||
|
|
||||||
def build_stream_api_dict(
|
def build_stream_api_dict(
|
||||||
raw_stream_dict: RawStreamDict, recent_traffic: dict[int, int] | None
|
raw_stream_dict: RawStreamDict,
|
||||||
|
recent_traffic: dict[int, int] | None,
|
||||||
|
setting_groups_dict: dict[int, int | AnonymousSettingGroupDict],
|
||||||
) -> APIStreamDict:
|
) -> APIStreamDict:
|
||||||
# Add a few computed fields not directly from the data models.
|
# Add a few computed fields not directly from the data models.
|
||||||
if recent_traffic is not None:
|
if recent_traffic is not None:
|
||||||
|
@ -133,9 +143,13 @@ def build_stream_api_dict(
|
||||||
# migration.
|
# migration.
|
||||||
is_announcement_only = raw_stream_dict["stream_post_policy"] == Stream.STREAM_POST_POLICY_ADMINS
|
is_announcement_only = raw_stream_dict["stream_post_policy"] == Stream.STREAM_POST_POLICY_ADMINS
|
||||||
|
|
||||||
|
can_remove_subscribers_group = setting_groups_dict[
|
||||||
|
raw_stream_dict["can_remove_subscribers_group_id"]
|
||||||
|
]
|
||||||
|
|
||||||
return APIStreamDict(
|
return APIStreamDict(
|
||||||
is_archived=raw_stream_dict["deactivated"],
|
is_archived=raw_stream_dict["deactivated"],
|
||||||
can_remove_subscribers_group=raw_stream_dict["can_remove_subscribers_group_id"],
|
can_remove_subscribers_group=can_remove_subscribers_group,
|
||||||
creator_id=raw_stream_dict["creator_id"],
|
creator_id=raw_stream_dict["creator_id"],
|
||||||
date_created=datetime_to_timestamp(raw_stream_dict["date_created"]),
|
date_created=datetime_to_timestamp(raw_stream_dict["date_created"]),
|
||||||
description=raw_stream_dict["description"],
|
description=raw_stream_dict["description"],
|
||||||
|
@ -471,6 +485,13 @@ def gather_subscriptions_helper(
|
||||||
}
|
}
|
||||||
all_streams_map: dict[int, RawStreamDict] = {stream["id"]: stream for stream in all_streams}
|
all_streams_map: dict[int, RawStreamDict] = {stream["id"]: stream for stream in all_streams}
|
||||||
|
|
||||||
|
setting_group_ids = set()
|
||||||
|
for stream in all_streams:
|
||||||
|
for setting_name in Stream.stream_permission_group_settings:
|
||||||
|
setting_group_ids.add(stream[setting_name + "_id"])
|
||||||
|
|
||||||
|
setting_groups_dict = get_setting_values_for_group_settings(list(setting_group_ids))
|
||||||
|
|
||||||
sub_dicts_query: Iterable[RawSubscriptionDict] = (
|
sub_dicts_query: Iterable[RawSubscriptionDict] = (
|
||||||
get_stream_subscriptions_for_user(user_profile)
|
get_stream_subscriptions_for_user(user_profile)
|
||||||
.values(
|
.values(
|
||||||
|
@ -505,7 +526,9 @@ def gather_subscriptions_helper(
|
||||||
stream_id = get_stream_id(sub_dict)
|
stream_id = get_stream_id(sub_dict)
|
||||||
sub_unsub_stream_ids.add(stream_id)
|
sub_unsub_stream_ids.add(stream_id)
|
||||||
raw_stream_dict = all_streams_map[stream_id]
|
raw_stream_dict = all_streams_map[stream_id]
|
||||||
stream_api_dict = build_stream_api_dict(raw_stream_dict, recent_traffic)
|
stream_api_dict = build_stream_api_dict(
|
||||||
|
raw_stream_dict, recent_traffic, setting_groups_dict
|
||||||
|
)
|
||||||
stream_dict = build_stream_dict_for_sub(
|
stream_dict = build_stream_dict_for_sub(
|
||||||
user=user_profile,
|
user=user_profile,
|
||||||
sub_dict=sub_dict,
|
sub_dict=sub_dict,
|
||||||
|
|
|
@ -191,7 +191,7 @@ class SubscriptionStreamDict(TypedDict):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
audible_notifications: bool | None
|
audible_notifications: bool | None
|
||||||
can_remove_subscribers_group: int
|
can_remove_subscribers_group: int | AnonymousSettingGroupDict
|
||||||
color: str
|
color: str
|
||||||
creator_id: int | None
|
creator_id: int | None
|
||||||
date_created: int
|
date_created: int
|
||||||
|
@ -245,7 +245,7 @@ class DefaultStreamDict(TypedDict):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
is_archived: bool
|
is_archived: bool
|
||||||
can_remove_subscribers_group: int
|
can_remove_subscribers_group: int | AnonymousSettingGroupDict
|
||||||
creator_id: int | None
|
creator_id: int | None
|
||||||
date_created: int
|
date_created: int
|
||||||
description: str
|
description: str
|
||||||
|
|
|
@ -143,7 +143,7 @@ class Stream(models.Model):
|
||||||
|
|
||||||
stream_permission_group_settings = {
|
stream_permission_group_settings = {
|
||||||
"can_remove_subscribers_group": GroupPermissionSetting(
|
"can_remove_subscribers_group": GroupPermissionSetting(
|
||||||
require_system_group=True,
|
require_system_group=False,
|
||||||
allow_internet_group=False,
|
allow_internet_group=False,
|
||||||
allow_owners_group=False,
|
allow_owners_group=False,
|
||||||
allow_nobody_group=False,
|
allow_nobody_group=False,
|
||||||
|
|
|
@ -1459,7 +1459,33 @@ paths:
|
||||||
value:
|
value:
|
||||||
description: |
|
description: |
|
||||||
The new value of the changed property.
|
The new value of the changed property.
|
||||||
|
|
||||||
|
**Changes**: Starting with Zulip 10.0 (feature level 320), this
|
||||||
|
field can be an object for `can_remove_subscribers_group` property,
|
||||||
|
which is a [group-setting value][setting-values], when the setting
|
||||||
|
is set to a combination of users and groups.
|
||||||
|
|
||||||
|
[setting-values]: /api/group-setting-values
|
||||||
oneOf:
|
oneOf:
|
||||||
|
- type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
direct_members:
|
||||||
|
description: |
|
||||||
|
The list of IDs of individual users in the collection of users with this permission.
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
direct_subgroups:
|
||||||
|
description: |
|
||||||
|
The list of IDs of the groups in the collection of users with this permission.
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
description: |
|
||||||
|
If an object, it will be a [group-setting value][setting-values] with these fields:
|
||||||
|
|
||||||
|
[setting-values]: /api/group-setting-values
|
||||||
- type: integer
|
- type: integer
|
||||||
- type: boolean
|
- type: boolean
|
||||||
- type: string
|
- type: string
|
||||||
|
@ -10364,7 +10390,7 @@ paths:
|
||||||
message_retention_days:
|
message_retention_days:
|
||||||
$ref: "#/components/schemas/MessageRetentionDays"
|
$ref: "#/components/schemas/MessageRetentionDays"
|
||||||
can_remove_subscribers_group:
|
can_remove_subscribers_group:
|
||||||
$ref: "#/components/schemas/CanRemoveSubscribersGroupId"
|
$ref: "#/components/schemas/CanRemoveSubscribersGroup"
|
||||||
required:
|
required:
|
||||||
- subscriptions
|
- subscriptions
|
||||||
encoding:
|
encoding:
|
||||||
|
@ -19958,7 +19984,43 @@ paths:
|
||||||
message_retention_days:
|
message_retention_days:
|
||||||
$ref: "#/components/schemas/MessageRetentionDays"
|
$ref: "#/components/schemas/MessageRetentionDays"
|
||||||
can_remove_subscribers_group:
|
can_remove_subscribers_group:
|
||||||
$ref: "#/components/schemas/CanRemoveSubscribersGroupId"
|
description: |
|
||||||
|
The set of users who have permission to unsubscribe others from this
|
||||||
|
channel expressed as an [update to a group-setting value][update-group-setting].
|
||||||
|
|
||||||
|
Note that a user who is a member of the specified user group must
|
||||||
|
also [have access](/help/channel-permissions) to the channel in
|
||||||
|
order to unsubscribe others.
|
||||||
|
|
||||||
|
**Changes**: Prior to Zulip 10.0 (feature level 320), this value was
|
||||||
|
always the integer ID of a system group.
|
||||||
|
|
||||||
|
Before Zulip 8.0 (feature level 197), the `can_remove_subscribers_group`
|
||||||
|
setting was named `can_remove_subscribers_group_id`.
|
||||||
|
|
||||||
|
New in Zulip 7.0 (feature level 161).
|
||||||
|
type: object
|
||||||
|
additionalProperties: false
|
||||||
|
properties:
|
||||||
|
new:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
The new [group-setting value](/api/group-setting-values) for who would
|
||||||
|
have the permission to remove subscribers from the channel.
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
old:
|
||||||
|
allOf:
|
||||||
|
- description: |
|
||||||
|
The expected current [group-setting value](/api/group-setting-values)
|
||||||
|
for who has the permission to remove subscribers from the channel.
|
||||||
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
|
required:
|
||||||
|
- new
|
||||||
|
example:
|
||||||
|
{
|
||||||
|
"new": {"direct_members": [10], "direct_subgroups": [11]},
|
||||||
|
"old": 15,
|
||||||
|
}
|
||||||
encoding:
|
encoding:
|
||||||
is_private:
|
is_private:
|
||||||
contentType: application/json
|
contentType: application/json
|
||||||
|
@ -21819,16 +21881,7 @@ components:
|
||||||
**Changes**: Deprecated in Zulip 3.0 (feature level 1). Clients
|
**Changes**: Deprecated in Zulip 3.0 (feature level 1). Clients
|
||||||
should use `stream_post_policy` instead.
|
should use `stream_post_policy` instead.
|
||||||
can_remove_subscribers_group:
|
can_remove_subscribers_group:
|
||||||
type: integer
|
$ref: "#/components/schemas/CanRemoveSubscribersGroup"
|
||||||
description: |
|
|
||||||
ID of the user group whose members are allowed to unsubscribe others
|
|
||||||
from the channel.
|
|
||||||
|
|
||||||
**Changes**: Before Zulip 8.0 (feature level 197),
|
|
||||||
the `can_remove_subscribers_group` setting
|
|
||||||
was named `can_remove_subscribers_group_id`.
|
|
||||||
|
|
||||||
New in Zulip 6.0 (feature level 142).
|
|
||||||
BasicBot:
|
BasicBot:
|
||||||
allOf:
|
allOf:
|
||||||
- $ref: "#/components/schemas/BasicBotBase"
|
- $ref: "#/components/schemas/BasicBotBase"
|
||||||
|
@ -22835,16 +22888,7 @@ components:
|
||||||
If `null`, the channel was recently created and there is
|
If `null`, the channel was recently created and there is
|
||||||
insufficient data to estimate the average traffic.
|
insufficient data to estimate the average traffic.
|
||||||
can_remove_subscribers_group:
|
can_remove_subscribers_group:
|
||||||
type: integer
|
$ref: "#/components/schemas/CanRemoveSubscribersGroup"
|
||||||
description: |
|
|
||||||
ID of the user group whose members are allowed to unsubscribe others
|
|
||||||
from the channel.
|
|
||||||
|
|
||||||
**Changes**: Before Zulip 8.0 (feature level 197),
|
|
||||||
the `can_remove_subscribers_group` setting
|
|
||||||
was named `can_remove_subscribers_group_id`.
|
|
||||||
|
|
||||||
New in Zulip 6.0 (feature level 142).
|
|
||||||
is_archived:
|
is_archived:
|
||||||
type: boolean
|
type: boolean
|
||||||
description: |
|
description: |
|
||||||
|
@ -24515,25 +24559,26 @@ components:
|
||||||
- type: string
|
- type: string
|
||||||
- type: integer
|
- type: integer
|
||||||
example: "20"
|
example: "20"
|
||||||
CanRemoveSubscribersGroupId:
|
CanRemoveSubscribersGroup:
|
||||||
description: |
|
allOf:
|
||||||
ID of the [user group](/api/get-user-groups) whose members are
|
- $ref: "#/components/schemas/GroupSettingValue"
|
||||||
allowed to unsubscribe others from the channel. Note that a user
|
- description: |
|
||||||
who is a member of the specified user group must also [have
|
A [group-setting value][setting-values] defining the set of users
|
||||||
access](/help/channel-permissions) to the channel in order to
|
who have permission to remove subscribers from this channel.
|
||||||
unsubscribe others.
|
|
||||||
|
|
||||||
This setting can currently only be set to user groups that are
|
Note that a user who is a member of the specified user group must
|
||||||
system groups, except for the system groups named
|
also [have access](/help/channel-permissions) to the channel in
|
||||||
`"role:internet"` and `"role:owners"`.
|
order to unsubscribe others.
|
||||||
|
|
||||||
**Changes**: Before Zulip 8.0 (feature level 197),
|
**Changes**: Prior to Zulip 10.0 (feature level 320), this value was
|
||||||
the `can_remove_subscribers_group` setting
|
always the integer ID of a system group.
|
||||||
was named `can_remove_subscribers_group_id`.
|
|
||||||
|
|
||||||
New in Zulip 7.0 (feature level 161).
|
Before Zulip 8.0 (feature level 197), the `can_remove_subscribers_group`
|
||||||
type: integer
|
setting was named `can_remove_subscribers_group_id`.
|
||||||
example: 20
|
|
||||||
|
New in Zulip 6.0 (feature level 142).
|
||||||
|
|
||||||
|
[setting-values]: /api/group-setting-values
|
||||||
LinkifierPattern:
|
LinkifierPattern:
|
||||||
description: |
|
description: |
|
||||||
The [Python regular
|
The [Python regular
|
||||||
|
|
|
@ -1192,7 +1192,7 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
realm = get_realm_with_settings(realm_id=user.realm_id)
|
realm = get_realm_with_settings(realm_id=user.realm_id)
|
||||||
|
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(40),
|
self.assert_database_query_count(42),
|
||||||
mock.patch("zerver.lib.events.always_want") as want_mock,
|
mock.patch("zerver.lib.events.always_want") as want_mock,
|
||||||
):
|
):
|
||||||
fetch_initial_state_data(user, realm=realm)
|
fetch_initial_state_data(user, realm=realm)
|
||||||
|
@ -1224,9 +1224,9 @@ class FetchQueriesTest(ZulipTestCase):
|
||||||
saved_snippets=1,
|
saved_snippets=1,
|
||||||
scheduled_messages=1,
|
scheduled_messages=1,
|
||||||
starred_messages=1,
|
starred_messages=1,
|
||||||
stream=3,
|
stream=4,
|
||||||
stop_words=0,
|
stop_words=0,
|
||||||
subscription=4,
|
subscription=5,
|
||||||
update_display_settings=0,
|
update_display_settings=0,
|
||||||
update_global_notifications=0,
|
update_global_notifications=0,
|
||||||
update_message_flags=5,
|
update_message_flags=5,
|
||||||
|
|
|
@ -4538,6 +4538,26 @@ class SubscribeActionTest(BaseAction):
|
||||||
acting_user=self.example_user("hamlet"),
|
acting_user=self.example_user("hamlet"),
|
||||||
)
|
)
|
||||||
check_stream_update("events[0]", events[0])
|
check_stream_update("events[0]", events[0])
|
||||||
|
self.assertEqual(events[0]["value"], moderators_group.id)
|
||||||
|
|
||||||
|
setting_group = self.create_or_update_anonymous_group_for_setting(
|
||||||
|
[self.user_profile],
|
||||||
|
[moderators_group],
|
||||||
|
)
|
||||||
|
with self.verify_action(include_subscribers=include_subscribers, num_events=1) as events:
|
||||||
|
do_change_stream_group_based_setting(
|
||||||
|
stream,
|
||||||
|
"can_remove_subscribers_group",
|
||||||
|
setting_group,
|
||||||
|
acting_user=self.example_user("hamlet"),
|
||||||
|
)
|
||||||
|
check_stream_update("events[0]", events[0])
|
||||||
|
self.assertEqual(
|
||||||
|
events[0]["value"],
|
||||||
|
AnonymousSettingGroupDict(
|
||||||
|
direct_members=[self.user_profile.id], direct_subgroups=[moderators_group.id]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
# Subscribe to a totally new invite-only stream, so it's just Hamlet on it
|
# Subscribe to a totally new invite-only stream, so it's just Hamlet on it
|
||||||
stream = self.make_stream("private", self.user_profile.realm, invite_only=True)
|
stream = self.make_stream("private", self.user_profile.realm, invite_only=True)
|
||||||
|
|
|
@ -271,7 +271,7 @@ class HomeTest(ZulipTestCase):
|
||||||
|
|
||||||
# Verify succeeds once logged-in
|
# Verify succeeds once logged-in
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(51),
|
self.assert_database_query_count(52),
|
||||||
patch("zerver.lib.cache.cache_set") as cache_mock,
|
patch("zerver.lib.cache.cache_set") as cache_mock,
|
||||||
):
|
):
|
||||||
result = self._get_home_page(stream="Denmark")
|
result = self._get_home_page(stream="Denmark")
|
||||||
|
@ -576,7 +576,7 @@ class HomeTest(ZulipTestCase):
|
||||||
# Verify number of queries for Realm admin isn't much higher than for normal users.
|
# Verify number of queries for Realm admin isn't much higher than for normal users.
|
||||||
self.login("iago")
|
self.login("iago")
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(51),
|
self.assert_database_query_count(52),
|
||||||
patch("zerver.lib.cache.cache_set") as cache_mock,
|
patch("zerver.lib.cache.cache_set") as cache_mock,
|
||||||
):
|
):
|
||||||
result = self._get_home_page()
|
result = self._get_home_page()
|
||||||
|
@ -608,7 +608,7 @@ class HomeTest(ZulipTestCase):
|
||||||
self._get_home_page()
|
self._get_home_page()
|
||||||
|
|
||||||
# Then for the second page load, measure the number of queries.
|
# Then for the second page load, measure the number of queries.
|
||||||
with self.assert_database_query_count(46):
|
with self.assert_database_query_count(47):
|
||||||
result = self._get_home_page()
|
result = self._get_home_page()
|
||||||
|
|
||||||
# Do a sanity check that our new streams were in the payload.
|
# Do a sanity check that our new streams were in the payload.
|
||||||
|
|
|
@ -570,10 +570,31 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
allow_fail=True,
|
allow_fail=True,
|
||||||
subdomain="zulip",
|
subdomain="zulip",
|
||||||
)
|
)
|
||||||
self.assert_json_error(
|
self.assert_json_success(result)
|
||||||
result, "'can_remove_subscribers_group' must be a system user group."
|
stream = get_stream("new_stream3", realm)
|
||||||
|
self.assertEqual(stream.can_remove_subscribers_group.id, hamletcharacters_group.id)
|
||||||
|
|
||||||
|
subscriptions = [{"name": "new_stream4", "description": "Fourth new stream"}]
|
||||||
|
result = self.common_subscribe_to_streams(
|
||||||
|
user,
|
||||||
|
subscriptions,
|
||||||
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{"direct_members": [user.id], "direct_subgroups": [moderators_system_group.id]}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
|
allow_fail=True,
|
||||||
|
subdomain="zulip",
|
||||||
|
)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
stream = get_stream("new_stream4", realm)
|
||||||
|
self.assertEqual(list(stream.can_remove_subscribers_group.direct_members.all()), [user])
|
||||||
|
self.assertEqual(
|
||||||
|
list(stream.can_remove_subscribers_group.direct_subgroups.all()),
|
||||||
|
[moderators_system_group],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subscriptions = [{"name": "new_stream5", "description": "Fifth new stream"}]
|
||||||
internet_group = NamedUserGroup.objects.get(
|
internet_group = NamedUserGroup.objects.get(
|
||||||
name="role:internet", is_system_group=True, realm=realm
|
name="role:internet", is_system_group=True, realm=realm
|
||||||
)
|
)
|
||||||
|
@ -2273,14 +2294,22 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
self.login("shiva")
|
self.login("shiva")
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(moderators_system_group.id).decode()},
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{"new": moderators_system_group.id}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_error(result, "Must be an organization administrator")
|
self.assert_json_error(result, "Must be an organization administrator")
|
||||||
|
|
||||||
self.login("iago")
|
self.login("iago")
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(moderators_system_group.id).decode()},
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{"new": moderators_system_group.id}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
stream = get_stream("stream_name1", realm)
|
stream = get_stream("stream_name1", realm)
|
||||||
|
@ -2290,10 +2319,56 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
hamletcharacters_group = NamedUserGroup.objects.get(name="hamletcharacters", realm=realm)
|
hamletcharacters_group = NamedUserGroup.objects.get(name="hamletcharacters", realm=realm)
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(hamletcharacters_group.id).decode()},
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{"new": hamletcharacters_group.id}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_error(
|
self.assert_json_success(result)
|
||||||
result, "'can_remove_subscribers_group' must be a system user group."
|
stream = get_stream("stream_name1", realm)
|
||||||
|
self.assertEqual(stream.can_remove_subscribers_group.id, hamletcharacters_group.id)
|
||||||
|
|
||||||
|
# Test changing it to anonymous group.
|
||||||
|
hamlet = self.example_user("hamlet")
|
||||||
|
|
||||||
|
# Test passing incorrect old value.
|
||||||
|
result = self.client_patch(
|
||||||
|
f"/json/streams/{stream.id}",
|
||||||
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{
|
||||||
|
"new": {
|
||||||
|
"direct_members": [hamlet.id],
|
||||||
|
"direct_subgroups": [moderators_system_group.id],
|
||||||
|
},
|
||||||
|
"old": moderators_system_group.id,
|
||||||
|
}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_json_error(result, "'old' value does not match the expected value.")
|
||||||
|
|
||||||
|
result = self.client_patch(
|
||||||
|
f"/json/streams/{stream.id}",
|
||||||
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{
|
||||||
|
"new": {
|
||||||
|
"direct_members": [hamlet.id],
|
||||||
|
"direct_subgroups": [moderators_system_group.id],
|
||||||
|
},
|
||||||
|
"old": hamletcharacters_group.id,
|
||||||
|
}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
stream = get_stream("stream_name1", realm)
|
||||||
|
self.assertEqual(list(stream.can_remove_subscribers_group.direct_members.all()), [hamlet])
|
||||||
|
self.assertEqual(
|
||||||
|
list(stream.can_remove_subscribers_group.direct_subgroups.all()),
|
||||||
|
[moderators_system_group],
|
||||||
)
|
)
|
||||||
|
|
||||||
internet_group = NamedUserGroup.objects.get(
|
internet_group = NamedUserGroup.objects.get(
|
||||||
|
@ -2301,7 +2376,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
)
|
)
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(internet_group.id).decode()},
|
{"can_remove_subscribers_group": orjson.dumps({"new": internet_group.id}).decode()},
|
||||||
)
|
)
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result,
|
result,
|
||||||
|
@ -2313,7 +2388,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
)
|
)
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(owners_group.id).decode()},
|
{"can_remove_subscribers_group": orjson.dumps({"new": owners_group.id}).decode()},
|
||||||
)
|
)
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result,
|
result,
|
||||||
|
@ -2325,7 +2400,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
)
|
)
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(nobody_group.id).decode()},
|
{"can_remove_subscribers_group": orjson.dumps({"new": nobody_group.id}).decode()},
|
||||||
)
|
)
|
||||||
self.assert_json_error(
|
self.assert_json_error(
|
||||||
result,
|
result,
|
||||||
|
@ -2337,14 +2412,22 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
stream = self.make_stream("stream_name2", invite_only=True)
|
stream = self.make_stream("stream_name2", invite_only=True)
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(moderators_system_group.id).decode()},
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{"new": moderators_system_group.id}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_error(result, "Invalid channel ID")
|
self.assert_json_error(result, "Invalid channel ID")
|
||||||
|
|
||||||
self.subscribe(user_profile, "stream_name2")
|
self.subscribe(user_profile, "stream_name2")
|
||||||
result = self.client_patch(
|
result = self.client_patch(
|
||||||
f"/json/streams/{stream.id}",
|
f"/json/streams/{stream.id}",
|
||||||
{"can_remove_subscribers_group": orjson.dumps(moderators_system_group.id).decode()},
|
{
|
||||||
|
"can_remove_subscribers_group": orjson.dumps(
|
||||||
|
{"new": moderators_system_group.id}
|
||||||
|
).decode()
|
||||||
|
},
|
||||||
)
|
)
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
stream = get_stream("stream_name2", realm)
|
stream = get_stream("stream_name2", realm)
|
||||||
|
@ -2676,7 +2759,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
are on.
|
are on.
|
||||||
"""
|
"""
|
||||||
result = self.attempt_unsubscribe_of_principal(
|
result = self.attempt_unsubscribe_of_principal(
|
||||||
query_count=17,
|
query_count=18,
|
||||||
target_users=[self.example_user("cordelia")],
|
target_users=[self.example_user("cordelia")],
|
||||||
is_realm_admin=True,
|
is_realm_admin=True,
|
||||||
is_subbed=True,
|
is_subbed=True,
|
||||||
|
@ -2693,7 +2776,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
streams you aren't on.
|
streams you aren't on.
|
||||||
"""
|
"""
|
||||||
result = self.attempt_unsubscribe_of_principal(
|
result = self.attempt_unsubscribe_of_principal(
|
||||||
query_count=17,
|
query_count=18,
|
||||||
target_users=[self.example_user("cordelia")],
|
target_users=[self.example_user("cordelia")],
|
||||||
is_realm_admin=True,
|
is_realm_admin=True,
|
||||||
is_subbed=False,
|
is_subbed=False,
|
||||||
|
@ -2863,6 +2946,17 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
self.subscribe(self.example_user("shiva"), stream.name)
|
self.subscribe(self.example_user("shiva"), stream.name)
|
||||||
check_unsubscribing_user(self.example_user("shiva"), leadership_group)
|
check_unsubscribing_user(self.example_user("shiva"), leadership_group)
|
||||||
|
|
||||||
|
# Test changing setting to anonymous group.
|
||||||
|
setting_group = self.create_or_update_anonymous_group_for_setting(
|
||||||
|
[hamlet],
|
||||||
|
[leadership_group],
|
||||||
|
)
|
||||||
|
check_unsubscribing_user(self.example_user("othello"), setting_group, expect_fail=True)
|
||||||
|
check_unsubscribing_user(self.example_user("desdemona"), setting_group, expect_fail=True)
|
||||||
|
check_unsubscribing_user(self.example_user("hamlet"), setting_group)
|
||||||
|
check_unsubscribing_user(self.example_user("iago"), setting_group)
|
||||||
|
check_unsubscribing_user(self.example_user("shiva"), setting_group)
|
||||||
|
|
||||||
def test_remove_invalid_user(self) -> None:
|
def test_remove_invalid_user(self) -> None:
|
||||||
"""
|
"""
|
||||||
Trying to unsubscribe an invalid user from a stream fails gracefully.
|
Trying to unsubscribe an invalid user from a stream fails gracefully.
|
||||||
|
@ -4707,7 +4801,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
streams_to_sub = ["multi_user_stream"]
|
streams_to_sub = ["multi_user_stream"]
|
||||||
with (
|
with (
|
||||||
self.capture_send_event_calls(expected_num_events=5) as events,
|
self.capture_send_event_calls(expected_num_events=5) as events,
|
||||||
self.assert_database_query_count(37),
|
self.assert_database_query_count(40),
|
||||||
):
|
):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
|
@ -4733,7 +4827,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
# Now add ourselves
|
# Now add ourselves
|
||||||
with (
|
with (
|
||||||
self.capture_send_event_calls(expected_num_events=2) as events,
|
self.capture_send_event_calls(expected_num_events=2) as events,
|
||||||
self.assert_database_query_count(14),
|
self.assert_database_query_count(16),
|
||||||
):
|
):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
|
@ -5027,7 +5121,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
# Sends 3 peer-remove events, 2 unsubscribe events
|
# Sends 3 peer-remove events, 2 unsubscribe events
|
||||||
# and 2 stream delete events for private streams.
|
# and 2 stream delete events for private streams.
|
||||||
with (
|
with (
|
||||||
self.assert_database_query_count(16),
|
self.assert_database_query_count(17),
|
||||||
self.assert_memcached_count(3),
|
self.assert_memcached_count(3),
|
||||||
self.capture_send_event_calls(expected_num_events=7) as events,
|
self.capture_send_event_calls(expected_num_events=7) as events,
|
||||||
):
|
):
|
||||||
|
@ -5098,7 +5192,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
# Verify that peer_event events are never sent in Zephyr
|
# Verify that peer_event events are never sent in Zephyr
|
||||||
# realm. This does generate stream creation events from
|
# realm. This does generate stream creation events from
|
||||||
# send_stream_creation_events_for_previously_inaccessible_streams.
|
# send_stream_creation_events_for_previously_inaccessible_streams.
|
||||||
with self.assert_database_query_count(num_streams + 11):
|
with self.assert_database_query_count(num_streams + 13):
|
||||||
with self.capture_send_event_calls(expected_num_events=num_streams + 1) as events:
|
with self.capture_send_event_calls(expected_num_events=num_streams + 1) as events:
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
mit_user,
|
mit_user,
|
||||||
|
@ -5179,7 +5273,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 (
|
with (
|
||||||
self.assert_database_query_count(16),
|
self.assert_database_query_count(18),
|
||||||
self.assert_memcached_count(3),
|
self.assert_memcached_count(3),
|
||||||
mock.patch("zerver.views.streams.send_messages_for_new_subscribers"),
|
mock.patch("zerver.views.streams.send_messages_for_new_subscribers"),
|
||||||
):
|
):
|
||||||
|
@ -5547,7 +5641,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
]
|
]
|
||||||
|
|
||||||
# Test creating a public stream when realm does not have a notification stream.
|
# Test creating a public stream when realm does not have a notification stream.
|
||||||
with self.assert_database_query_count(37):
|
with self.assert_database_query_count(40):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
[new_streams[0]],
|
[new_streams[0]],
|
||||||
|
@ -5555,7 +5649,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
)
|
)
|
||||||
|
|
||||||
# Test creating private stream.
|
# Test creating private stream.
|
||||||
with self.assert_database_query_count(39):
|
with self.assert_database_query_count(42):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
[new_streams[1]],
|
[new_streams[1]],
|
||||||
|
@ -5567,7 +5661,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
new_stream_announcements_stream = get_stream(self.streams[0], self.test_realm)
|
new_stream_announcements_stream = get_stream(self.streams[0], self.test_realm)
|
||||||
self.test_realm.new_stream_announcements_stream_id = new_stream_announcements_stream.id
|
self.test_realm.new_stream_announcements_stream_id = new_stream_announcements_stream.id
|
||||||
self.test_realm.save()
|
self.test_realm.save()
|
||||||
with self.assert_database_query_count(48):
|
with self.assert_database_query_count(51):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.test_user,
|
self.test_user,
|
||||||
[new_streams[2]],
|
[new_streams[2]],
|
||||||
|
@ -6047,7 +6141,7 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
polonius.id,
|
polonius.id,
|
||||||
]
|
]
|
||||||
|
|
||||||
with self.assert_database_query_count(43):
|
with self.assert_database_query_count(45):
|
||||||
self.common_subscribe_to_streams(
|
self.common_subscribe_to_streams(
|
||||||
self.user_profile,
|
self.user_profile,
|
||||||
streams,
|
streams,
|
||||||
|
@ -6095,7 +6189,7 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
for user in [cordelia, othello, polonius]:
|
for user in [cordelia, othello, polonius]:
|
||||||
self.assert_user_got_subscription_notification(user, msg)
|
self.assert_user_got_subscription_notification(user, msg)
|
||||||
|
|
||||||
with self.assert_database_query_count(4):
|
with self.assert_database_query_count(5):
|
||||||
subscribed_streams, _ = gather_subscriptions(
|
subscribed_streams, _ = gather_subscriptions(
|
||||||
self.user_profile, include_subscribers=True
|
self.user_profile, include_subscribers=True
|
||||||
)
|
)
|
||||||
|
@ -6170,7 +6264,7 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
create_private_streams()
|
create_private_streams()
|
||||||
|
|
||||||
def get_never_subscribed() -> list[NeverSubscribedStreamDict]:
|
def get_never_subscribed() -> list[NeverSubscribedStreamDict]:
|
||||||
with self.assert_database_query_count(4):
|
with self.assert_database_query_count(5):
|
||||||
sub_data = gather_subscriptions_helper(self.user_profile)
|
sub_data = gather_subscriptions_helper(self.user_profile)
|
||||||
self.verify_sub_fields(sub_data)
|
self.verify_sub_fields(sub_data)
|
||||||
never_subscribed = sub_data.never_subscribed
|
never_subscribed = sub_data.never_subscribed
|
||||||
|
@ -6367,7 +6461,7 @@ class GetSubscribersTest(ZulipTestCase):
|
||||||
subdomain="zephyr",
|
subdomain="zephyr",
|
||||||
)
|
)
|
||||||
|
|
||||||
with self.assert_database_query_count(3):
|
with self.assert_database_query_count(4):
|
||||||
subscribed_streams, _ = gather_subscriptions(mit_user_profile, include_subscribers=True)
|
subscribed_streams, _ = gather_subscriptions(mit_user_profile, include_subscribers=True)
|
||||||
|
|
||||||
self.assertGreaterEqual(len(subscribed_streams), 2)
|
self.assertGreaterEqual(len(subscribed_streams), 2)
|
||||||
|
|
|
@ -74,7 +74,14 @@ from zerver.lib.topic import (
|
||||||
)
|
)
|
||||||
from zerver.lib.typed_endpoint import ApiParamConfig, PathOnly, typed_endpoint
|
from zerver.lib.typed_endpoint import ApiParamConfig, PathOnly, typed_endpoint
|
||||||
from zerver.lib.typed_endpoint_validators import check_color, check_int_in_validator
|
from zerver.lib.typed_endpoint_validators import check_color, check_int_in_validator
|
||||||
from zerver.lib.user_groups import access_user_group_for_setting
|
from zerver.lib.types import AnonymousSettingGroupDict
|
||||||
|
from zerver.lib.user_groups import (
|
||||||
|
GroupSettingChangeRequest,
|
||||||
|
access_user_group_for_setting,
|
||||||
|
get_group_setting_value_for_api,
|
||||||
|
parse_group_setting_value,
|
||||||
|
validate_group_setting_value_change,
|
||||||
|
)
|
||||||
from zerver.lib.users import bulk_access_users_by_email, bulk_access_users_by_id
|
from zerver.lib.users import bulk_access_users_by_email, bulk_access_users_by_id
|
||||||
from zerver.lib.utils import assert_is_not_none
|
from zerver.lib.utils import assert_is_not_none
|
||||||
from zerver.models import NamedUserGroup, Realm, Stream, UserProfile
|
from zerver.models import NamedUserGroup, Realm, Stream, UserProfile
|
||||||
|
@ -249,9 +256,7 @@ def update_stream_backend(
|
||||||
is_web_public: Json[bool] | None = None,
|
is_web_public: Json[bool] | None = None,
|
||||||
new_name: str | None = None,
|
new_name: str | None = None,
|
||||||
message_retention_days: Json[str] | Json[int] | None = None,
|
message_retention_days: Json[str] | Json[int] | None = None,
|
||||||
can_remove_subscribers_group_id: Annotated[
|
can_remove_subscribers_group: Json[GroupSettingChangeRequest] | None = None,
|
||||||
Json[int | None], ApiParamConfig("can_remove_subscribers_group")
|
|
||||||
] = None,
|
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
# We allow realm administrators to update the stream name and
|
# We allow realm administrators to update the stream name and
|
||||||
# description even for private streams.
|
# description even for private streams.
|
||||||
|
@ -379,28 +384,42 @@ def update_stream_backend(
|
||||||
|
|
||||||
for setting_name, permission_configuration in Stream.stream_permission_group_settings.items():
|
for setting_name, permission_configuration in Stream.stream_permission_group_settings.items():
|
||||||
request_settings_dict = locals()
|
request_settings_dict = locals()
|
||||||
setting_group_id_name = permission_configuration.id_field_name
|
assert setting_name in request_settings_dict
|
||||||
|
if request_settings_dict[setting_name] is None:
|
||||||
if setting_group_id_name not in request_settings_dict: # nocoverage
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if request_settings_dict[setting_group_id_name] is not None and request_settings_dict[
|
setting_value = request_settings_dict[setting_name]
|
||||||
setting_group_id_name
|
new_setting_value = parse_group_setting_value(setting_value.new, setting_name)
|
||||||
] != getattr(stream, setting_group_id_name):
|
|
||||||
|
expected_current_setting_value = None
|
||||||
|
if setting_value.old is not None:
|
||||||
|
expected_current_setting_value = parse_group_setting_value(
|
||||||
|
setting_value.old, setting_name
|
||||||
|
)
|
||||||
|
|
||||||
|
current_value = getattr(stream, setting_name)
|
||||||
|
current_setting_api_value = get_group_setting_value_for_api(current_value)
|
||||||
|
|
||||||
|
if validate_group_setting_value_change(
|
||||||
|
current_setting_api_value, new_setting_value, expected_current_setting_value
|
||||||
|
):
|
||||||
if sub is None and stream.invite_only:
|
if sub is None and stream.invite_only:
|
||||||
# Admins cannot change this setting for unsubscribed private streams.
|
# Admins cannot change this setting for unsubscribed private streams.
|
||||||
raise JsonableError(_("Invalid channel ID"))
|
raise JsonableError(_("Invalid channel ID"))
|
||||||
|
|
||||||
user_group_id = request_settings_dict[setting_group_id_name]
|
|
||||||
with transaction.atomic(durable=True):
|
with transaction.atomic(durable=True):
|
||||||
user_group = access_user_group_for_setting(
|
user_group = access_user_group_for_setting(
|
||||||
user_group_id,
|
new_setting_value,
|
||||||
user_profile,
|
user_profile,
|
||||||
setting_name=setting_name,
|
setting_name=setting_name,
|
||||||
permission_configuration=permission_configuration,
|
permission_configuration=permission_configuration,
|
||||||
|
current_setting_value=current_value,
|
||||||
)
|
)
|
||||||
do_change_stream_group_based_setting(
|
do_change_stream_group_based_setting(
|
||||||
stream, setting_name, user_group, acting_user=user_profile
|
stream,
|
||||||
|
setting_name,
|
||||||
|
user_group,
|
||||||
|
old_setting_api_value=current_setting_api_value,
|
||||||
|
acting_user=user_profile,
|
||||||
)
|
)
|
||||||
|
|
||||||
return json_success(request)
|
return json_success(request)
|
||||||
|
@ -558,9 +577,7 @@ def add_subscriptions_backend(
|
||||||
] = Stream.STREAM_POST_POLICY_EVERYONE,
|
] = Stream.STREAM_POST_POLICY_EVERYONE,
|
||||||
history_public_to_subscribers: Json[bool] | None = None,
|
history_public_to_subscribers: Json[bool] | None = None,
|
||||||
message_retention_days: Json[str] | Json[int] = RETENTION_DEFAULT,
|
message_retention_days: Json[str] | Json[int] = RETENTION_DEFAULT,
|
||||||
can_remove_subscribers_group_id: Annotated[
|
can_remove_subscribers_group: Json[int | AnonymousSettingGroupDict] | None = None,
|
||||||
Json[int | None], ApiParamConfig("can_remove_subscribers_group")
|
|
||||||
] = None,
|
|
||||||
announce: Json[bool] = False,
|
announce: Json[bool] = False,
|
||||||
principals: Json[list[str] | list[int]] | None = None,
|
principals: Json[list[str] | list[int]] | None = None,
|
||||||
authorization_errors_fatal: Json[bool] = True,
|
authorization_errors_fatal: Json[bool] = True,
|
||||||
|
@ -572,12 +589,15 @@ def add_subscriptions_backend(
|
||||||
if principals is None:
|
if principals is None:
|
||||||
principals = []
|
principals = []
|
||||||
|
|
||||||
if can_remove_subscribers_group_id is not None:
|
if can_remove_subscribers_group is not None:
|
||||||
|
setting_value = parse_group_setting_value(
|
||||||
|
can_remove_subscribers_group, "can_remove_subscribers_group"
|
||||||
|
)
|
||||||
permission_configuration = Stream.stream_permission_group_settings[
|
permission_configuration = Stream.stream_permission_group_settings[
|
||||||
"can_remove_subscribers_group"
|
"can_remove_subscribers_group"
|
||||||
]
|
]
|
||||||
can_remove_subscribers_group = access_user_group_for_setting(
|
can_remove_subscribers_group_value = access_user_group_for_setting(
|
||||||
can_remove_subscribers_group_id,
|
setting_value,
|
||||||
user_profile,
|
user_profile,
|
||||||
setting_name="can_remove_subscribers_group",
|
setting_name="can_remove_subscribers_group",
|
||||||
permission_configuration=permission_configuration,
|
permission_configuration=permission_configuration,
|
||||||
|
@ -586,7 +606,7 @@ def add_subscriptions_backend(
|
||||||
can_remove_subscribers_group_default_name = Stream.stream_permission_group_settings[
|
can_remove_subscribers_group_default_name = Stream.stream_permission_group_settings[
|
||||||
"can_remove_subscribers_group"
|
"can_remove_subscribers_group"
|
||||||
].default_group_name
|
].default_group_name
|
||||||
can_remove_subscribers_group = NamedUserGroup.objects.get(
|
can_remove_subscribers_group_value = NamedUserGroup.objects.get(
|
||||||
name=can_remove_subscribers_group_default_name,
|
name=can_remove_subscribers_group_default_name,
|
||||||
realm=user_profile.realm,
|
realm=user_profile.realm,
|
||||||
is_system_group=True,
|
is_system_group=True,
|
||||||
|
@ -612,7 +632,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_dict_copy["can_remove_subscribers_group"] = can_remove_subscribers_group_value
|
||||||
|
|
||||||
stream_dicts.append(stream_dict_copy)
|
stream_dicts.append(stream_dict_copy)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue