Implement API for default stream groups.

This commit is contained in:
Vishnu Ks 2017-11-01 22:50:34 +05:30 committed by Tim Abbott
parent 5ff690d486
commit f44b60a150
7 changed files with 339 additions and 6 deletions

View File

@ -69,7 +69,7 @@ from zerver.models import Realm, RealmEmoji, Stream, UserProfile, UserActivity,
custom_profile_fields_for_realm, get_huddle_user_ids, \
CustomProfileFieldValue, validate_attachment_request, get_system_bot, \
get_display_recipient_by_id, query_for_ids, get_huddle_recipient, \
UserGroup, UserGroupMembership
UserGroup, UserGroupMembership, get_default_stream_groups
from zerver.lib.alert_words import alert_words_in_realm
from zerver.lib.avatar import avatar_url
@ -1648,6 +1648,17 @@ def check_stream_name(stream_name):
if ord(i) == 0:
raise JsonableError(_("Stream name '%s' contains NULL (0x00) characters." % (stream_name)))
def check_default_stream_group_name(group_name: Text) -> None:
if group_name.strip() == "":
raise JsonableError(_("Invalid default stream group name '%s'" % (group_name)))
if len(group_name) > DefaultStreamGroup.MAX_NAME_LENGTH:
raise JsonableError(_("Default stream group name too long (limit: %s characters)"
% (DefaultStreamGroup.MAX_NAME_LENGTH)))
for i in group_name:
if ord(i) == 0:
raise JsonableError(_("Default stream group name '%s' contains NULL (0x00) characters."
% (group_name)))
def send_pm_if_empty_stream(sender, stream, stream_name, realm):
# type: (UserProfile, Optional[Stream], Text, Realm) -> None
"""If a bot sends a message to a stream that doesn't exist or has no
@ -2888,6 +2899,13 @@ def notify_default_streams(realm_id):
)
send_event(event, active_user_ids(realm_id))
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))
)
send_event(event, active_user_ids(realm.id))
def do_add_default_stream(stream):
# type: (Stream) -> None
realm_id = stream.realm_id
@ -2903,6 +2921,61 @@ def do_remove_default_stream(stream):
DefaultStream.objects.filter(realm_id=realm_id, stream_id=stream_id).delete()
notify_default_streams(realm_id)
def do_create_default_stream_group(realm: Realm, group_name: Text, streams: List[Stream]) -> None:
default_streams = get_default_streams_for_realm(realm.id)
for stream in streams:
if stream in default_streams:
raise JsonableError(_("'%s' is a default stream and cannot be added to '%s'") % (stream.name, group_name))
check_default_stream_group_name(group_name)
(group, created) = DefaultStreamGroup.objects.get_or_create(name=group_name, realm=realm)
if not created:
raise JsonableError(_("Default stream group '%s' already exists") % (group_name,))
group.streams = streams
group.save()
notify_default_stream_groups(realm)
def do_add_streams_to_default_stream_group(realm: Realm, group_name: Text, streams: List[Stream]) -> None:
try:
group = DefaultStreamGroup.objects.get(name=group_name, realm=realm)
except DefaultStreamGroup.DoesNotExist:
raise JsonableError(_("Default stream group '%s' does not exist") % (group_name,))
default_streams = get_default_streams_for_realm(realm.id)
for stream in streams:
if stream in default_streams:
raise JsonableError(_("'%s' is a default stream and cannot be added to '%s'") % (stream.name, group_name))
if stream in group.streams.all():
raise JsonableError(_("Stream '%s' is already present in default stream group '%s'")
% (stream.name, group_name))
group.streams.add(stream)
group.save()
notify_default_stream_groups(realm)
def do_remove_streams_from_default_stream_group(realm: Realm, group_name: Text, streams: List[Stream]) -> None:
try:
group = DefaultStreamGroup.objects.get(name=group_name, realm=realm)
except DefaultStreamGroup.DoesNotExist:
raise JsonableError(_("Default stream group '%s' does not exist") % (group_name,))
for stream in streams:
if stream not in group.streams.all():
raise JsonableError(_("Stream '%s' is not present in default stream group '%s'")
% (stream.name, group_name))
group.streams.remove(stream)
group.save()
notify_default_stream_groups(realm)
def do_remove_default_stream_group(realm: Realm, group_name: Text) -> None:
try:
DefaultStreamGroup.objects.filter(name=group_name, realm=realm).delete()
except DefaultStreamGroup.DoesNotExist:
raise JsonableError(_("Default stream group '%s' does not exist") % (group_name,))
notify_default_stream_groups(realm)
def get_default_streams_for_realm(realm_id):
# type: (int) -> List[Stream]
return [default.stream for default in
@ -2919,6 +2992,9 @@ def streams_to_dicts_sorted(streams):
# type: (List[Stream]) -> List[Dict[str, Any]]
return sorted([stream.to_dict() for stream in streams], key=lambda elt: elt["name"])
def default_stream_groups_to_dicts_sorted(groups: List[DefaultStreamGroup]) -> List[Dict[str, Any]]:
return sorted([group.to_dict() for group in groups], key=lambda elt: elt["name"])
def do_update_user_activity_interval(user_profile, log_time):
# type: (UserProfile, datetime.datetime) -> None
effective_end = log_time + UserActivityInterval.MIN_INTERVAL_LENGTH

View File

@ -32,7 +32,8 @@ from zerver.lib.actions import (
validate_user_access_to_subscribers_helper,
do_get_streams, get_default_streams_for_realm,
gather_subscriptions_helper, get_cross_realm_dicts,
get_status_dict, streams_to_dicts_sorted
get_status_dict, streams_to_dicts_sorted,
default_stream_groups_to_dicts_sorted
)
from zerver.lib.upload import get_total_uploads_size_for_user
from zerver.lib.user_groups import user_groups_in_realm_serialized
@ -40,7 +41,8 @@ from zerver.tornado.event_queue import request_event_queue, get_user_events
from zerver.models import Client, Message, Realm, UserPresence, UserProfile, \
get_user_profile_by_id, \
get_realm_user_dicts, realm_filters_for_realm, get_user,\
get_owned_bot_dicts, custom_profile_fields_for_realm, get_realm_domains
get_owned_bot_dicts, custom_profile_fields_for_realm, get_realm_domains, \
get_default_stream_groups
from zproject.backends import email_auth_enabled, password_auth_enabled
from version import ZULIP_VERSION
@ -231,6 +233,9 @@ def fetch_initial_state_data(user_profile, event_types, queue_id, client_gravata
state['streams'] = do_get_streams(user_profile)
if want('default_streams'):
state['realm_default_streams'] = streams_to_dicts_sorted(get_default_streams_for_realm(user_profile.realm_id))
if want('default_stream_groups'):
state['realm_default_stream_groups'] = default_stream_groups_to_dicts_sorted(
get_default_stream_groups(user_profile.realm))
if want('update_display_settings'):
for prop in UserProfile.property_types:
@ -396,6 +401,8 @@ def apply_event(state, event, user_profile, client_gravatar, include_subscribers
state['streams'] = [s for s in state['streams'] if s["stream_id"] not in stream_ids]
elif event['type'] == 'default_streams':
state['realm_default_streams'] = event['default_streams']
elif event['type'] == 'default_stream_groups':
state['realm_default_stream_groups'] = event['default_stream_groups']
elif event['type'] == 'realm':
if event['op'] == "update":
field = 'realm_' + event['property']

View File

@ -28,6 +28,7 @@ from zerver.lib.actions import (
do_add_reaction_legacy,
do_add_realm_domain,
do_add_realm_filter,
do_add_streams_to_default_stream_group,
do_change_avatar_fields,
do_change_bot_owner,
do_change_default_all_public_streams,
@ -41,6 +42,7 @@ from zerver.lib.actions import (
do_change_stream_description,
do_change_subscription_property,
do_create_user,
do_create_default_stream_group,
do_deactivate_stream,
do_deactivate_user,
do_delete_message,
@ -50,10 +52,12 @@ from zerver.lib.actions import (
do_regenerate_api_key,
do_remove_alert_words,
do_remove_default_stream,
do_remove_default_stream_group,
do_remove_reaction_legacy,
do_remove_realm_domain,
do_remove_realm_emoji,
do_remove_realm_filter,
do_remove_streams_from_default_stream_group,
do_rename_stream,
do_set_realm_authentication_methods,
do_set_realm_message_editing,
@ -979,6 +983,45 @@ class EventsRegisterTest(ZulipTestCase):
error = alert_words_checker('events[0]', events[0])
self.assert_on_error(error)
def test_default_stream_groups_events(self):
# type: () -> None
default_stream_groups_checker = self.check_events_dict([
('type', equals('default_stream_groups')),
('default_stream_groups', check_list(check_dict_only([
('name', check_string),
('streams', check_list(check_dict_only([
('description', check_string),
('invite_only', check_bool),
('name', check_string),
('stream_id', check_int)]))),
]))),
])
streams = []
for stream_name in ["Scotland", "Verona", "Denmark"]:
streams.append(get_stream(stream_name, self.user_profile.realm))
events = self.do_test(lambda: do_create_default_stream_group(self.user_profile.realm,
"group1", streams))
error = default_stream_groups_checker('events[0]', events[0])
self.assert_on_error(error)
venice_stream = get_stream("Venice", self.user_profile.realm)
events = self.do_test(lambda: do_add_streams_to_default_stream_group(self.user_profile.realm,
"group1", [venice_stream]))
error = default_stream_groups_checker('events[0]', events[0])
self.assert_on_error(error)
events = self.do_test(lambda: do_remove_streams_from_default_stream_group(self.user_profile.realm,
"group1", [venice_stream]))
error = default_stream_groups_checker('events[0]', events[0])
self.assert_on_error(error)
events = self.do_test(lambda: do_remove_default_stream_group(self.user_profile.realm,
"group1"))
error = default_stream_groups_checker('events[0]', events[0])
self.assert_on_error(error)
def test_default_streams_events(self):
# type: () -> None
default_streams_checker = self.check_events_dict([
@ -2498,13 +2541,14 @@ class FetchQueriesTest(ZulipTestCase):
client_gravatar=False,
)
self.assert_length(queries, 29)
self.assert_length(queries, 30)
expected_counts = dict(
alert_words=0,
attachments=1,
custom_profile_fields=1,
default_streams=1,
default_stream_groups=1,
hotspots=0,
message=1,
muted_topics=1,

View File

@ -111,6 +111,7 @@ class HomeTest(ZulipTestCase):
"realm_bots",
"realm_create_stream_by_admins_only",
"realm_default_language",
"realm_default_stream_groups",
"realm_default_streams",
"realm_description",
"realm_domains",
@ -180,7 +181,7 @@ class HomeTest(ZulipTestCase):
with patch('zerver.lib.cache.cache_set') as cache_mock:
result = self._get_home_page(stream='Denmark')
self.assert_length(queries, 41)
self.assert_length(queries, 42)
self.assert_length(cache_mock.call_args_list, 10)
html = result.content.decode('utf-8')
@ -245,7 +246,7 @@ class HomeTest(ZulipTestCase):
with queries_captured() as queries2:
result = self._get_home_page()
self.assert_length(queries2, 35)
self.assert_length(queries2, 36)
# Do a sanity check that our new streams were in the payload.
html = result.content.decode('utf-8')

View File

@ -41,6 +41,7 @@ from zerver.lib.test_runner import (
from zerver.models import (
get_display_recipient, Message, Realm, Recipient, Stream, Subscription,
DefaultStream, UserProfile, get_user_profile_by_id, active_user_ids,
get_default_stream_groups
)
from zerver.lib.actions import (
@ -52,6 +53,9 @@ from zerver.lib.actions import (
create_stream_if_needed, create_streams_if_needed,
do_deactivate_stream,
stream_welcome_message,
do_create_default_stream_group,
do_add_streams_to_default_stream_group, do_remove_streams_from_default_stream_group,
do_remove_default_stream_group,
)
from zerver.views.streams import (
@ -827,6 +831,165 @@ class DefaultStreamTest(ZulipTestCase):
self.assert_json_success(result)
self.assertFalse(stream_name in self.get_default_stream_names(user_profile.realm))
class DefaultStreamGroupTest(ZulipTestCase):
def test_create_update_and_remove_default_stream_group(self) -> None:
realm = get_realm("zulip")
# Test creating new default stream group
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 0)
streams = []
for stream_name in ["stream1", "stream2", "stream3"]:
(stream, _) = create_stream_if_needed(realm, stream_name)
streams.append(stream)
group_name = "group1"
do_create_default_stream_group(realm, group_name, streams)
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 1)
self.assertEqual(default_stream_groups[0].name, group_name)
self.assertEqual(list(default_stream_groups[0].streams.all()), streams)
# Test adding streams to existing default stream group
new_stream_names = ["stream4", "stream5"]
new_streams = []
for new_stream_name in new_stream_names:
(new_stream, _) = create_stream_if_needed(realm, new_stream_name)
new_streams.append(new_stream)
streams.append(new_stream)
do_add_streams_to_default_stream_group(realm, group_name, new_streams)
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 1)
self.assertEqual(default_stream_groups[0].name, group_name)
self.assertEqual(list(default_stream_groups[0].streams.all()), streams)
# Test removing streams from existing default stream group
do_remove_streams_from_default_stream_group(realm, group_name, new_streams)
remaining_streams = streams[0:3]
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 1)
self.assertEqual(default_stream_groups[0].name, group_name)
self.assertEqual(list(default_stream_groups[0].streams.all()), remaining_streams)
# Test removing default stream group
do_remove_default_stream_group(realm, group_name)
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 0)
# Test creating a default stream group which contains a default stream
do_add_default_stream(remaining_streams[0])
with self.assertRaisesRegex(JsonableError, "'stream1' is a default stream and cannot be added to 'group1'"):
do_create_default_stream_group(realm, group_name, remaining_streams)
def test_api_calls(self) -> None:
self.login(self.example_email("hamlet"))
user_profile = self.example_user('hamlet')
realm = user_profile.realm
do_change_is_admin(user_profile, True)
# Test creating new default stream group
stream_names = ["stream1", "stream2", "stream3"]
group_name = "group1"
streams = []
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 0)
for stream_name in stream_names:
(stream, _) = create_stream_if_needed(realm, stream_name)
streams.append(stream)
result = self.client_post('/json/default_stream_groups',
{"group_name": "", "stream_names": ujson.dumps(stream_names)})
self.assert_json_error(result, "Invalid default stream group name ''")
result = self.client_post('/json/default_stream_groups',
{"group_name": group_name, "stream_names": ujson.dumps(stream_names)})
self.assert_json_success(result)
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 1)
self.assertEqual(default_stream_groups[0].name, group_name)
self.assertEqual(list(default_stream_groups[0].streams.all()), streams)
# Test adding streams to existing default stream group
new_stream_names = ["stream4", "stream5"]
new_streams = []
for new_stream_name in new_stream_names:
(new_stream, _) = create_stream_if_needed(realm, new_stream_name)
new_streams.append(new_stream)
streams.append(new_stream)
result = self.client_patch("/json/default_stream_groups",
{"group_name": group_name,
"stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, "Missing 'op' argument")
result = self.client_patch("/json/default_stream_groups",
{"group_name": group_name, "op": "invalid",
"stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, 'Nothing to do. Specify at least one of "add" or "remove".')
result = self.client_patch("/json/default_stream_groups",
{"group_name": "dumbledore's army", "op": "add",
"stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, "Default stream group 'dumbledore's army' does not exist")
do_add_default_stream(new_streams[0])
result = self.client_patch('/json/default_stream_groups',
{"group_name": group_name, "op": "add", "stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, "'stream4' is a default stream and cannot be added to 'group1'")
do_remove_default_stream(new_streams[0])
result = self.client_patch("/json/default_stream_groups",
{"group_name": group_name, "op": "add",
"stream_names": ujson.dumps(new_stream_names)})
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 1)
self.assertEqual(default_stream_groups[0].name, group_name)
self.assertEqual(list(default_stream_groups[0].streams.all()), streams)
result = self.client_patch('/json/default_stream_groups',
{"group_name": group_name, "op": "add", "stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, "Stream 'stream4' is already present in default stream group 'group1'")
# Test removing streams from default stream group
result = self.client_patch("/json/default_stream_groups",
{"group_name": "dumbledore's army", "op": "remove",
"stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, "Default stream group 'dumbledore's army' does not exist")
result = self.client_patch("/json/default_stream_groups",
{"group_name": group_name, "op": "remove",
"stream_names": ujson.dumps(["random stream name"])})
self.assert_json_error(result, "Invalid stream name 'random stream name'")
streams.remove(new_streams[0])
result = self.client_patch("/json/default_stream_groups",
{"group_name": group_name, "op": "remove",
"stream_names": ujson.dumps([new_stream_names[0]])})
self.assert_json_success(result)
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 1)
self.assertEqual(default_stream_groups[0].name, group_name)
self.assertEqual(list(default_stream_groups[0].streams.all()), streams)
result = self.client_patch("/json/default_stream_groups",
{"group_name": group_name, "op": "remove",
"stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, "Stream 'stream4' is not present in default stream group 'group1'")
# Test deleting a default stream group
result = self.client_delete('/json/default_stream_groups', {"group_name": group_name})
self.assert_json_success(result)
default_stream_groups = get_default_stream_groups(realm)
self.assert_length(default_stream_groups, 0)
do_remove_default_stream(new_stream)
result = self.client_patch("/json/default_stream_groups",
{"group_name": group_name, "op": "add", "stream_names": ujson.dumps(new_stream_names)})
self.assert_json_error(result, "Default stream group 'group1' does not exist")
class SubscriptionPropertiesTest(ZulipTestCase):
def test_set_stream_color(self):
# type: () -> None

View File

@ -18,6 +18,8 @@ from zerver.lib.actions import bulk_remove_subscriptions, \
do_deactivate_stream, do_change_stream_invite_only, do_add_default_stream, \
do_change_stream_description, do_get_streams, \
do_remove_default_stream, get_topic_history_for_stream, \
do_create_default_stream_group, do_add_streams_to_default_stream_group, \
do_remove_streams_from_default_stream_group, do_remove_default_stream_group, \
prep_stream_welcome_message
from zerver.lib.response import json_success, json_error, json_response
from zerver.lib.streams import access_stream_by_id, access_stream_by_name, \
@ -72,6 +74,42 @@ def add_default_stream(request, user_profile, stream_name=REQ()):
do_add_default_stream(stream)
return json_success()
@require_realm_admin
@has_request_variables
def create_default_stream_group(request: HttpRequest, user_profile: UserProfile,
group_name: Text=REQ(),
stream_names: List[Text]=REQ(validator=check_list(check_string))) -> None:
streams = []
for stream_name in stream_names:
(stream, recipient, sub) = access_stream_by_name(user_profile, stream_name)
streams.append(stream)
do_create_default_stream_group(user_profile.realm, group_name, streams)
return json_success()
@require_realm_admin
@has_request_variables
def update_default_stream_group(request: HttpRequest, user_profile: UserProfile,
group_name: Text=REQ(), op: Text=REQ(),
stream_names: List[Text]=REQ(validator=check_list(check_string))) -> None:
streams = []
for stream_name in stream_names:
(stream, recipient, sub) = access_stream_by_name(user_profile, stream_name)
streams.append(stream)
if op == 'add':
do_add_streams_to_default_stream_group(user_profile.realm, group_name, streams)
elif op == 'remove':
do_remove_streams_from_default_stream_group(user_profile.realm, group_name, streams)
else:
return json_error(_('Nothing to do. Specify at least one of "add" or "remove".'))
return json_success()
@require_realm_admin
@has_request_variables
def remove_default_stream_group(request: HttpRequest, user_profile: UserProfile, group_name: Text=REQ()) -> None:
do_remove_default_stream_group(user_profile.realm, group_name)
return json_success()
@require_realm_admin
@has_request_variables
def remove_default_stream(request, user_profile, stream_name=REQ()):

View File

@ -283,6 +283,10 @@ v1_api_and_json_patterns = [
url(r'^default_streams$', rest_dispatch,
{'POST': 'zerver.views.streams.add_default_stream',
'DELETE': 'zerver.views.streams.remove_default_stream'}),
url(r'^default_stream_groups', rest_dispatch,
{'POST': 'zerver.views.streams.create_default_stream_group',
'PATCH': 'zerver.views.streams.update_default_stream_group',
'DELETE': 'zerver.views.streams.remove_default_stream_group'}),
# GET lists your streams, POST bulk adds, PATCH bulk modifies/removes
url(r'^users/me/subscriptions$', rest_dispatch,
{'GET': 'zerver.views.streams.list_subscriptions_backend',