mirror of
https://github.com/zulip/zulip.git
synced 2025-10-23 16:14:02 +00:00
streams: Add folder foreign key to Stream table.
This commit also adds "folder_id" field in stream and subscription objects. Fixes part of #31972.
This commit is contained in:
4
api_docs/unmerged.d/ZF-2f0879.md
Normal file
4
api_docs/unmerged.d/ZF-2f0879.md
Normal file
@@ -0,0 +1,4 @@
|
||||
* [`GET /users/me/subscriptions`](/api/get-subscriptions),
|
||||
[`GET /streams`](/api/get-streams), [`GET /events`](/api/get-events),
|
||||
[`POST /register`](/api/register-queue): Added `folder_id` field
|
||||
to Stream and Subscription objects.
|
@@ -462,6 +462,7 @@ def send_subscription_add_events(
|
||||
date_created=stream_dict["date_created"],
|
||||
description=stream_dict["description"],
|
||||
first_message_id=stream_dict["first_message_id"],
|
||||
folder_id=stream_dict["folder_id"],
|
||||
is_recently_active=stream_dict["is_recently_active"],
|
||||
history_public_to_subscribers=stream_dict["history_public_to_subscribers"],
|
||||
invite_only=stream_dict["invite_only"],
|
||||
|
@@ -1313,12 +1313,33 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
|
||||
if "zerver_usergroup" not in data:
|
||||
system_groups_name_dict = create_system_user_groups_for_realm(realm)
|
||||
|
||||
channel_folder_id_to_creator_id = {}
|
||||
if "zerver_channelfolder" in data:
|
||||
fix_datetime_fields(data, "zerver_channelfolder")
|
||||
re_map_foreign_keys(data, "zerver_channelfolder", "realm", related_table="realm")
|
||||
re_map_foreign_keys(
|
||||
data, "zerver_channelfolder", "creator", related_table="user_profile"
|
||||
)
|
||||
|
||||
# To correctly set .folder attribute for streams, we
|
||||
# would need to create ChannelFolder objects before
|
||||
# creating Stream objects. So we retain the .creator
|
||||
# attribute data in a mapping, so that we can update it
|
||||
# once the UserProfile objects are created.
|
||||
for channel_folder in data["zerver_channelfolder"]:
|
||||
creator_id = channel_folder.pop("creator_id", None)
|
||||
channel_folder_id_to_creator_id[channel_folder["id"]] = creator_id
|
||||
|
||||
bulk_import_model(data, ChannelFolder)
|
||||
|
||||
# Email tokens will automatically be randomly generated when the
|
||||
# Stream objects are created by Django.
|
||||
fix_datetime_fields(data, "zerver_stream")
|
||||
re_map_foreign_keys(data, "zerver_stream", "realm", related_table="realm")
|
||||
|
||||
re_map_foreign_keys(data, "zerver_stream", "creator", related_table="user_profile")
|
||||
re_map_foreign_keys(data, "zerver_stream", "folder", related_table="channelfolder")
|
||||
|
||||
# There's a circular dependency between Stream and UserProfile due to
|
||||
# the .creator attribute. We untangle it by first remembering the creator_id
|
||||
# for all the streams and then removing those fields from the data.
|
||||
@@ -1390,11 +1411,10 @@ def do_import_realm(import_dir: Path, subdomain: str, processes: int = 1) -> Rea
|
||||
stream.creator_id = stream_id_to_creator_id[stream.id]
|
||||
Stream.objects.bulk_update(streams, ["creator_id"])
|
||||
|
||||
if "zerver_channelfolder" in data:
|
||||
fix_datetime_fields(data, "zerver_channelfolder")
|
||||
re_map_foreign_keys(data, "zerver_channelfolder", "realm", related_table="realm")
|
||||
re_map_foreign_keys(data, "zerver_channelfolder", "creator", related_table="user_profile")
|
||||
bulk_import_model(data, ChannelFolder)
|
||||
channel_folders = ChannelFolder.objects.filter(id__in=channel_folder_id_to_creator_id.keys())
|
||||
for channel_folder in channel_folders:
|
||||
channel_folder.creator_id = channel_folder_id_to_creator_id[channel_folder.id]
|
||||
ChannelFolder.objects.bulk_update(channel_folders, ["creator_id"])
|
||||
|
||||
if "zerver_namedusergroup" in data:
|
||||
# UserProfiles have been loaded, so now we're ready to set .creator_id
|
||||
|
@@ -1507,6 +1507,7 @@ def stream_to_dict(
|
||||
date_created=datetime_to_timestamp(stream.date_created),
|
||||
description=stream.description,
|
||||
first_message_id=stream.first_message_id,
|
||||
folder_id=stream.folder_id,
|
||||
is_recently_active=stream.is_recently_active,
|
||||
history_public_to_subscribers=stream.history_public_to_subscribers,
|
||||
invite_only=stream.invite_only,
|
||||
|
@@ -81,6 +81,7 @@ def get_web_public_subs(
|
||||
date_created = datetime_to_timestamp(stream.date_created)
|
||||
description = stream.description
|
||||
first_message_id = stream.first_message_id
|
||||
folder_id = stream.folder_id
|
||||
is_recently_active = stream.is_recently_active
|
||||
history_public_to_subscribers = stream.history_public_to_subscribers
|
||||
invite_only = stream.invite_only
|
||||
@@ -124,6 +125,7 @@ def get_web_public_subs(
|
||||
desktop_notifications=desktop_notifications,
|
||||
email_notifications=email_notifications,
|
||||
first_message_id=first_message_id,
|
||||
folder_id=folder_id,
|
||||
is_recently_active=is_recently_active,
|
||||
history_public_to_subscribers=history_public_to_subscribers,
|
||||
in_home_view=in_home_view,
|
||||
@@ -203,6 +205,7 @@ def build_stream_api_dict(
|
||||
date_created=datetime_to_timestamp(raw_stream_dict["date_created"]),
|
||||
description=raw_stream_dict["description"],
|
||||
first_message_id=raw_stream_dict["first_message_id"],
|
||||
folder_id=raw_stream_dict["folder_id"],
|
||||
history_public_to_subscribers=raw_stream_dict["history_public_to_subscribers"],
|
||||
invite_only=raw_stream_dict["invite_only"],
|
||||
is_web_public=raw_stream_dict["is_web_public"],
|
||||
@@ -233,6 +236,7 @@ def build_stream_dict_for_sub(
|
||||
date_created = stream_dict["date_created"]
|
||||
description = stream_dict["description"]
|
||||
first_message_id = stream_dict["first_message_id"]
|
||||
folder_id = stream_dict["folder_id"]
|
||||
history_public_to_subscribers = stream_dict["history_public_to_subscribers"]
|
||||
invite_only = stream_dict["invite_only"]
|
||||
is_web_public = stream_dict["is_web_public"]
|
||||
@@ -275,6 +279,7 @@ def build_stream_dict_for_sub(
|
||||
desktop_notifications=desktop_notifications,
|
||||
email_notifications=email_notifications,
|
||||
first_message_id=first_message_id,
|
||||
folder_id=folder_id,
|
||||
is_recently_active=is_recently_active,
|
||||
history_public_to_subscribers=history_public_to_subscribers,
|
||||
in_home_view=in_home_view,
|
||||
@@ -304,6 +309,7 @@ def build_stream_dict_for_never_sub(
|
||||
date_created = datetime_to_timestamp(raw_stream_dict["date_created"])
|
||||
description = raw_stream_dict["description"]
|
||||
first_message_id = raw_stream_dict["first_message_id"]
|
||||
folder_id = raw_stream_dict["folder_id"]
|
||||
is_recently_active = raw_stream_dict["is_recently_active"]
|
||||
history_public_to_subscribers = raw_stream_dict["history_public_to_subscribers"]
|
||||
invite_only = raw_stream_dict["invite_only"]
|
||||
@@ -352,6 +358,7 @@ def build_stream_dict_for_never_sub(
|
||||
date_created=date_created,
|
||||
description=description,
|
||||
first_message_id=first_message_id,
|
||||
folder_id=folder_id,
|
||||
is_recently_active=is_recently_active,
|
||||
history_public_to_subscribers=history_public_to_subscribers,
|
||||
invite_only=invite_only,
|
||||
|
@@ -168,6 +168,7 @@ class RawStreamDict(TypedDict):
|
||||
deactivated: bool
|
||||
description: str
|
||||
first_message_id: int | None
|
||||
folder_id: int | None
|
||||
is_recently_active: bool
|
||||
history_public_to_subscribers: bool
|
||||
id: int
|
||||
@@ -216,6 +217,7 @@ class SubscriptionStreamDict(TypedDict):
|
||||
desktop_notifications: bool | None
|
||||
email_notifications: bool | None
|
||||
first_message_id: int | None
|
||||
folder_id: int | None
|
||||
is_recently_active: bool
|
||||
history_public_to_subscribers: bool
|
||||
in_home_view: bool
|
||||
@@ -248,6 +250,7 @@ class NeverSubscribedStreamDict(TypedDict):
|
||||
date_created: int
|
||||
description: str
|
||||
first_message_id: int | None
|
||||
folder_id: int | None
|
||||
is_recently_active: bool
|
||||
history_public_to_subscribers: bool
|
||||
invite_only: bool
|
||||
@@ -279,6 +282,7 @@ class DefaultStreamDict(TypedDict):
|
||||
date_created: int
|
||||
description: str
|
||||
first_message_id: int | None
|
||||
folder_id: int | None
|
||||
is_recently_active: bool
|
||||
history_public_to_subscribers: bool
|
||||
invite_only: bool
|
||||
|
20
zerver/migrations/0708_stream_folder.py
Normal file
20
zerver/migrations/0708_stream_folder.py
Normal file
@@ -0,0 +1,20 @@
|
||||
# Generated by Django 5.1.8 on 2025-05-16 07:41
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("zerver", "0707_realmauditlog_modified_channel_folder"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="stream",
|
||||
name="folder",
|
||||
field=models.ForeignKey(
|
||||
null=True, on_delete=django.db.models.deletion.SET_NULL, to="zerver.channelfolder"
|
||||
),
|
||||
),
|
||||
]
|
@@ -11,6 +11,7 @@ from typing_extensions import override
|
||||
|
||||
from zerver.lib.cache import flush_stream
|
||||
from zerver.lib.types import GroupPermissionSetting
|
||||
from zerver.models.channel_folders import ChannelFolder
|
||||
from zerver.models.groups import SystemGroups, UserGroup
|
||||
from zerver.models.realms import Realm
|
||||
from zerver.models.recipients import Recipient
|
||||
@@ -41,6 +42,8 @@ class Stream(models.Model):
|
||||
# Foreign key to the Recipient object for STREAM type messages to this stream.
|
||||
recipient = models.ForeignKey(Recipient, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
folder = models.ForeignKey(ChannelFolder, null=True, on_delete=models.SET_NULL)
|
||||
|
||||
# Various permission policy configurations
|
||||
PERMISSION_POLICIES: dict[str, dict[str, Any]] = {
|
||||
"web_public": {
|
||||
@@ -238,6 +241,7 @@ class Stream(models.Model):
|
||||
"deactivated",
|
||||
"description",
|
||||
"first_message_id",
|
||||
"folder_id",
|
||||
"history_public_to_subscribers",
|
||||
"id",
|
||||
"invite_only",
|
||||
|
@@ -692,6 +692,7 @@ paths:
|
||||
"stream_post_policy": 1,
|
||||
"history_public_to_subscribers": true,
|
||||
"first_message_id": null,
|
||||
"folder_id": 1,
|
||||
"is_recently_active": true,
|
||||
"message_retention_days": null,
|
||||
"is_announcement_only": false,
|
||||
@@ -1358,6 +1359,7 @@ paths:
|
||||
"stream_post_policy": 1,
|
||||
"history_public_to_subscribers": false,
|
||||
"first_message_id": null,
|
||||
"folder_id": 1,
|
||||
"is_recently_active": true,
|
||||
"message_retention_days": null,
|
||||
"is_announcement_only": false,
|
||||
@@ -15837,6 +15839,8 @@ paths:
|
||||
history_public_to_subscribers: {}
|
||||
first_message_id:
|
||||
nullable: true
|
||||
folder_id:
|
||||
nullable: true
|
||||
is_recently_active: {}
|
||||
is_announcement_only: {}
|
||||
can_add_subscribers_group: {}
|
||||
@@ -21036,6 +21040,8 @@ paths:
|
||||
history_public_to_subscribers: {}
|
||||
first_message_id:
|
||||
nullable: true
|
||||
folder_id:
|
||||
nullable: true
|
||||
is_recently_active: {}
|
||||
is_announcement_only: {}
|
||||
can_add_subscribers_group: {}
|
||||
@@ -21082,6 +21088,7 @@ paths:
|
||||
- message_retention_days
|
||||
- history_public_to_subscribers
|
||||
- first_message_id
|
||||
- folder_id
|
||||
- is_announcement_only
|
||||
- can_remove_subscribers_group
|
||||
- can_subscribe_group
|
||||
@@ -21101,6 +21108,7 @@ paths:
|
||||
"date_created": 1691057093,
|
||||
"description": "A private channel",
|
||||
"first_message_id": 18,
|
||||
"folder_id": 1,
|
||||
"is_recently_active": true,
|
||||
"history_public_to_subscribers": false,
|
||||
"invite_only": true,
|
||||
@@ -21123,6 +21131,7 @@ paths:
|
||||
"date_created": 1691057093,
|
||||
"description": "A default public channel",
|
||||
"first_message_id": 21,
|
||||
"folder_id": null,
|
||||
"is_recently_active": true,
|
||||
"history_public_to_subscribers": true,
|
||||
"invite_only": false,
|
||||
@@ -21173,6 +21182,7 @@ paths:
|
||||
{
|
||||
"description": "A Scandinavian country",
|
||||
"first_message_id": 1,
|
||||
"folder_id": 1,
|
||||
"is_recently_active": true,
|
||||
"history_public_to_subscribers": true,
|
||||
"date_created": 1691057093,
|
||||
@@ -23477,6 +23487,8 @@ components:
|
||||
history_public_to_subscribers: {}
|
||||
first_message_id:
|
||||
nullable: true
|
||||
folder_id:
|
||||
nullable: true
|
||||
is_recently_active: {}
|
||||
is_announcement_only: {}
|
||||
can_add_subscribers_group: {}
|
||||
@@ -23513,6 +23525,7 @@ components:
|
||||
- message_retention_days
|
||||
- history_public_to_subscribers
|
||||
- first_message_id
|
||||
- folder_id
|
||||
- is_recently_active
|
||||
- is_announcement_only
|
||||
- can_remove_subscribers_group
|
||||
@@ -23646,6 +23659,8 @@ components:
|
||||
Is `null` for channels with no message history.
|
||||
|
||||
**Changes**: New in Zulip 2.1.0.
|
||||
folder_id:
|
||||
$ref: "#/components/schemas/FolderId"
|
||||
is_recently_active:
|
||||
type: boolean
|
||||
description: |
|
||||
@@ -24712,6 +24727,8 @@ components:
|
||||
has older history that can be accessed.
|
||||
|
||||
Is `null` for channels with no message history.
|
||||
folder_id:
|
||||
$ref: "#/components/schemas/FolderId"
|
||||
is_recently_active:
|
||||
type: boolean
|
||||
description: |
|
||||
@@ -26578,6 +26595,15 @@ components:
|
||||
named groups' content could be inserted with `%(name_of_group)s`.
|
||||
type: string
|
||||
example: https://github.com/zulip/zulip/issues/{id}
|
||||
FolderId:
|
||||
type: integer
|
||||
nullable: true
|
||||
description: |
|
||||
The ID of the folder to which the channel belongs.
|
||||
|
||||
Is `null` if channel does not belong to any folder.
|
||||
|
||||
**Changes**: New in Zulip 11.0 (feature level ZF-2f0879).
|
||||
|
||||
###################
|
||||
# Shared responses
|
||||
|
@@ -1619,12 +1619,14 @@ class RealmImportExportTest(ExportFile):
|
||||
flags=OnboardingUserMessage.flags.starred,
|
||||
)
|
||||
|
||||
ChannelFolder.objects.create(
|
||||
channel_folder = ChannelFolder.objects.create(
|
||||
realm=original_realm,
|
||||
name="Frontend",
|
||||
description="Frontend channels",
|
||||
creator=self.example_user("iago"),
|
||||
)
|
||||
stream.folder = channel_folder
|
||||
stream.save()
|
||||
|
||||
# We want to have an extra, malformed RealmEmoji with no .author
|
||||
# to test that upon import that gets fixed.
|
||||
@@ -1759,6 +1761,15 @@ class RealmImportExportTest(ExportFile):
|
||||
Recipient.objects.get(type=Recipient.STREAM, type_id=stream.id).id,
|
||||
)
|
||||
|
||||
# Check folder field for imported streams
|
||||
for stream in Stream.objects.filter(realm=imported_realm):
|
||||
if stream.name == "Verona":
|
||||
# Folder was only set for "Verona" stream in original realm.
|
||||
assert stream.folder is not None
|
||||
self.assertEqual(stream.folder.name, "Frontend")
|
||||
else:
|
||||
self.assertIsNone(stream.folder_id)
|
||||
|
||||
for dm_group in DirectMessageGroup.objects.all():
|
||||
# Direct Message groups don't have a realm column, so we just test all
|
||||
# Direct Message groups for simplicity.
|
||||
|
Reference in New Issue
Block a user