diff --git a/analytics/management/commands/populate_analytics_db.py b/analytics/management/commands/populate_analytics_db.py index 425289655d..2b8d6661e0 100644 --- a/analytics/management/commands/populate_analytics_db.py +++ b/analytics/management/commands/populate_analytics_db.py @@ -20,7 +20,7 @@ from zerver.actions.users import do_change_user_role from zerver.lib.create_user import create_user from zerver.lib.stream_color import STREAM_ASSIGNMENT_COLORS from zerver.lib.timestamp import floor_to_day -from zerver.models import Client, Realm, Recipient, Stream, Subscription, UserProfile +from zerver.models import Client, Realm, Recipient, Stream, Subscription, UserGroup, UserProfile class Command(BaseCommand): @@ -87,7 +87,16 @@ class Command(BaseCommand): force_date_joined=installation_time, ) do_change_user_role(shylock, UserProfile.ROLE_REALM_OWNER, acting_user=None) - stream = Stream.objects.create(name="all", realm=realm, date_created=installation_time) + + administrators_user_group = UserGroup.objects.get( + name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True + ) + stream = Stream.objects.create( + name="all", + realm=realm, + date_created=installation_time, + can_remove_subscribers_group=administrators_user_group, + ) recipient = Recipient.objects.create(type_id=stream.id, type=Recipient.STREAM) stream.recipient = recipient stream.save(update_fields=["recipient"]) diff --git a/analytics/tests/test_counts.py b/analytics/tests/test_counts.py index 73febd1ebb..0a1909435d 100644 --- a/analytics/tests/test_counts.py +++ b/analytics/tests/test_counts.py @@ -66,6 +66,7 @@ from zerver.models import ( Recipient, Stream, UserActivityInterval, + UserGroup, UserProfile, get_client, get_user, @@ -84,6 +85,9 @@ class AnalyticsTestCase(ZulipTestCase): self.default_realm = do_create_realm( string_id="realmtest", name="Realm Test", date_created=self.TIME_ZERO - 2 * self.DAY ) + self.administrators_user_group = UserGroup.objects.get( + name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=self.default_realm, is_system_group=True + ) # used to generate unique names in self.create_* self.name_counter = 100 @@ -125,6 +129,7 @@ class AnalyticsTestCase(ZulipTestCase): "name": f"stream name {self.name_counter}", "realm": self.default_realm, "date_created": self.TIME_LAST_HOUR, + "can_remove_subscribers_group": self.administrators_user_group, } for key, value in defaults.items(): kwargs[key] = kwargs.get(key, value) diff --git a/zerver/lib/bulk_create.py b/zerver/lib/bulk_create.py index 652b92bb14..36a006ae6f 100644 --- a/zerver/lib/bulk_create.py +++ b/zerver/lib/bulk_create.py @@ -175,6 +175,9 @@ def bulk_create_streams(realm: Realm, stream_dict: Dict[str, Dict[str, Any]]) -> existing_streams = { name.lower() for name in Stream.objects.filter(realm=realm).values_list("name", flat=True) } + administrators_user_group = UserGroup.objects.get( + name=UserGroup.ADMINISTRATORS_GROUP_NAME, is_system_group=True, realm=realm + ) streams_to_create: List[Stream] = [] for name, options in stream_dict.items(): if "history_public_to_subscribers" not in options: @@ -195,6 +198,7 @@ def bulk_create_streams(realm: Realm, stream_dict: Dict[str, Dict[str, Any]]) -> history_public_to_subscribers=options["history_public_to_subscribers"], is_web_public=options.get("is_web_public", False), is_in_zephyr_realm=realm.is_zephyr_mirror_realm, + can_remove_subscribers_group=administrators_user_group, ), ) # Sort streams by name before creating them so that we can have a diff --git a/zerver/lib/import_realm.py b/zerver/lib/import_realm.py index fcb885f03f..c97cf777a1 100644 --- a/zerver/lib/import_realm.py +++ b/zerver/lib/import_realm.py @@ -181,6 +181,15 @@ def fix_upload_links(data: TableData, message_table: TableName) -> None: ) +def fix_streams_can_remove_subscribers_group_column(data: TableData, realm: Realm) -> None: + table = get_db_table(Stream) + admins_group = UserGroup.objects.get( + name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True + ) + for stream in data[table]: + stream["can_remove_subscribers_group_id"] = admins_group.id + + def create_subscription_events(data: TableData, realm_id: int) -> None: """ When the export data doesn't contain the table `zerver_realmauditlog`, @@ -976,6 +985,12 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea # Stream objects are created by Django. fix_datetime_fields(data, "zerver_stream") re_map_foreign_keys(data, "zerver_stream", "realm", related_table="realm") + if role_system_groups_dict is not None: + fix_streams_can_remove_subscribers_group_column(data, realm) + else: + re_map_foreign_keys( + data, "zerver_stream", "can_remove_subscribers_group", related_table="usergroup" + ) # Handle rendering of stream descriptions for import from non-Zulip for stream in data["zerver_stream"]: stream["rendered_description"] = render_stream_description(stream["description"]) diff --git a/zerver/lib/streams.py b/zerver/lib/streams.py index 3412af9fdc..5ba78a7657 100644 --- a/zerver/lib/streams.py +++ b/zerver/lib/streams.py @@ -26,6 +26,7 @@ from zerver.models import ( Recipient, Stream, Subscription, + UserGroup, UserProfile, active_non_guest_user_ids, bulk_get_streams, @@ -128,6 +129,9 @@ def create_stream_if_needed( history_public_to_subscribers = get_default_value_for_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 + ) with transaction.atomic(): (stream, created) = Stream.objects.get_or_create( @@ -142,6 +146,7 @@ def create_stream_if_needed( history_public_to_subscribers=history_public_to_subscribers, is_in_zephyr_realm=realm.is_zephyr_mirror_realm, message_retention_days=message_retention_days, + can_remove_subscribers_group=administrators_user_group, ), ) diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index 287e76a958..34c86588e7 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -89,6 +89,7 @@ from zerver.models import ( Recipient, Stream, Subscription, + UserGroup, UserGroupMembership, UserMessage, UserProfile, @@ -1188,6 +1189,9 @@ Output: history_public_to_subscribers = get_default_value_for_history_public_to_subscribers( realm, invite_only, history_public_to_subscribers ) + administrators_user_group = UserGroup.objects.get( + name=UserGroup.ADMINISTRATORS_GROUP_NAME, realm=realm, is_system_group=True + ) try: stream = Stream.objects.create( @@ -1196,6 +1200,7 @@ Output: invite_only=invite_only, is_web_public=is_web_public, history_public_to_subscribers=history_public_to_subscribers, + can_remove_subscribers_group=administrators_user_group, ) except IntegrityError: # nocoverage -- this is for bugs in the tests raise Exception( diff --git a/zerver/tests/test_subs.py b/zerver/tests/test_subs.py index b0fea9445e..3edccc5b92 100644 --- a/zerver/tests/test_subs.py +++ b/zerver/tests/test_subs.py @@ -4145,7 +4145,7 @@ class SubscriptionAPITest(ZulipTestCase): streams_to_sub, dict(principals=orjson.dumps([user1.id, user2.id]).decode()), ) - self.assert_length(queries, 36) + self.assert_length(queries, 37) for ev in [x for x in events if x["event"]["type"] not in ("message", "stream")]: if ev["event"]["op"] == "add": @@ -5022,7 +5022,7 @@ class SubscriptionAPITest(ZulipTestCase): [new_streams[0]], dict(principals=orjson.dumps([user1.id, user2.id]).decode()), ) - self.assert_length(queries, 36) + self.assert_length(queries, 37) # Test creating private stream. with queries_captured() as queries: @@ -5032,7 +5032,7 @@ class SubscriptionAPITest(ZulipTestCase): dict(principals=orjson.dumps([user1.id, user2.id]).decode()), invite_only=True, ) - self.assert_length(queries, 35) + self.assert_length(queries, 36) # Test creating a public stream with announce when realm has a notification stream. notifications_stream = get_stream(self.streams[0], self.test_realm) @@ -5047,7 +5047,7 @@ class SubscriptionAPITest(ZulipTestCase): principals=orjson.dumps([user1.id, user2.id]).decode(), ), ) - self.assert_length(queries, 44) + self.assert_length(queries, 45) class GetStreamsTest(ZulipTestCase):