2024-07-12 02:30:17 +02:00
|
|
|
from typing import Any, Iterable
|
2022-04-14 23:34:23 +02:00
|
|
|
|
|
|
|
from django.db import transaction
|
|
|
|
from django.utils.translation import gettext as _
|
|
|
|
|
2023-07-08 21:20:28 +02:00
|
|
|
from zerver.lib.default_streams import (
|
|
|
|
get_default_stream_ids_for_realm,
|
|
|
|
get_default_streams_for_realm_as_dicts,
|
|
|
|
)
|
2022-04-14 23:34:23 +02:00
|
|
|
from zerver.lib.exceptions import JsonableError
|
2023-12-15 03:57:04 +01:00
|
|
|
from zerver.models import DefaultStream, DefaultStreamGroup, Realm, Stream
|
|
|
|
from zerver.models.streams import get_default_stream_groups
|
2023-12-15 01:16:00 +01:00
|
|
|
from zerver.models.users import active_non_guest_user_ids
|
django_api: Extract send_event_on_commit helper.
django-stubs 4.2.1 gives transaction.on_commit a more accurate type
annotation, but this exposed that mypy can’t handle the lambda default
parameters that we use to recapture loop variables such as
for stream_id in public_stream_ids:
peer_user_ids = …
event = …
transaction.on_commit(
lambda event=event, peer_user_ids=peer_user_ids: send_event(
realm, event, peer_user_ids
)
)
https://github.com/python/mypy/issues/15459
A workaround that mypy accepts is
transaction.on_commit(
(
lambda event, peer_user_ids: lambda: send_event(
realm, event, peer_user_ids
)
)(event, peer_user_ids)
)
But that’s kind of ugly and potentially error-prone, so let’s make a
helper function for this very common pattern.
send_event_on_commit(realm, event, peer_user_ids)
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-06-17 20:53:07 +02:00
|
|
|
from zerver.tornado.django_api import send_event_on_commit
|
2022-04-14 23:34:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
def check_default_stream_group_name(group_name: str) -> None:
|
|
|
|
if group_name.strip() == "":
|
2023-07-17 22:40:33 +02:00
|
|
|
raise JsonableError(
|
2024-04-16 15:52:21 +02:00
|
|
|
_("Invalid default channel group name '{group_name}'").format(group_name=group_name)
|
2023-07-17 22:40:33 +02:00
|
|
|
)
|
2022-04-14 23:34:23 +02:00
|
|
|
if len(group_name) > DefaultStreamGroup.MAX_NAME_LENGTH:
|
|
|
|
raise JsonableError(
|
2024-04-16 15:52:21 +02:00
|
|
|
_("Default channel group name too long (limit: {max_length} characters)").format(
|
2023-07-17 22:40:33 +02:00
|
|
|
max_length=DefaultStreamGroup.MAX_NAME_LENGTH,
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
|
|
|
)
|
|
|
|
for i in group_name:
|
|
|
|
if ord(i) == 0:
|
|
|
|
raise JsonableError(
|
2023-07-17 22:40:33 +02:00
|
|
|
_(
|
2024-04-16 15:52:21 +02:00
|
|
|
"Default channel group name '{group_name}' contains NULL (0x00) characters."
|
2023-07-17 22:40:33 +02:00
|
|
|
).format(
|
|
|
|
group_name=group_name,
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def lookup_default_stream_groups(
|
2024-07-12 02:30:17 +02:00
|
|
|
default_stream_group_names: list[str], realm: Realm
|
|
|
|
) -> list[DefaultStreamGroup]:
|
2022-04-14 23:34:23 +02:00
|
|
|
default_stream_groups = []
|
|
|
|
for group_name in default_stream_group_names:
|
|
|
|
try:
|
|
|
|
default_stream_group = DefaultStreamGroup.objects.get(name=group_name, realm=realm)
|
|
|
|
except DefaultStreamGroup.DoesNotExist:
|
2023-07-17 22:40:33 +02:00
|
|
|
raise JsonableError(
|
2024-04-16 15:52:21 +02:00
|
|
|
_("Invalid default channel group {group_name}").format(group_name=group_name)
|
2023-07-17 22:40:33 +02:00
|
|
|
)
|
2022-04-14 23:34:23 +02:00
|
|
|
default_stream_groups.append(default_stream_group)
|
|
|
|
return default_stream_groups
|
|
|
|
|
|
|
|
|
|
|
|
def notify_default_streams(realm: Realm) -> None:
|
|
|
|
event = dict(
|
|
|
|
type="default_streams",
|
2023-07-08 21:20:28 +02:00
|
|
|
default_streams=get_default_streams_for_realm_as_dicts(realm.id),
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
django_api: Extract send_event_on_commit helper.
django-stubs 4.2.1 gives transaction.on_commit a more accurate type
annotation, but this exposed that mypy can’t handle the lambda default
parameters that we use to recapture loop variables such as
for stream_id in public_stream_ids:
peer_user_ids = …
event = …
transaction.on_commit(
lambda event=event, peer_user_ids=peer_user_ids: send_event(
realm, event, peer_user_ids
)
)
https://github.com/python/mypy/issues/15459
A workaround that mypy accepts is
transaction.on_commit(
(
lambda event, peer_user_ids: lambda: send_event(
realm, event, peer_user_ids
)
)(event, peer_user_ids)
)
But that’s kind of ugly and potentially error-prone, so let’s make a
helper function for this very common pattern.
send_event_on_commit(realm, event, peer_user_ids)
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-06-17 20:53:07 +02:00
|
|
|
send_event_on_commit(realm, event, active_non_guest_user_ids(realm.id))
|
2022-04-14 23:34:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
def notify_default_stream_groups(realm: Realm) -> None:
|
|
|
|
event = dict(
|
|
|
|
type="default_stream_groups",
|
|
|
|
default_stream_groups=default_stream_groups_to_dicts_sorted(
|
|
|
|
get_default_stream_groups(realm)
|
|
|
|
),
|
|
|
|
)
|
django_api: Extract send_event_on_commit helper.
django-stubs 4.2.1 gives transaction.on_commit a more accurate type
annotation, but this exposed that mypy can’t handle the lambda default
parameters that we use to recapture loop variables such as
for stream_id in public_stream_ids:
peer_user_ids = …
event = …
transaction.on_commit(
lambda event=event, peer_user_ids=peer_user_ids: send_event(
realm, event, peer_user_ids
)
)
https://github.com/python/mypy/issues/15459
A workaround that mypy accepts is
transaction.on_commit(
(
lambda event, peer_user_ids: lambda: send_event(
realm, event, peer_user_ids
)
)(event, peer_user_ids)
)
But that’s kind of ugly and potentially error-prone, so let’s make a
helper function for this very common pattern.
send_event_on_commit(realm, event, peer_user_ids)
Signed-off-by: Anders Kaseorg <anders@zulip.com>
2023-06-17 20:53:07 +02:00
|
|
|
send_event_on_commit(realm, event, active_non_guest_user_ids(realm.id))
|
2022-04-14 23:34:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
def do_add_default_stream(stream: Stream) -> None:
|
|
|
|
realm_id = stream.realm_id
|
|
|
|
stream_id = stream.id
|
|
|
|
if not DefaultStream.objects.filter(realm_id=realm_id, stream_id=stream_id).exists():
|
|
|
|
DefaultStream.objects.create(realm_id=realm_id, stream_id=stream_id)
|
|
|
|
notify_default_streams(stream.realm)
|
|
|
|
|
|
|
|
|
|
|
|
@transaction.atomic(savepoint=False)
|
|
|
|
def do_remove_default_stream(stream: Stream) -> None:
|
|
|
|
realm_id = stream.realm_id
|
|
|
|
stream_id = stream.id
|
|
|
|
DefaultStream.objects.filter(realm_id=realm_id, stream_id=stream_id).delete()
|
|
|
|
notify_default_streams(stream.realm)
|
|
|
|
|
|
|
|
|
|
|
|
def do_create_default_stream_group(
|
2024-07-12 02:30:17 +02:00
|
|
|
realm: Realm, group_name: str, description: str, streams: list[Stream]
|
2022-04-14 23:34:23 +02:00
|
|
|
) -> None:
|
2023-07-08 21:20:28 +02:00
|
|
|
default_stream_ids = get_default_stream_ids_for_realm(realm.id)
|
2022-04-14 23:34:23 +02:00
|
|
|
for stream in streams:
|
2023-07-08 21:20:28 +02:00
|
|
|
if stream.id in default_stream_ids:
|
2022-04-14 23:34:23 +02:00
|
|
|
raise JsonableError(
|
|
|
|
_(
|
2024-04-16 15:52:21 +02:00
|
|
|
"'{channel_name}' is a default channel and cannot be added to '{group_name}'",
|
2024-04-15 21:24:26 +02:00
|
|
|
).format(channel_name=stream.name, group_name=group_name)
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
check_default_stream_group_name(group_name)
|
|
|
|
(group, created) = DefaultStreamGroup.objects.get_or_create(
|
|
|
|
name=group_name, realm=realm, description=description
|
|
|
|
)
|
|
|
|
if not created:
|
|
|
|
raise JsonableError(
|
|
|
|
_(
|
2024-04-16 15:52:21 +02:00
|
|
|
"Default channel group '{group_name}' already exists",
|
2022-04-14 23:34:23 +02:00
|
|
|
).format(group_name=group_name)
|
|
|
|
)
|
|
|
|
|
|
|
|
group.streams.set(streams)
|
|
|
|
notify_default_stream_groups(realm)
|
|
|
|
|
|
|
|
|
|
|
|
def do_add_streams_to_default_stream_group(
|
2024-07-12 02:30:17 +02:00
|
|
|
realm: Realm, group: DefaultStreamGroup, streams: list[Stream]
|
2022-04-14 23:34:23 +02:00
|
|
|
) -> None:
|
2023-07-08 21:20:28 +02:00
|
|
|
default_stream_ids = get_default_stream_ids_for_realm(realm.id)
|
2022-04-14 23:34:23 +02:00
|
|
|
for stream in streams:
|
2023-07-08 21:20:28 +02:00
|
|
|
if stream.id in default_stream_ids:
|
2022-04-14 23:34:23 +02:00
|
|
|
raise JsonableError(
|
|
|
|
_(
|
2024-04-16 15:52:21 +02:00
|
|
|
"'{channel_name}' is a default channel and cannot be added to '{group_name}'",
|
2024-04-15 21:24:26 +02:00
|
|
|
).format(channel_name=stream.name, group_name=group.name)
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
|
|
|
if stream in group.streams.all():
|
|
|
|
raise JsonableError(
|
|
|
|
_(
|
2024-04-16 15:52:21 +02:00
|
|
|
"Channel '{channel_name}' is already present in default channel group '{group_name}'",
|
2024-04-15 21:24:26 +02:00
|
|
|
).format(channel_name=stream.name, group_name=group.name)
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
|
|
|
group.streams.add(stream)
|
|
|
|
|
|
|
|
group.save()
|
|
|
|
notify_default_stream_groups(realm)
|
|
|
|
|
|
|
|
|
|
|
|
def do_remove_streams_from_default_stream_group(
|
2024-07-12 02:30:17 +02:00
|
|
|
realm: Realm, group: DefaultStreamGroup, streams: list[Stream]
|
2022-04-14 23:34:23 +02:00
|
|
|
) -> None:
|
2023-07-09 19:47:12 +02:00
|
|
|
group_stream_ids = {stream.id for stream in group.streams.all()}
|
2022-04-14 23:34:23 +02:00
|
|
|
for stream in streams:
|
2023-07-09 19:47:12 +02:00
|
|
|
if stream.id not in group_stream_ids:
|
2022-04-14 23:34:23 +02:00
|
|
|
raise JsonableError(
|
|
|
|
_(
|
2024-04-16 15:52:21 +02:00
|
|
|
"Channel '{channel_name}' is not present in default channel group '{group_name}'",
|
2024-04-15 21:24:26 +02:00
|
|
|
).format(channel_name=stream.name, group_name=group.name)
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
|
|
|
|
2023-07-09 19:47:12 +02:00
|
|
|
delete_stream_ids = {stream.id for stream in streams}
|
|
|
|
|
|
|
|
group.streams.remove(*delete_stream_ids)
|
2022-04-14 23:34:23 +02:00
|
|
|
notify_default_stream_groups(realm)
|
|
|
|
|
|
|
|
|
|
|
|
def do_change_default_stream_group_name(
|
|
|
|
realm: Realm, group: DefaultStreamGroup, new_group_name: str
|
|
|
|
) -> None:
|
|
|
|
if group.name == new_group_name:
|
|
|
|
raise JsonableError(
|
2024-04-16 15:52:21 +02:00
|
|
|
_("This default channel group is already named '{group_name}'").format(
|
2023-07-17 22:40:33 +02:00
|
|
|
group_name=new_group_name
|
|
|
|
)
|
2022-04-14 23:34:23 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
if DefaultStreamGroup.objects.filter(name=new_group_name, realm=realm).exists():
|
2023-07-17 22:40:33 +02:00
|
|
|
raise JsonableError(
|
2024-04-16 15:52:21 +02:00
|
|
|
_("Default channel group '{group_name}' already exists").format(
|
2023-07-17 22:40:33 +02:00
|
|
|
group_name=new_group_name
|
|
|
|
)
|
|
|
|
)
|
2022-04-14 23:34:23 +02:00
|
|
|
|
|
|
|
group.name = new_group_name
|
|
|
|
group.save()
|
|
|
|
notify_default_stream_groups(realm)
|
|
|
|
|
|
|
|
|
|
|
|
def do_change_default_stream_group_description(
|
|
|
|
realm: Realm, group: DefaultStreamGroup, new_description: str
|
|
|
|
) -> None:
|
|
|
|
group.description = new_description
|
|
|
|
group.save()
|
|
|
|
notify_default_stream_groups(realm)
|
|
|
|
|
|
|
|
|
|
|
|
def do_remove_default_stream_group(realm: Realm, group: DefaultStreamGroup) -> None:
|
|
|
|
group.delete()
|
|
|
|
notify_default_stream_groups(realm)
|
|
|
|
|
|
|
|
|
2022-06-23 20:07:19 +02:00
|
|
|
def default_stream_groups_to_dicts_sorted(
|
|
|
|
groups: Iterable[DefaultStreamGroup],
|
2024-07-12 02:30:17 +02:00
|
|
|
) -> list[dict[str, Any]]:
|
2022-04-14 23:34:23 +02:00
|
|
|
return sorted((group.to_dict() for group in groups), key=lambda elt: elt["name"])
|