mirror of https://github.com/zulip/zulip.git
settings: Migrate to stream_post_policy structure.
This commit includes a new `stream_post_policy` setting, by replacing the `is_announcement_only` field from the Stream model, which is done by mirroring the structure of the existing `create_stream_policy`. It includes the necessary schema and database migrations to migrate the is_announcement_only boolean field to stream_post_policy, a smallPositiveInteger field similar to many other settings. This change is done to allow organization administrators to restrict new members from creating and posting to a stream. However, this does not affect admins who are new members. With many tweaks by tabbott to documentation under /help, etc. Fixes #13616.
This commit is contained in:
parent
31aecc0abb
commit
174b2abcfd
|
@ -81,6 +81,13 @@ people.small_avatar_url_for_person = function () {
|
||||||
return 'http://example.com/example.png';
|
return 'http://example.com/example.png';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const new_user = {
|
||||||
|
email: 'new_user@example.com',
|
||||||
|
user_id: 101,
|
||||||
|
full_name: 'New User',
|
||||||
|
date_joined: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
const me = {
|
const me = {
|
||||||
email: 'me@example.com',
|
email: 'me@example.com',
|
||||||
user_id: 30,
|
user_id: 30,
|
||||||
|
@ -100,6 +107,7 @@ const bob = {
|
||||||
full_name: 'Bob',
|
full_name: 'Bob',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
people.add(new_user);
|
||||||
people.add(me);
|
people.add(me);
|
||||||
people.initialize_current_user(me.user_id);
|
people.initialize_current_user(me.user_id);
|
||||||
|
|
||||||
|
@ -315,30 +323,30 @@ run_test('validate_stream_message', () => {
|
||||||
assert($("#compose-all-everyone").visible());
|
assert($("#compose-all-everyone").visible());
|
||||||
});
|
});
|
||||||
|
|
||||||
run_test('test_validate_stream_message_announcement_only', () => {
|
run_test('test_validate_stream_message_post_policy', () => {
|
||||||
// This test is in continuation with test_validate but it has been seperated out
|
// This test is in continuation with test_validate but it has been seperated out
|
||||||
// for better readabilty. Their relative position of execution should not be changed.
|
// for better readabilty. Their relative position of execution should not be changed.
|
||||||
// Although the position with respect to test_validate_stream_message does not matter
|
// Although the position with respect to test_validate_stream_message does not matter
|
||||||
// as `get_announcement_only` is reset at the end.
|
// as `get_stream_post_policy` is reset at the end.
|
||||||
page_params.is_admin = false;
|
page_params.is_admin = false;
|
||||||
const sub = {
|
const sub = {
|
||||||
stream_id: 102,
|
stream_id: 102,
|
||||||
name: 'stream102',
|
name: 'stream102',
|
||||||
subscribed: true,
|
subscribed: true,
|
||||||
announcement_only: true,
|
stream_post_policy: stream_data.stream_post_policy_values.admins.code,
|
||||||
};
|
};
|
||||||
stream_data.get_announcement_only = function () {
|
stream_data.get_stream_post_policy = function () {
|
||||||
return true;
|
return 2;
|
||||||
};
|
};
|
||||||
compose_state.topic('subject102');
|
compose_state.topic('subject102');
|
||||||
stream_data.add_sub('stream102', sub);
|
stream_data.add_sub('stream102', sub);
|
||||||
assert(!compose.validate());
|
assert(!compose.validate());
|
||||||
assert.equal($('#compose-error-msg').html(), i18n.t("Only organization admins are allowed to post to this stream."));
|
assert.equal($('#compose-error-msg').html(), i18n.t("Only organization admins are allowed to post to this stream."));
|
||||||
|
|
||||||
// reset `get_announcement_only` so that any tests occurung after this
|
// reset `get_stream_post_policy` so that any tests occurung after this
|
||||||
// do not reproduce this error.
|
// do not reproduce this error.
|
||||||
stream_data.get_announcement_only = function () {
|
stream_data.get_stream_post_policy = function () {
|
||||||
return false;
|
return stream_data.stream_post_policy_values.everyone.code;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,8 @@ set_global('$', global.make_zjquery());
|
||||||
set_global('compose_pm_pill', {
|
set_global('compose_pm_pill', {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('people');
|
zrequire('people');
|
||||||
zrequire('compose_ui');
|
zrequire('compose_ui');
|
||||||
zrequire('compose');
|
zrequire('compose');
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
set_global('blueslip', {});
|
set_global('blueslip', {});
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
global.blueslip.warn = function () {};
|
global.blueslip.warn = function () {};
|
||||||
|
|
||||||
zrequire('util');
|
zrequire('util');
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('util');
|
zrequire('util');
|
||||||
zrequire('unread');
|
zrequire('unread');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
|
|
|
@ -59,6 +59,7 @@ const denmark_stream = {
|
||||||
// prefer to test with a clean slate.
|
// prefer to test with a clean slate.
|
||||||
|
|
||||||
set_global('page_params', {});
|
set_global('page_params', {});
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
set_global('i18n', global.stub_i18n);
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('hash_util');
|
zrequire('hash_util');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
zrequire('people');
|
zrequire('people');
|
||||||
|
|
|
@ -5,6 +5,8 @@ set_global('location', {
|
||||||
host: 'example.com',
|
host: 'example.com',
|
||||||
});
|
});
|
||||||
set_global('to_$', () => window_stub);
|
set_global('to_$', () => window_stub);
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('people');
|
zrequire('people');
|
||||||
zrequire('hash_util');
|
zrequire('hash_util');
|
||||||
zrequire('hashchange');
|
zrequire('hashchange');
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
zrequire('hash_util');
|
zrequire('hash_util');
|
||||||
set_global('katex', zrequire('katex', 'katex/dist/katex.min.js'));
|
set_global('katex', zrequire('katex', 'katex/dist/katex.min.js'));
|
||||||
set_global('marked', zrequire('marked', 'third/marked/lib/marked'));
|
set_global('marked', zrequire('marked', 'third/marked/lib/marked'));
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('util');
|
zrequire('util');
|
||||||
zrequire('fenced_code');
|
zrequire('fenced_code');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('muting');
|
zrequire('muting');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
set_global('blueslip', global.make_zblueslip());
|
set_global('blueslip', global.make_zblueslip());
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
set_global('$', global.make_zjquery());
|
set_global('$', global.make_zjquery());
|
||||||
zrequire('hash_util');
|
zrequire('hash_util');
|
||||||
zrequire('hashchange');
|
zrequire('hashchange');
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
set_global('$', global.make_zjquery());
|
set_global('$', global.make_zjquery());
|
||||||
|
|
||||||
zrequire('narrow_state');
|
zrequire('narrow_state');
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('Filter', 'js/filter');
|
zrequire('Filter', 'js/filter');
|
||||||
zrequire('MessageListData', 'js/message_list_data');
|
zrequire('MessageListData', 'js/message_list_data');
|
||||||
zrequire('narrow_state');
|
zrequire('narrow_state');
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('people');
|
zrequire('people');
|
||||||
zrequire('Filter', 'js/filter');
|
zrequire('Filter', 'js/filter');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('Filter', 'js/filter');
|
zrequire('Filter', 'js/filter');
|
||||||
zrequire('people');
|
zrequire('people');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
|
|
|
@ -20,6 +20,8 @@ const _navigator = {
|
||||||
};
|
};
|
||||||
set_global('navigator', _navigator);
|
set_global('navigator', _navigator);
|
||||||
|
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('alert_words');
|
zrequire('alert_words');
|
||||||
zrequire('muting');
|
zrequire('muting');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
|
|
|
@ -6,6 +6,8 @@ set_global('message_store', {
|
||||||
user_ids: () => [],
|
user_ids: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('util');
|
zrequire('util');
|
||||||
zrequire('typeahead_helper');
|
zrequire('typeahead_helper');
|
||||||
set_global('Handlebars', global.make_handlebars());
|
set_global('Handlebars', global.make_handlebars());
|
||||||
|
|
|
@ -5,6 +5,8 @@ set_global('message_store', {
|
||||||
user_ids: () => [],
|
user_ids: () => [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('util');
|
zrequire('util');
|
||||||
zrequire('typeahead_helper');
|
zrequire('typeahead_helper');
|
||||||
set_global('Handlebars', global.make_handlebars());
|
set_global('Handlebars', global.make_handlebars());
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
set_global('$', global.make_zjquery());
|
set_global('$', global.make_zjquery());
|
||||||
|
|
||||||
zrequire('settings_muting');
|
zrequire('settings_muting');
|
||||||
|
|
|
@ -58,7 +58,7 @@ run_test('basics', () => {
|
||||||
stream_id: 2,
|
stream_id: 2,
|
||||||
is_muted: false,
|
is_muted: false,
|
||||||
invite_only: true,
|
invite_only: true,
|
||||||
is_announcement_only: true,
|
stream_post_policy: stream_data.stream_post_policy_values.admins.code,
|
||||||
};
|
};
|
||||||
const test = {
|
const test = {
|
||||||
subscribed: true,
|
subscribed: true,
|
||||||
|
@ -88,8 +88,8 @@ run_test('basics', () => {
|
||||||
|
|
||||||
assert(stream_data.get_invite_only('social'));
|
assert(stream_data.get_invite_only('social'));
|
||||||
assert(!stream_data.get_invite_only('unknown'));
|
assert(!stream_data.get_invite_only('unknown'));
|
||||||
assert(stream_data.get_announcement_only('social'));
|
assert(stream_data.get_stream_post_policy('social'));
|
||||||
assert(!stream_data.get_announcement_only('unknown'));
|
assert(!stream_data.get_stream_post_policy('unknown'));
|
||||||
|
|
||||||
assert.equal(stream_data.get_color('social'), 'red');
|
assert.equal(stream_data.get_color('social'), 'red');
|
||||||
assert.equal(stream_data.get_color('unknown'), global.stream_color.default_color);
|
assert.equal(stream_data.get_color('unknown'), global.stream_color.default_color);
|
||||||
|
@ -458,7 +458,7 @@ run_test('stream_settings', () => {
|
||||||
subscribed: true,
|
subscribed: true,
|
||||||
invite_only: true,
|
invite_only: true,
|
||||||
history_public_to_subscribers: true,
|
history_public_to_subscribers: true,
|
||||||
is_announcement_only: true,
|
stream_post_policy: stream_data.stream_post_policy_values.admins.code,
|
||||||
};
|
};
|
||||||
stream_data.clear_subscriptions();
|
stream_data.clear_subscriptions();
|
||||||
stream_data.add_sub(cinnamon.name, cinnamon);
|
stream_data.add_sub(cinnamon.name, cinnamon);
|
||||||
|
@ -479,18 +479,20 @@ run_test('stream_settings', () => {
|
||||||
assert.equal(sub_rows[2].invite_only, false);
|
assert.equal(sub_rows[2].invite_only, false);
|
||||||
|
|
||||||
assert.equal(sub_rows[0].history_public_to_subscribers, true);
|
assert.equal(sub_rows[0].history_public_to_subscribers, true);
|
||||||
assert.equal(sub_rows[0].is_announcement_only, true);
|
assert.equal(sub_rows[0].stream_post_policy ===
|
||||||
|
stream_data.stream_post_policy_values.admins.code, true);
|
||||||
|
|
||||||
const sub = stream_data.get_sub('a');
|
const sub = stream_data.get_sub('a');
|
||||||
stream_data.update_stream_privacy(sub, {
|
stream_data.update_stream_privacy(sub, {
|
||||||
invite_only: false,
|
invite_only: false,
|
||||||
history_public_to_subscribers: false,
|
history_public_to_subscribers: false,
|
||||||
});
|
});
|
||||||
stream_data.update_stream_announcement_only(sub, false);
|
stream_data.update_stream_post_policy(sub, 1);
|
||||||
stream_data.update_calculated_fields(sub);
|
stream_data.update_calculated_fields(sub);
|
||||||
assert.equal(sub.invite_only, false);
|
assert.equal(sub.invite_only, false);
|
||||||
assert.equal(sub.history_public_to_subscribers, false);
|
assert.equal(sub.history_public_to_subscribers, false);
|
||||||
assert.equal(sub.is_announcement_only, false);
|
assert.equal(sub.stream_post_policy,
|
||||||
|
stream_data.stream_post_policy_values.everyone.code);
|
||||||
|
|
||||||
// For guest user only retrieve subscribed streams
|
// For guest user only retrieve subscribed streams
|
||||||
sub_rows = stream_data.get_updated_unsorted_subs();
|
sub_rows = stream_data.get_updated_unsorted_subs();
|
||||||
|
|
|
@ -2,6 +2,7 @@ const noop = function () {};
|
||||||
const return_true = function () { return true; };
|
const return_true = function () { return true; };
|
||||||
set_global('$', global.make_zjquery());
|
set_global('$', global.make_zjquery());
|
||||||
set_global('document', 'document-stub');
|
set_global('document', 'document-stub');
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
set_global('colorspace', {
|
set_global('colorspace', {
|
||||||
sRGB_to_linear: noop,
|
sRGB_to_linear: noop,
|
||||||
|
@ -150,14 +151,14 @@ run_test('update_property', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test stream is_announcement_only change event
|
// Test stream stream_post_policy change event
|
||||||
with_overrides(function (override) {
|
with_overrides(function (override) {
|
||||||
global.with_stub(function (stub) {
|
global.with_stub(function (stub) {
|
||||||
override('subs.update_stream_announcement_only', stub.f);
|
override('subs.update_stream_post_policy', stub.f);
|
||||||
stream_events.update_property(1, 'is_announcement_only', true);
|
stream_events.update_property(1, 'stream_post_policy', stream_data.stream_post_policy_values.admins.code);
|
||||||
const args = stub.get_args('sub', 'val');
|
const args = stub.get_args('sub', 'val');
|
||||||
assert.equal(args.sub.stream_id, 1);
|
assert.equal(args.sub.stream_id, 1);
|
||||||
assert.equal(args.val, true);
|
assert.equal(args.val, stream_data.stream_post_policy_values.admins.code);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
set_global('document', 'document-stub');
|
set_global('document', 'document-stub');
|
||||||
set_global('$', global.make_zjquery());
|
set_global('$', global.make_zjquery());
|
||||||
set_global('blueslip', global.make_zblueslip());
|
set_global('blueslip', global.make_zblueslip());
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
const FoldDict = zrequire('fold_dict').FoldDict;
|
const FoldDict = zrequire('fold_dict').FoldDict;
|
||||||
const IntDict = zrequire('int_dict').IntDict;
|
const IntDict = zrequire('int_dict').IntDict;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('util');
|
zrequire('util');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
zrequire('stream_sort');
|
zrequire('stream_sort');
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
global.stub_out_jquery();
|
global.stub_out_jquery();
|
||||||
|
|
||||||
set_global('ui', {
|
set_global('ui', {
|
||||||
|
|
|
@ -5,6 +5,7 @@ set_global('i18n', global.stub_i18n);
|
||||||
set_global('page_params', {});
|
set_global('page_params', {});
|
||||||
zrequire('settings_notifications');
|
zrequire('settings_notifications');
|
||||||
zrequire('stream_edit');
|
zrequire('stream_edit');
|
||||||
|
zrequire('stream_data');
|
||||||
|
|
||||||
const { JSDOM } = require("jsdom");
|
const { JSDOM } = require("jsdom");
|
||||||
const { window } = new JSDOM();
|
const { window } = new JSDOM();
|
||||||
|
@ -1281,6 +1282,7 @@ run_test('subscription_stream_privacy_modal', () => {
|
||||||
stream_id: 999,
|
stream_id: 999,
|
||||||
is_private: true,
|
is_private: true,
|
||||||
is_admin: true,
|
is_admin: true,
|
||||||
|
stream_post_policy_values: stream_data.stream_post_policy_values,
|
||||||
};
|
};
|
||||||
const html = render('subscription_stream_privacy_modal', args);
|
const html = render('subscription_stream_privacy_modal', args);
|
||||||
|
|
||||||
|
@ -1289,8 +1291,13 @@ run_test('subscription_stream_privacy_modal', () => {
|
||||||
assert.equal(other_options[1].value, 'invite-only-public-history');
|
assert.equal(other_options[1].value, 'invite-only-public-history');
|
||||||
assert.equal(other_options[2].value, 'invite-only');
|
assert.equal(other_options[2].value, 'invite-only');
|
||||||
|
|
||||||
const is_announcement_only = $(html).find("input[name=is-announcement-only]");
|
const stream_post_policy = $(html).find("input[name=stream-post-policy]");
|
||||||
assert.equal(is_announcement_only.prop('checked'), false);
|
assert.equal(stream_post_policy[0].value,
|
||||||
|
stream_data.stream_post_policy_values.everyone.code);
|
||||||
|
assert.equal(stream_post_policy[1].value,
|
||||||
|
stream_data.stream_post_policy_values.admins.code);
|
||||||
|
assert.equal(stream_post_policy[2].value,
|
||||||
|
stream_data.stream_post_policy_values.non_new_members.code);
|
||||||
|
|
||||||
const button = $(html).find("#change-stream-privacy-button");
|
const button = $(html).find("#change-stream-privacy-button");
|
||||||
assert(button.hasClass("btn-danger"));
|
assert(button.hasClass("btn-danger"));
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('unread');
|
zrequire('unread');
|
||||||
zrequire('util');
|
zrequire('util');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
|
|
|
@ -2,6 +2,7 @@ set_global('blueslip', global.make_zblueslip());
|
||||||
set_global('pm_conversations', {
|
set_global('pm_conversations', {
|
||||||
recent: {},
|
recent: {},
|
||||||
});
|
});
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('muting');
|
zrequire('muting');
|
||||||
zrequire('unread');
|
zrequire('unread');
|
||||||
|
|
|
@ -2,6 +2,7 @@ set_global('narrow_state', {});
|
||||||
set_global('unread', {});
|
set_global('unread', {});
|
||||||
set_global('muting', {});
|
set_global('muting', {});
|
||||||
set_global('message_list', {});
|
set_global('message_list', {});
|
||||||
|
set_global('i18n', global.stub_i18n);
|
||||||
|
|
||||||
zrequire('hash_util');
|
zrequire('hash_util');
|
||||||
zrequire('stream_data');
|
zrequire('stream_data');
|
||||||
|
|
|
@ -477,13 +477,30 @@ function validate_stream_message_announce(stream_name) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate_stream_message_announcement_only(stream_name) {
|
function validate_stream_message_post_policy(stream_name) {
|
||||||
// Only allow realm admins to post to announcement_only streams.
|
if (page_params.is_admin) {
|
||||||
const is_announcement_only = stream_data.get_announcement_only(stream_name);
|
return true;
|
||||||
if (is_announcement_only && !page_params.is_admin) {
|
}
|
||||||
|
|
||||||
|
const stream_post_permission_type = stream_data.stream_post_policy_values;
|
||||||
|
const stream_post_policy = stream_data.get_stream_post_policy(stream_name);
|
||||||
|
|
||||||
|
if (stream_post_policy === stream_post_permission_type.admins.code) {
|
||||||
compose_error(i18n.t("Only organization admins are allowed to post to this stream."));
|
compose_error(i18n.t("Only organization admins are allowed to post to this stream."));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const person = people.get_person_from_user_id(page_params.user_id);
|
||||||
|
const current_datetime = new Date(Date.now());
|
||||||
|
const person_date_joined = new Date(person.date_joined);
|
||||||
|
const days = new Date(current_datetime - person_date_joined).getDate();
|
||||||
|
let error_text;
|
||||||
|
if (stream_post_policy === stream_post_permission_type.non_new_members.code &&
|
||||||
|
days < page_params.realm_waiting_period_threshold) {
|
||||||
|
error_text = i18n.t("New members are not allowed to post to this stream.<br>Permission will be granted in __days__ days.", {days: days});
|
||||||
|
compose_error(error_text);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +554,7 @@ function validate_stream_message() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!validate_stream_message_announcement_only(stream_name)) {
|
if (!validate_stream_message_post_policy(stream_name)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ const stream_name_error = (function () {
|
||||||
}());
|
}());
|
||||||
|
|
||||||
function ajaxSubscribeForCreation(stream_name, description, user_ids, invite_only,
|
function ajaxSubscribeForCreation(stream_name, description, user_ids, invite_only,
|
||||||
is_announcement_only, announce, history_public_to_subscribers) {
|
stream_post_policy, announce, history_public_to_subscribers) {
|
||||||
// TODO: We can eliminate the user_ids -> principals conversion
|
// TODO: We can eliminate the user_ids -> principals conversion
|
||||||
// once we upgrade the backend to accept user_ids.
|
// once we upgrade the backend to accept user_ids.
|
||||||
const persons = _.compact(_.map(user_ids, (user_id) => {
|
const persons = _.compact(_.map(user_ids, (user_id) => {
|
||||||
|
@ -114,7 +114,7 @@ function ajaxSubscribeForCreation(stream_name, description, user_ids, invite_onl
|
||||||
description: description}]),
|
description: description}]),
|
||||||
principals: JSON.stringify(principals),
|
principals: JSON.stringify(principals),
|
||||||
invite_only: JSON.stringify(invite_only),
|
invite_only: JSON.stringify(invite_only),
|
||||||
is_announcement_only: JSON.stringify(is_announcement_only),
|
stream_post_policy: JSON.stringify(stream_post_policy),
|
||||||
announce: JSON.stringify(announce),
|
announce: JSON.stringify(announce),
|
||||||
history_public_to_subscribers: JSON.stringify(history_public_to_subscribers),
|
history_public_to_subscribers: JSON.stringify(history_public_to_subscribers),
|
||||||
},
|
},
|
||||||
|
@ -182,12 +182,18 @@ function create_stream() {
|
||||||
const stream_name = $.trim($("#create_stream_name").val());
|
const stream_name = $.trim($("#create_stream_name").val());
|
||||||
const description = $.trim($("#create_stream_description").val());
|
const description = $.trim($("#create_stream_description").val());
|
||||||
const privacy_setting = $('#stream_creation_form input[name=privacy]:checked').val();
|
const privacy_setting = $('#stream_creation_form input[name=privacy]:checked').val();
|
||||||
const is_announcement_only = $('#stream_creation_form input[name=is-announcement-only]').prop('checked');
|
let stream_post_policy = parseInt($('#stream_creation_form input[name=stream-post-policy]').val(), 10);
|
||||||
const principals = get_principals();
|
const principals = get_principals();
|
||||||
|
|
||||||
let invite_only;
|
let invite_only;
|
||||||
let history_public_to_subscribers;
|
let history_public_to_subscribers;
|
||||||
|
|
||||||
|
// Because the stream_post_policy field is hidden when non-administrators create streams,
|
||||||
|
// we need to set the default value here.
|
||||||
|
if (isNaN(stream_post_policy)) {
|
||||||
|
stream_post_policy = stream_data.stream_post_policy_values.everyone.code;
|
||||||
|
}
|
||||||
|
|
||||||
if (privacy_setting === 'invite-only') {
|
if (privacy_setting === 'invite-only') {
|
||||||
invite_only = true;
|
invite_only = true;
|
||||||
history_public_to_subscribers = false;
|
history_public_to_subscribers = false;
|
||||||
|
@ -219,7 +225,7 @@ function create_stream() {
|
||||||
description,
|
description,
|
||||||
principals,
|
principals,
|
||||||
invite_only,
|
invite_only,
|
||||||
is_announcement_only,
|
stream_post_policy,
|
||||||
announce,
|
announce,
|
||||||
history_public_to_subscribers
|
history_public_to_subscribers
|
||||||
);
|
);
|
||||||
|
|
|
@ -89,6 +89,21 @@ let filter_out_inactives = false;
|
||||||
const stream_ids_by_name = new FoldDict();
|
const stream_ids_by_name = new FoldDict();
|
||||||
const default_stream_ids = new Set();
|
const default_stream_ids = new Set();
|
||||||
|
|
||||||
|
exports.stream_post_policy_values = {
|
||||||
|
everyone: {
|
||||||
|
code: 1,
|
||||||
|
description: i18n.t("All stream members can post"),
|
||||||
|
},
|
||||||
|
admins: {
|
||||||
|
code: 2,
|
||||||
|
description: i18n.t("Only organization administrators can post"),
|
||||||
|
},
|
||||||
|
non_new_members: {
|
||||||
|
code: 3,
|
||||||
|
description: i18n.t("Only organization full members can post"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
exports.clear_subscriptions = function () {
|
exports.clear_subscriptions = function () {
|
||||||
stream_info = new BinaryDict(function (sub) {
|
stream_info = new BinaryDict(function (sub) {
|
||||||
return sub.subscribed;
|
return sub.subscribed;
|
||||||
|
@ -375,8 +390,8 @@ exports.get_subscriber_count = function (stream_name) {
|
||||||
return sub.subscribers.size;
|
return sub.subscribers.size;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.update_stream_announcement_only = function (sub, is_announcement_only) {
|
exports.update_stream_post_policy = function (sub, stream_post_policy) {
|
||||||
sub.is_announcement_only = is_announcement_only;
|
sub.stream_post_policy = stream_post_policy;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.update_stream_privacy = function (sub, values) {
|
exports.update_stream_privacy = function (sub, values) {
|
||||||
|
@ -505,12 +520,12 @@ exports.get_invite_only = function (stream_name) {
|
||||||
return sub.invite_only;
|
return sub.invite_only;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.get_announcement_only = function (stream_name) {
|
exports.get_stream_post_policy = function (stream_name) {
|
||||||
const sub = exports.get_sub(stream_name);
|
const sub = exports.get_sub(stream_name);
|
||||||
if (sub === undefined) {
|
if (sub === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return sub.is_announcement_only;
|
return sub.stream_post_policy;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.all_topics_in_cache = function (sub) {
|
exports.all_topics_in_cache = function (sub) {
|
||||||
|
|
|
@ -272,6 +272,7 @@ exports.show_settings_for = function (node) {
|
||||||
const html = render_subscription_settings({
|
const html = render_subscription_settings({
|
||||||
sub: sub,
|
sub: sub,
|
||||||
settings: exports.stream_settings(sub),
|
settings: exports.stream_settings(sub),
|
||||||
|
stream_post_policy_values: stream_data.stream_post_policy_values,
|
||||||
});
|
});
|
||||||
ui.get_content_element($('.subscriptions .right .settings')).html(html);
|
ui.get_content_element($('.subscriptions .right .settings')).html(html);
|
||||||
|
|
||||||
|
@ -338,7 +339,6 @@ function stream_setting_clicked(e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
exports.bulk_set_stream_property = function (sub_data) {
|
exports.bulk_set_stream_property = function (sub_data) {
|
||||||
return channel.post({
|
return channel.post({
|
||||||
url: '/json/users/me/subscriptions/properties',
|
url: '/json/users/me/subscriptions/properties',
|
||||||
|
@ -359,7 +359,7 @@ function change_stream_privacy(e) {
|
||||||
const sub = stream_data.get_sub_by_id(stream_id);
|
const sub = stream_data.get_sub_by_id(stream_id);
|
||||||
|
|
||||||
const privacy_setting = $('#stream_privacy_modal input[name=privacy]:checked').val();
|
const privacy_setting = $('#stream_privacy_modal input[name=privacy]:checked').val();
|
||||||
const is_announcement_only = $('#stream_privacy_modal input[name=is-announcement-only]').prop('checked');
|
const stream_post_policy = parseInt($('#stream_privacy_modal input[name=stream-post-policy]:checked').val(), 10);
|
||||||
|
|
||||||
let invite_only;
|
let invite_only;
|
||||||
let history_public_to_subscribers;
|
let history_public_to_subscribers;
|
||||||
|
@ -380,7 +380,7 @@ function change_stream_privacy(e) {
|
||||||
stream_name: sub.name,
|
stream_name: sub.name,
|
||||||
// toggle the privacy setting
|
// toggle the privacy setting
|
||||||
is_private: JSON.stringify(invite_only),
|
is_private: JSON.stringify(invite_only),
|
||||||
is_announcement_only: JSON.stringify(is_announcement_only),
|
stream_post_policy: JSON.stringify(stream_post_policy),
|
||||||
history_public_to_subscribers: JSON.stringify(history_public_to_subscribers),
|
history_public_to_subscribers: JSON.stringify(history_public_to_subscribers),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -493,7 +493,8 @@ exports.initialize = function () {
|
||||||
const template_data = {
|
const template_data = {
|
||||||
stream_id: stream_id,
|
stream_id: stream_id,
|
||||||
stream_name: stream.name,
|
stream_name: stream.name,
|
||||||
is_announcement_only: stream.is_announcement_only,
|
stream_post_policy_values: stream_data.stream_post_policy_values,
|
||||||
|
stream_post_policy: stream.stream_post_policy,
|
||||||
is_public: !stream.invite_only,
|
is_public: !stream.invite_only,
|
||||||
is_private: stream.invite_only && !stream.history_public_to_subscribers,
|
is_private: stream.invite_only && !stream.history_public_to_subscribers,
|
||||||
is_private_with_public_history: stream.invite_only &&
|
is_private_with_public_history: stream.invite_only &&
|
||||||
|
|
|
@ -53,8 +53,8 @@ exports.update_property = function (stream_id, property, value, other_values) {
|
||||||
case 'wildcard_mentions_notify':
|
case 'wildcard_mentions_notify':
|
||||||
update_stream_setting(sub, value, property);
|
update_stream_setting(sub, value, property);
|
||||||
break;
|
break;
|
||||||
case 'is_announcement_only':
|
case 'stream_post_policy':
|
||||||
subs.update_stream_announcement_only(sub, value);
|
subs.update_stream_post_policy(sub, value);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
blueslip.warn("Unexpected subscription property type", {property: property,
|
blueslip.warn("Unexpected subscription property type", {property: property,
|
||||||
|
|
|
@ -141,7 +141,7 @@ exports.update_stream_privacy_type_icon = function (sub) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.update_stream_privacy_type_text = function (sub) {
|
exports.update_stream_subscription_type_text = function (sub) {
|
||||||
const stream_settings = stream_edit.settings_for_sub(sub);
|
const stream_settings = stream_edit.settings_for_sub(sub);
|
||||||
const html = render_subscription_type(sub);
|
const html = render_subscription_type(sub);
|
||||||
if (stream_edit.is_sub_settings_active(sub)) {
|
if (stream_edit.is_sub_settings_active(sub)) {
|
||||||
|
|
|
@ -172,7 +172,7 @@ exports.update_stream_privacy = function (sub, values) {
|
||||||
|
|
||||||
// Update UI elements
|
// Update UI elements
|
||||||
stream_ui_updates.update_stream_privacy_type_icon(sub);
|
stream_ui_updates.update_stream_privacy_type_icon(sub);
|
||||||
stream_ui_updates.update_stream_privacy_type_text(sub);
|
stream_ui_updates.update_stream_subscription_type_text(sub);
|
||||||
stream_ui_updates.update_change_stream_privacy_settings(sub);
|
stream_ui_updates.update_change_stream_privacy_settings(sub);
|
||||||
stream_ui_updates.update_settings_button_for_sub(sub);
|
stream_ui_updates.update_settings_button_for_sub(sub);
|
||||||
stream_ui_updates.update_subscribers_count(sub);
|
stream_ui_updates.update_subscribers_count(sub);
|
||||||
|
@ -180,11 +180,11 @@ exports.update_stream_privacy = function (sub, values) {
|
||||||
stream_list.redraw_stream_privacy(sub);
|
stream_list.redraw_stream_privacy(sub);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.update_stream_announcement_only = function (sub, new_value) {
|
exports.update_stream_post_policy = function (sub, new_value) {
|
||||||
stream_data.update_stream_announcement_only(sub, new_value);
|
stream_data.update_stream_post_policy(sub, new_value);
|
||||||
stream_data.update_calculated_fields(sub);
|
stream_data.update_calculated_fields(sub);
|
||||||
|
|
||||||
stream_ui_updates.update_stream_privacy_type_text(sub);
|
stream_ui_updates.update_stream_subscription_type_text(sub);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.set_color = function (stream_id, color) {
|
exports.set_color = function (stream_id, color) {
|
||||||
|
@ -549,6 +549,8 @@ exports.setup_page = function (callback) {
|
||||||
max_name_length: page_params.stream_name_max_length,
|
max_name_length: page_params.stream_name_max_length,
|
||||||
max_description_length: page_params.stream_description_max_length,
|
max_description_length: page_params.stream_description_max_length,
|
||||||
is_admin: page_params.is_admin,
|
is_admin: page_params.is_admin,
|
||||||
|
stream_post_policy_values: stream_data.stream_post_policy_values,
|
||||||
|
stream_post_policy: stream_data.stream_post_policy_values.everyone.code,
|
||||||
};
|
};
|
||||||
|
|
||||||
const rendered = render_subscription_table_body(template_data);
|
const rendered = render_subscription_table_body(template_data);
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
{{t 'These settings are explained in detail in the <a target="_blank" href="/help/stream-permissions">help center</a>.'}}
|
{{t 'These settings are explained in detail in the <a target="_blank" href="/help/stream-permissions">help center</a>.'}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{> stream_types is_public=true }}
|
{{> stream_types is_public=true stream_post_policy=stream_post_policy_values.everyone.code}}
|
||||||
|
|
||||||
<div id="announce-new-stream">
|
<div id="announce-new-stream">
|
||||||
<label class="checkbox">
|
<label class="checkbox">
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<ul class="grey-box">
|
<ul class="grey-box">
|
||||||
|
<h4>{{t 'Who can access the stream?'}}</h4>
|
||||||
<li>
|
<li>
|
||||||
<label class="radio">
|
<label class="radio">
|
||||||
<input type="radio" name="privacy" value="public" {{#if is_public}}checked{{/if}} />
|
<input type="radio" name="privacy" value="public" {{#if is_public}}checked{{/if}} />
|
||||||
|
@ -18,12 +19,14 @@
|
||||||
</label>
|
</label>
|
||||||
</li>
|
</li>
|
||||||
{{#if is_admin}}
|
{{#if is_admin}}
|
||||||
<li>
|
<h4>{{t 'Who can post to the stream?'}}</h4>
|
||||||
<label class="checkbox">
|
{{#each stream_post_policy_values}}
|
||||||
<input type="checkbox" name="is-announcement-only" value="is-announcement-only" {{#if is_announcement_only}}checked{{/if}}/>
|
<li>
|
||||||
<span></span>
|
<label class="radio">
|
||||||
{{t 'Restrict posting to organization administrators' }}
|
<input type="radio" name="stream-post-policy" value="{{ this.code }}" {{#if (eq this.code ../stream_post_policy) }}checked{{/if}} />
|
||||||
</label>
|
{{ this.description }}
|
||||||
</li>
|
</label>
|
||||||
|
</li>
|
||||||
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -41,7 +41,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="subscription-type">
|
<div class="subscription-type">
|
||||||
<div class="subscription-type-text">
|
<div class="subscription-type-text">
|
||||||
{{> subscription_type}}
|
{{> subscription_type
|
||||||
|
stream_post_policy_values=../stream_post_policy_values}}
|
||||||
</div>
|
</div>
|
||||||
<a class="change-stream-privacy" {{#unless can_change_stream_permissions}}style="display: none;"{{/unless}}>[{{t "Change" }}]</a>
|
<a class="change-stream-privacy" {{#unless can_change_stream_permissions}}style="display: none;"{{/unless}}>[{{t "Change" }}]</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -8,8 +8,10 @@
|
||||||
{{else}}
|
{{else}}
|
||||||
{{t 'This is a <i class="hash" aria-hidden="true"></i> <b>public stream</b>. Any member of the organization can join without an invitation.' }}
|
{{t 'This is a <i class="hash" aria-hidden="true"></i> <b>public stream</b>. Any member of the organization can join without an invitation.' }}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if is_announcement_only}}
|
{{#if (eq stream_post_policy stream_post_policy_values.admins.code)}}
|
||||||
{{t 'Only organization administrators can post.'}}
|
{{t 'Only organization administrators can post.'}}
|
||||||
|
{{else if (eq stream_post_policy stream_post_policy_values.non_new_members.code)}}
|
||||||
|
{{t 'New members cannot post.'}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{t 'All stream members can post.'}}
|
{{t 'All stream members can post.'}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -7,7 +7,7 @@ including:
|
||||||
* Stream [name](/help/rename-a-stream) and [description](/help/change-the-stream-description)
|
* Stream [name](/help/rename-a-stream) and [description](/help/change-the-stream-description)
|
||||||
* Stream [permissions](/help/stream-permissions), including
|
* Stream [permissions](/help/stream-permissions), including
|
||||||
[privacy](/help/change-the-privacy-of-a-stream) and [who can
|
[privacy](/help/change-the-privacy-of-a-stream) and [who can
|
||||||
send](/help/announcement-only-streams).
|
send](/help/stream-sending-policy).
|
||||||
|
|
||||||
`PATCH {{ api_url }}/v1/streams/{stream_id}`
|
`PATCH {{ api_url }}/v1/streams/{stream_id}`
|
||||||
|
|
||||||
|
|
|
@ -157,4 +157,4 @@
|
||||||
* [Change a stream's description](/help/change-the-stream-description)
|
* [Change a stream's description](/help/change-the-stream-description)
|
||||||
* [Change the privacy of a stream](/help/change-the-privacy-of-a-stream)
|
* [Change the privacy of a stream](/help/change-the-privacy-of-a-stream)
|
||||||
* [Add or remove users from a stream](/help/add-or-remove-users-from-a-stream)
|
* [Add or remove users from a stream](/help/add-or-remove-users-from-a-stream)
|
||||||
* [Announcement-only streams](/help/announcement-only-streams)
|
* [Stream posting policy](/help/stream-sending-policy)
|
||||||
|
|
|
@ -17,9 +17,9 @@ Zulip has many features designed to simplify moderation:
|
||||||
* Link to a code of conduct in your
|
* Link to a code of conduct in your
|
||||||
[organization description](/help/create-your-organization-profile)
|
[organization description](/help/create-your-organization-profile)
|
||||||
(displayed on the registration page).
|
(displayed on the registration page).
|
||||||
* Create at least one
|
* Create a [default stream](/help/set-default-streams-for-new-users)
|
||||||
[default stream](/help/set-default-streams-for-new-users) where
|
for announcements where [only admins can
|
||||||
[only admins can post](/help/announcement-only-streams).
|
post](/help/stream-sending-policy).
|
||||||
* Add a [waiting period](/help/restrict-permissions-of-new-members) before
|
* Add a [waiting period](/help/restrict-permissions-of-new-members) before
|
||||||
new users can take disruptive actions.
|
new users can take disruptive actions.
|
||||||
* [Restrict email visibility](/help/restrict-visibility-of-email-addresses)
|
* [Restrict email visibility](/help/restrict-visibility-of-email-addresses)
|
||||||
|
|
|
@ -16,6 +16,7 @@ Currently, the following actions support limiting access to full members.
|
||||||
|
|
||||||
- [Creating streams](/help/configure-who-can-create-streams)
|
- [Creating streams](/help/configure-who-can-create-streams)
|
||||||
- [Adding users to streams](/help/configure-who-can-invite-to-streams)
|
- [Adding users to streams](/help/configure-who-can-invite-to-streams)
|
||||||
|
- [Restricting posting to a stream](/help/stream-sending-policy)
|
||||||
|
|
||||||
### Set waiting period for new members
|
### Set waiting period for new members
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
Streams are similar to chatrooms, IRC channels, or email lists in that they
|
Streams are similar to chatrooms, IRC channels, or email lists in that they
|
||||||
determine who receives a message. There are three types of streams in Zulip.
|
determine who receives a message. There are three types of streams in Zulip.
|
||||||
|
|
||||||
* **Public**: Anyone other than guests can join, and anyone (other than guests) can view the complete message
|
* **Public**: Anyone other than guests can join, and anyone (other
|
||||||
history without joining.
|
than guests) can view the complete message history without joining.
|
||||||
|
|
||||||
* **Private, shared history**: You must be added by a member of the stream. The
|
* **Private, shared history**: You must be added by a member of the stream. The
|
||||||
complete message history is available as soon as you are added.
|
complete message history is available as soon as you are added.
|
||||||
|
@ -62,11 +62,9 @@ private stream messages:
|
||||||
|
|
||||||
◾ If subscribed to the stream
|
◾ If subscribed to the stream
|
||||||
|
|
||||||
✶ Configurable. Org admins and Members can, by default, post to
|
✶ [Configurable](/help/stream-sending-policy). Org admins and
|
||||||
any public stream, and Guests can only post to public streams if they
|
Members can, by default, post to any public stream, and Guests can
|
||||||
are subscribed. Additionally, streams can be configured to only allow
|
only post to public streams if they are subscribed.
|
||||||
administrators to post.
|
|
||||||
|
|
||||||
|
|
||||||
### Private streams
|
### Private streams
|
||||||
|
|
||||||
|
@ -90,4 +88,5 @@ administrators to post.
|
||||||
|
|
||||||
◾ If subscribed to the stream
|
◾ If subscribed to the stream
|
||||||
|
|
||||||
✶ Configurable, but at minimum must be subscribed to the stream
|
✶ [Configurable](/help/stream-sending-policy), but at minimum
|
||||||
|
must be subscribed to the stream.
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
# Announcement-only streams
|
# Stream posting policy
|
||||||
|
|
||||||
{!admin-only.md!}
|
{!admin-only.md!}
|
||||||
|
|
||||||
By default, anyone who belongs to a stream can also send messages to the
|
By default, anyone who belongs to a stream can also send messages to
|
||||||
stream. However, sometimes it's useful to have a stream (often a
|
the stream. However, sometimes it's useful to have a stream (often a
|
||||||
[default stream](/help/set-default-streams-for-new-users)) where only
|
[default stream](/help/set-default-streams-for-new-users)) where only
|
||||||
organization administrators can send messages.
|
certain users can send messages.
|
||||||
|
|
||||||
### Restrict posting to organization administrators
|
|
||||||
|
|
||||||
{start_tabs}
|
{start_tabs}
|
||||||
|
|
||||||
|
@ -18,7 +16,7 @@ organization administrators can send messages.
|
||||||
1. On the right, click **[Change]** next to the description of the stream
|
1. On the right, click **[Change]** next to the description of the stream
|
||||||
permissions.
|
permissions.
|
||||||
|
|
||||||
1. Click **Restrict posting to organization administrators**.
|
1. Under "Who can post to the stream?", select the option you prefer.
|
||||||
|
|
||||||
1. Click **Save Changes**.
|
1. Click **Save Changes**.
|
||||||
|
|
|
@ -1824,7 +1824,7 @@ def create_stream_if_needed(realm: Realm,
|
||||||
stream_name: str,
|
stream_name: str,
|
||||||
*,
|
*,
|
||||||
invite_only: bool=False,
|
invite_only: bool=False,
|
||||||
is_announcement_only: bool=False,
|
stream_post_policy: int=Stream.STREAM_POST_POLICY_EVERYONE,
|
||||||
history_public_to_subscribers: Optional[bool]=None,
|
history_public_to_subscribers: Optional[bool]=None,
|
||||||
stream_description: str="") -> Tuple[Stream, bool]:
|
stream_description: str="") -> Tuple[Stream, bool]:
|
||||||
|
|
||||||
|
@ -1838,7 +1838,7 @@ def create_stream_if_needed(realm: Realm,
|
||||||
name=stream_name,
|
name=stream_name,
|
||||||
description=stream_description,
|
description=stream_description,
|
||||||
invite_only=invite_only,
|
invite_only=invite_only,
|
||||||
is_announcement_only=is_announcement_only,
|
stream_post_policy=stream_post_policy,
|
||||||
history_public_to_subscribers=history_public_to_subscribers,
|
history_public_to_subscribers=history_public_to_subscribers,
|
||||||
is_in_zephyr_realm=realm.is_zephyr_mirror_realm
|
is_in_zephyr_realm=realm.is_zephyr_mirror_realm
|
||||||
)
|
)
|
||||||
|
@ -1878,7 +1878,7 @@ def create_streams_if_needed(realm: Realm,
|
||||||
realm,
|
realm,
|
||||||
stream_dict["name"],
|
stream_dict["name"],
|
||||||
invite_only=stream_dict.get("invite_only", False),
|
invite_only=stream_dict.get("invite_only", False),
|
||||||
is_announcement_only=stream_dict.get("is_announcement_only", False),
|
stream_post_policy=stream_dict.get("stream_post_policy", Stream.STREAM_POST_POLICY_EVERYONE),
|
||||||
history_public_to_subscribers=stream_dict.get("history_public_to_subscribers"),
|
history_public_to_subscribers=stream_dict.get("history_public_to_subscribers"),
|
||||||
stream_description=stream_dict.get("description", "")
|
stream_description=stream_dict.get("description", "")
|
||||||
)
|
)
|
||||||
|
@ -2243,14 +2243,20 @@ def validate_sender_can_write_to_stream(sender: UserProfile,
|
||||||
# Our caller is responsible for making sure that `stream` actually
|
# Our caller is responsible for making sure that `stream` actually
|
||||||
# matches the realm of the sender.
|
# matches the realm of the sender.
|
||||||
|
|
||||||
if stream.is_announcement_only:
|
# Organization admins can send to any stream, irrespective of the stream_post_policy value.
|
||||||
if sender.is_realm_admin or is_cross_realm_bot_email(sender.delivery_email):
|
if sender.is_realm_admin or is_cross_realm_bot_email(sender.delivery_email):
|
||||||
pass
|
pass
|
||||||
elif sender.is_bot and (sender.bot_owner is not None and
|
elif sender.is_bot and (sender.bot_owner is not None and
|
||||||
sender.bot_owner.is_realm_admin):
|
sender.bot_owner.is_realm_admin):
|
||||||
pass
|
pass
|
||||||
else:
|
elif stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS:
|
||||||
raise JsonableError(_("Only organization administrators can send to this stream."))
|
raise JsonableError(_("Only organization administrators can send to this stream."))
|
||||||
|
elif stream.stream_post_policy == Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS:
|
||||||
|
if sender.is_bot and (sender.bot_owner is not None and
|
||||||
|
sender.bot_owner.is_new_member):
|
||||||
|
raise JsonableError(_("New members cannot send to this stream."))
|
||||||
|
elif sender.is_new_member:
|
||||||
|
raise JsonableError(_("New members cannot send to this stream."))
|
||||||
|
|
||||||
if not (stream.invite_only or sender.is_guest):
|
if not (stream.invite_only or sender.is_guest):
|
||||||
# This is a public stream and sender is not a guest user
|
# This is a public stream and sender is not a guest user
|
||||||
|
@ -3632,14 +3638,28 @@ def do_change_stream_web_public(stream: Stream, is_web_public: bool) -> None:
|
||||||
stream.is_web_public = is_web_public
|
stream.is_web_public = is_web_public
|
||||||
stream.save(update_fields=['is_web_public'])
|
stream.save(update_fields=['is_web_public'])
|
||||||
|
|
||||||
def do_change_stream_announcement_only(stream: Stream, is_announcement_only: bool) -> None:
|
def do_change_stream_post_policy(stream: Stream, stream_post_policy: int) -> None:
|
||||||
stream.is_announcement_only = is_announcement_only
|
stream.stream_post_policy = stream_post_policy
|
||||||
stream.save(update_fields=['is_announcement_only'])
|
stream.save(update_fields=['stream_post_policy'])
|
||||||
|
event = dict(
|
||||||
|
op="update",
|
||||||
|
type="stream",
|
||||||
|
property="stream_post_policy",
|
||||||
|
value=stream_post_policy,
|
||||||
|
stream_id=stream.id,
|
||||||
|
name=stream.name,
|
||||||
|
)
|
||||||
|
send_event(stream.realm, event, can_access_stream_user_ids(stream))
|
||||||
|
|
||||||
|
# Backwards-compatibility code: We removed the
|
||||||
|
# is_announcement_only property in early 2020, but we send a
|
||||||
|
# duplicate event for legacy mobile clients that might want the
|
||||||
|
# data.
|
||||||
event = dict(
|
event = dict(
|
||||||
op="update",
|
op="update",
|
||||||
type="stream",
|
type="stream",
|
||||||
property="is_announcement_only",
|
property="is_announcement_only",
|
||||||
value=is_announcement_only,
|
value=stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS,
|
||||||
stream_id=stream.id,
|
stream_id=stream.id,
|
||||||
name=stream.name,
|
name=stream.name,
|
||||||
)
|
)
|
||||||
|
@ -4818,6 +4838,11 @@ def gather_subscriptions_helper(user_profile: UserProfile,
|
||||||
# Backwards-compatibility for clients that haven't been
|
# Backwards-compatibility for clients that haven't been
|
||||||
# updated for the in_home_view => is_muted API migration.
|
# updated for the in_home_view => is_muted API migration.
|
||||||
stream_dict['in_home_view'] = not stream_dict['is_muted']
|
stream_dict['in_home_view'] = not stream_dict['is_muted']
|
||||||
|
# Backwards-compatibility for clients that haven't been
|
||||||
|
# updated for the is_announcement_only -> stream_post_policy
|
||||||
|
# migration.
|
||||||
|
stream_dict['is_announcement_only'] = \
|
||||||
|
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS
|
||||||
|
|
||||||
# Add a few computed fields not directly from the data models.
|
# Add a few computed fields not directly from the data models.
|
||||||
stream_dict['is_old_stream'] = is_old_stream(stream["date_created"])
|
stream_dict['is_old_stream'] = is_old_stream(stream["date_created"])
|
||||||
|
@ -4866,6 +4891,9 @@ def gather_subscriptions_helper(user_profile: UserProfile,
|
||||||
stream_dict['is_old_stream'] = is_old_stream(stream["date_created"])
|
stream_dict['is_old_stream'] = is_old_stream(stream["date_created"])
|
||||||
stream_dict['stream_weekly_traffic'] = get_average_weekly_stream_traffic(
|
stream_dict['stream_weekly_traffic'] = get_average_weekly_stream_traffic(
|
||||||
stream["id"], stream["date_created"], recent_traffic)
|
stream["id"], stream["date_created"], recent_traffic)
|
||||||
|
# Backwards-compatibility addition of removed field.
|
||||||
|
stream_dict['is_announcement_only'] = \
|
||||||
|
stream['stream_post_policy'] == Stream.STREAM_POST_POLICY_ADMINS
|
||||||
|
|
||||||
if is_public or user_profile.is_realm_admin:
|
if is_public or user_profile.is_realm_admin:
|
||||||
subscribers = subscriber_map[stream["id"]]
|
subscribers = subscriber_map[stream["id"]]
|
||||||
|
|
|
@ -113,7 +113,8 @@ def bulk_create_streams(realm: Realm,
|
||||||
description=options["description"],
|
description=options["description"],
|
||||||
rendered_description=render_stream_description(options["description"]),
|
rendered_description=render_stream_description(options["description"]),
|
||||||
invite_only=options.get("invite_only", False),
|
invite_only=options.get("invite_only", False),
|
||||||
is_announcement_only=options.get("is_announcement_only", False),
|
stream_post_policy=options.get("stream_post_policy",
|
||||||
|
Stream.STREAM_POST_POLICY_EVERYONE),
|
||||||
history_public_to_subscribers=options["history_public_to_subscribers"],
|
history_public_to_subscribers=options["history_public_to_subscribers"],
|
||||||
is_web_public=options.get("is_web_public", False),
|
is_web_public=options.get("is_web_public", False),
|
||||||
is_in_zephyr_realm=realm.is_zephyr_mirror_realm,
|
is_in_zephyr_realm=realm.is_zephyr_mirror_realm,
|
||||||
|
|
|
@ -477,7 +477,7 @@ def apply_event(state: Dict[str, Any],
|
||||||
stream_data['subscribers'] = []
|
stream_data['subscribers'] = []
|
||||||
stream_data['stream_weekly_traffic'] = None
|
stream_data['stream_weekly_traffic'] = None
|
||||||
stream_data['is_old_stream'] = False
|
stream_data['is_old_stream'] = False
|
||||||
stream_data['is_announcement_only'] = False
|
stream_data['stream_post_policy'] = Stream.STREAM_POST_POLICY_EVERYONE
|
||||||
# Add stream to never_subscribed (if not invite_only)
|
# Add stream to never_subscribed (if not invite_only)
|
||||||
state['never_subscribed'].append(stream_data)
|
state['never_subscribed'].append(stream_data)
|
||||||
state['streams'].append(stream)
|
state['streams'].append(stream)
|
||||||
|
|
|
@ -261,8 +261,16 @@ def list_to_streams(streams_raw: Iterable[Mapping[str, Any]],
|
||||||
stream_name = stream_dict["name"]
|
stream_name = stream_dict["name"]
|
||||||
stream = existing_stream_map.get(stream_name.lower())
|
stream = existing_stream_map.get(stream_name.lower())
|
||||||
if stream is None:
|
if stream is None:
|
||||||
if stream_dict.get("is_announcement_only", False) and not user_profile.is_realm_admin:
|
# Non admins cannot create STREAM_POST_POLICY_ADMINS streams.
|
||||||
|
if ((stream_dict.get("stream_post_policy", False) ==
|
||||||
|
Stream.STREAM_POST_POLICY_ADMINS) and not user_profile.is_realm_admin):
|
||||||
member_creating_announcement_only_stream = True
|
member_creating_announcement_only_stream = True
|
||||||
|
# New members cannot create STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
|
||||||
|
# unless they are admins who are also new members of the organization.
|
||||||
|
if ((stream_dict.get("stream_post_policy", False) ==
|
||||||
|
Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS) and user_profile.is_new_member):
|
||||||
|
if not user_profile.is_realm_admin:
|
||||||
|
member_creating_announcement_only_stream = True
|
||||||
missing_stream_dicts.append(stream_dict)
|
missing_stream_dicts.append(stream_dict)
|
||||||
else:
|
else:
|
||||||
existing_streams.append(stream)
|
existing_streams.append(stream)
|
||||||
|
|
|
@ -292,12 +292,12 @@ class ZulipTestCase(TestCase):
|
||||||
def notification_bot(self) -> UserProfile:
|
def notification_bot(self) -> UserProfile:
|
||||||
return get_system_bot(settings.NOTIFICATION_BOT)
|
return get_system_bot(settings.NOTIFICATION_BOT)
|
||||||
|
|
||||||
def create_test_bot(self, short_name: str, user_profile: UserProfile,
|
def create_test_bot(self, short_name: str, user_profile: UserProfile, full_name: str='Foo Bot',
|
||||||
assert_json_error_msg: str=None, **extras: Any) -> Optional[UserProfile]:
|
assert_json_error_msg: str=None, **extras: Any) -> Optional[UserProfile]:
|
||||||
self.login(user_profile.delivery_email)
|
self.login(user_profile.delivery_email)
|
||||||
bot_info = {
|
bot_info = {
|
||||||
'short_name': short_name,
|
'short_name': short_name,
|
||||||
'full_name': 'Foo Bot',
|
'full_name': full_name,
|
||||||
}
|
}
|
||||||
bot_info.update(extras)
|
bot_info.update(extras)
|
||||||
result = self.client_post("/json/bots", bot_info)
|
result = self.client_post("/json/bots", bot_info)
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.26 on 2020-01-27 22:03
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0262_mutedtopic_date_muted'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='stream',
|
||||||
|
name='stream_post_policy',
|
||||||
|
field=models.PositiveSmallIntegerField(default=1),
|
||||||
|
),
|
||||||
|
]
|
|
@ -0,0 +1,29 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.26 on 2020-01-25 23:47
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.backends.postgresql_psycopg2.schema import DatabaseSchemaEditor
|
||||||
|
from django.db.migrations.state import StateApps
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_stream_post_policy(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
|
||||||
|
STREAM_POST_POLICY_EVERYONE = 1
|
||||||
|
STREAM_POST_POLICY_ADMINS = 2
|
||||||
|
|
||||||
|
Stream = apps.get_model('zerver', 'Stream')
|
||||||
|
Stream.objects.filter(is_announcement_only=False) \
|
||||||
|
.update(stream_post_policy=STREAM_POST_POLICY_EVERYONE)
|
||||||
|
Stream.objects.filter(is_announcement_only=True) \
|
||||||
|
.update(stream_post_policy=STREAM_POST_POLICY_ADMINS)
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0263_stream_stream_post_policy'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(upgrade_stream_post_policy,
|
||||||
|
reverse_code=migrations.RunPython.noop),
|
||||||
|
]
|
|
@ -0,0 +1,19 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.26 on 2020-01-27 22:05
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('zerver', '0264_migrate_is_announcement_only'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='stream',
|
||||||
|
name='is_announcement_only',
|
||||||
|
),
|
||||||
|
]
|
|
@ -1373,8 +1373,18 @@ class Stream(models.Model):
|
||||||
# Whether this stream's content should be published by the web-public archive features
|
# Whether this stream's content should be published by the web-public archive features
|
||||||
is_web_public = models.BooleanField(default=False) # type: bool
|
is_web_public = models.BooleanField(default=False) # type: bool
|
||||||
|
|
||||||
# Whether only organization administrators can send messages to this stream
|
STREAM_POST_POLICY_EVERYONE = 1
|
||||||
is_announcement_only = models.BooleanField(default=False) # type: bool
|
STREAM_POST_POLICY_ADMINS = 2
|
||||||
|
STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS = 3
|
||||||
|
# TODO: Implement policy to restrict posting to a user group or admins.
|
||||||
|
|
||||||
|
# Who in the organization has permission to send messages to this stream.
|
||||||
|
stream_post_policy = models.PositiveSmallIntegerField(default=STREAM_POST_POLICY_EVERYONE) # type: int
|
||||||
|
STREAM_POST_POLICY_TYPES = [
|
||||||
|
STREAM_POST_POLICY_EVERYONE,
|
||||||
|
STREAM_POST_POLICY_ADMINS,
|
||||||
|
STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS,
|
||||||
|
]
|
||||||
|
|
||||||
# The unique thing about Zephyr public streams is that we never list their
|
# The unique thing about Zephyr public streams is that we never list their
|
||||||
# users. We may try to generalize this concept later, but for now
|
# users. We may try to generalize this concept later, but for now
|
||||||
|
@ -1434,7 +1444,7 @@ class Stream(models.Model):
|
||||||
"rendered_description",
|
"rendered_description",
|
||||||
"invite_only",
|
"invite_only",
|
||||||
"is_web_public",
|
"is_web_public",
|
||||||
"is_announcement_only",
|
"stream_post_policy",
|
||||||
"history_public_to_subscribers",
|
"history_public_to_subscribers",
|
||||||
"first_message_id",
|
"first_message_id",
|
||||||
]
|
]
|
||||||
|
@ -1447,6 +1457,7 @@ class Stream(models.Model):
|
||||||
result['stream_id'] = self.id
|
result['stream_id'] = self.id
|
||||||
continue
|
continue
|
||||||
result[field_name] = getattr(self, field_name)
|
result[field_name] = getattr(self, field_name)
|
||||||
|
result['is_announcement_only'] = self.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS
|
||||||
return result
|
return result
|
||||||
|
|
||||||
post_save.connect(flush_stream, sender=Stream)
|
post_save.connect(flush_stream, sender=Stream)
|
||||||
|
|
|
@ -328,7 +328,7 @@ def update_stream(client, stream_id):
|
||||||
# Update the stream by a given ID
|
# Update the stream by a given ID
|
||||||
request = {
|
request = {
|
||||||
'stream_id': stream_id,
|
'stream_id': stream_id,
|
||||||
'is_announcement_only': True,
|
'stream_post_policy': 2,
|
||||||
'is_private': True,
|
'is_private': True,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1504,14 +1504,13 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: None
|
default: None
|
||||||
example: false
|
example: false
|
||||||
- name: is_announcement_only
|
- name: stream_post_policy
|
||||||
in: query
|
in: query
|
||||||
description: A boolean indicating if the stream is an announcements only stream.
|
description: Indicates which users can post messages to the stream.
|
||||||
Only organization admins can post to announcements only streams.
|
|
||||||
schema:
|
schema:
|
||||||
type: boolean
|
type: integer
|
||||||
default: false
|
default: 1
|
||||||
example: false
|
example: 1
|
||||||
- name: announce
|
- name: announce
|
||||||
in: query
|
in: query
|
||||||
description: If `announce` is `True` and one of the streams specified
|
description: If `announce` is `True` and one of the streams specified
|
||||||
|
@ -2624,6 +2623,13 @@ paths:
|
||||||
type: boolean
|
type: boolean
|
||||||
example: true
|
example: true
|
||||||
required: false
|
required: false
|
||||||
|
- name: stream_post_policy
|
||||||
|
in: query
|
||||||
|
description: Indicates which users can post messages to the stream.
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
example: 2
|
||||||
|
required: false
|
||||||
- name: history_public_to_subscribers
|
- name: history_public_to_subscribers
|
||||||
in: query
|
in: query
|
||||||
description: The new state for the history_public_to_subscribers.
|
description: The new state for the history_public_to_subscribers.
|
||||||
|
|
|
@ -52,7 +52,7 @@ from zerver.lib.actions import (
|
||||||
do_change_realm_domain,
|
do_change_realm_domain,
|
||||||
do_change_stream_description,
|
do_change_stream_description,
|
||||||
do_change_stream_invite_only,
|
do_change_stream_invite_only,
|
||||||
do_change_stream_announcement_only,
|
do_change_stream_post_policy,
|
||||||
do_change_subscription_property,
|
do_change_subscription_property,
|
||||||
do_change_user_delivery_email,
|
do_change_user_delivery_email,
|
||||||
do_create_user,
|
do_create_user,
|
||||||
|
@ -134,7 +134,7 @@ from zerver.lib.topic_mutes import (
|
||||||
)
|
)
|
||||||
from zerver.lib.validator import (
|
from zerver.lib.validator import (
|
||||||
check_bool, check_dict, check_dict_only, check_float, check_int, check_list, check_string,
|
check_bool, check_dict, check_dict_only, check_float, check_int, check_list, check_string,
|
||||||
equals, check_none_or, Validator, check_url
|
equals, check_none_or, Validator, check_url, check_int_in
|
||||||
)
|
)
|
||||||
from zerver.lib.users import get_api_key
|
from zerver.lib.users import get_api_key
|
||||||
|
|
||||||
|
@ -1438,6 +1438,7 @@ class EventsRegisterTest(ZulipTestCase):
|
||||||
('invite_only', check_bool),
|
('invite_only', check_bool),
|
||||||
('is_web_public', check_bool),
|
('is_web_public', check_bool),
|
||||||
('is_announcement_only', check_bool),
|
('is_announcement_only', check_bool),
|
||||||
|
('stream_post_policy', check_int_in(Stream.STREAM_POST_POLICY_TYPES)),
|
||||||
('name', check_string),
|
('name', check_string),
|
||||||
('stream_id', check_int),
|
('stream_id', check_int),
|
||||||
('first_message_id', check_none_or(check_int)),
|
('first_message_id', check_none_or(check_int)),
|
||||||
|
@ -2508,6 +2509,7 @@ class EventsRegisterTest(ZulipTestCase):
|
||||||
('invite_only', check_bool),
|
('invite_only', check_bool),
|
||||||
('is_web_public', check_bool),
|
('is_web_public', check_bool),
|
||||||
('is_announcement_only', check_bool),
|
('is_announcement_only', check_bool),
|
||||||
|
('stream_post_policy', check_int_in(Stream.STREAM_POST_POLICY_TYPES)),
|
||||||
('is_muted', check_bool),
|
('is_muted', check_bool),
|
||||||
('in_home_view', check_bool),
|
('in_home_view', check_bool),
|
||||||
('name', check_string),
|
('name', check_string),
|
||||||
|
@ -2584,13 +2586,13 @@ class EventsRegisterTest(ZulipTestCase):
|
||||||
('value', check_bool),
|
('value', check_bool),
|
||||||
('history_public_to_subscribers', check_bool),
|
('history_public_to_subscribers', check_bool),
|
||||||
])
|
])
|
||||||
stream_update_is_announcement_only_schema_checker = self.check_events_dict([
|
stream_update_stream_post_policy_schema_checker = self.check_events_dict([
|
||||||
('type', equals('stream')),
|
('type', equals('stream')),
|
||||||
('op', equals('update')),
|
('op', equals('update')),
|
||||||
('property', equals('is_announcement_only')),
|
('property', equals('stream_post_policy')),
|
||||||
('stream_id', check_int),
|
('stream_id', check_int),
|
||||||
('name', check_string),
|
('name', check_string),
|
||||||
('value', check_bool),
|
('value', check_int_in(Stream.STREAM_POST_POLICY_TYPES)),
|
||||||
])
|
])
|
||||||
|
|
||||||
# Subscribe to a totally new stream, so it's just Hamlet on it
|
# Subscribe to a totally new stream, so it's just Hamlet on it
|
||||||
|
@ -2655,11 +2657,11 @@ class EventsRegisterTest(ZulipTestCase):
|
||||||
error = stream_update_invite_only_schema_checker('events[0]', events[0])
|
error = stream_update_invite_only_schema_checker('events[0]', events[0])
|
||||||
self.assert_on_error(error)
|
self.assert_on_error(error)
|
||||||
|
|
||||||
# Update stream is_announcement_only property
|
# Update stream stream_post_policy property
|
||||||
action = lambda: do_change_stream_announcement_only(stream, True)
|
action = lambda: do_change_stream_post_policy(stream, Stream.STREAM_POST_POLICY_ADMINS)
|
||||||
events = self.do_test(action,
|
events = self.do_test(action,
|
||||||
include_subscribers=include_subscribers)
|
include_subscribers=include_subscribers, num_events=2)
|
||||||
error = stream_update_is_announcement_only_schema_checker('events[0]', events[0])
|
error = stream_update_stream_post_policy_schema_checker('events[0]', events[0])
|
||||||
self.assert_on_error(error)
|
self.assert_on_error(error)
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -1424,6 +1424,15 @@ class SewMessageAndReactionTest(ZulipTestCase):
|
||||||
|
|
||||||
class MessagePOSTTest(ZulipTestCase):
|
class MessagePOSTTest(ZulipTestCase):
|
||||||
|
|
||||||
|
def _send_and_verify_message(self, email: str, stream_name: str, error_msg: str=None) -> None:
|
||||||
|
if error_msg is None:
|
||||||
|
msg_id = self.send_stream_message(email, stream_name)
|
||||||
|
result = self.client_get('/json/messages/' + str(msg_id))
|
||||||
|
self.assert_json_success(result)
|
||||||
|
else:
|
||||||
|
with self.assertRaisesRegex(JsonableError, error_msg):
|
||||||
|
self.send_stream_message(email, stream_name)
|
||||||
|
|
||||||
def test_message_to_self(self) -> None:
|
def test_message_to_self(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending a message to a stream to which you are subscribed is
|
Sending a message to a stream to which you are subscribed is
|
||||||
|
@ -1489,36 +1498,52 @@ class MessagePOSTTest(ZulipTestCase):
|
||||||
sent_message = self.get_last_message()
|
sent_message = self.get_last_message()
|
||||||
self.assertEqual(sent_message.content, "Stream message by ID.")
|
self.assertEqual(sent_message.content, "Stream message by ID.")
|
||||||
|
|
||||||
def test_message_to_announce(self) -> None:
|
def test_sending_message_as_stream_post_policy_admins(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending a message to an announcement_only stream by a realm admin
|
Sending messages to streams which only the admins can create and post to.
|
||||||
successful.
|
|
||||||
"""
|
"""
|
||||||
user_profile = self.example_user("iago")
|
admin_profile = self.example_user("iago")
|
||||||
self.login(user_profile.email)
|
self.login(admin_profile.email)
|
||||||
|
|
||||||
stream_name = "Verona"
|
stream_name = "Verona"
|
||||||
stream = get_stream(stream_name, user_profile.realm)
|
stream = get_stream(stream_name, admin_profile.realm)
|
||||||
stream.is_announcement_only = True
|
stream.stream_post_policy = Stream.STREAM_POST_POLICY_ADMINS
|
||||||
stream.save()
|
stream.save()
|
||||||
result = self.client_post("/json/messages", {"type": "stream",
|
|
||||||
"to": stream_name,
|
|
||||||
"client": "test suite",
|
|
||||||
"content": "Test message",
|
|
||||||
"topic": "Test topic"})
|
|
||||||
self.assert_json_success(result)
|
|
||||||
|
|
||||||
|
# Admins and their owned bots can send to STREAM_POST_POLICY_ADMINS streams
|
||||||
|
self._send_and_verify_message(admin_profile.email, stream_name)
|
||||||
admin_owned_bot = self.create_test_bot(
|
admin_owned_bot = self.create_test_bot(
|
||||||
short_name='whatever',
|
short_name='whatever1',
|
||||||
user_profile=user_profile,
|
full_name='whatever1',
|
||||||
|
user_profile=admin_profile,
|
||||||
)
|
)
|
||||||
result = self.api_post(admin_owned_bot.email,
|
self._send_and_verify_message(admin_owned_bot.email, stream_name)
|
||||||
"/api/v1/messages", {"type": "stream",
|
|
||||||
"to": stream_name,
|
non_admin_profile = self.example_user("hamlet")
|
||||||
"client": "test suite",
|
self.login(non_admin_profile.email)
|
||||||
"content": "Test message",
|
|
||||||
"topic": "Test topic"})
|
# Non admins and their owned bots cannot send to STREAM_POST_POLICY_ADMINS streams
|
||||||
self.assert_json_success(result)
|
self._send_and_verify_message(non_admin_profile.email, stream_name,
|
||||||
|
"Only organization administrators can send to this stream.")
|
||||||
|
non_admin_owned_bot = self.create_test_bot(
|
||||||
|
short_name='whatever2',
|
||||||
|
full_name='whatever2',
|
||||||
|
user_profile=non_admin_profile,
|
||||||
|
)
|
||||||
|
self._send_and_verify_message(non_admin_owned_bot.email, stream_name,
|
||||||
|
"Only organization administrators can send to this stream.")
|
||||||
|
|
||||||
|
# Bots without owner (except cross realm bot) cannot send to announcement only streams
|
||||||
|
bot_without_owner = do_create_user(
|
||||||
|
email='free-bot@zulip.testserver',
|
||||||
|
password='',
|
||||||
|
realm=non_admin_profile.realm,
|
||||||
|
full_name='freebot',
|
||||||
|
short_name='freebot',
|
||||||
|
bot_type=UserProfile.DEFAULT_BOT,
|
||||||
|
)
|
||||||
|
self._send_and_verify_message(bot_without_owner.email, stream_name,
|
||||||
|
"Only organization administrators can send to this stream.")
|
||||||
|
|
||||||
# Cross realm bots should be allowed (through internal_send_message)
|
# Cross realm bots should be allowed (through internal_send_message)
|
||||||
notification_bot = get_system_bot("notification-bot@zulip.com")
|
notification_bot = get_system_bot("notification-bot@zulip.com")
|
||||||
|
@ -1526,54 +1551,71 @@ class MessagePOSTTest(ZulipTestCase):
|
||||||
'Test topic', 'Test message by notification bot')
|
'Test topic', 'Test message by notification bot')
|
||||||
self.assertEqual(self.get_last_message().content, 'Test message by notification bot')
|
self.assertEqual(self.get_last_message().content, 'Test message by notification bot')
|
||||||
|
|
||||||
def test_message_fail_to_announce(self) -> None:
|
def test_sending_message_as_stream_post_policy_restrict_new_members(self) -> None:
|
||||||
"""
|
"""
|
||||||
Sending a message to an announcement_only stream not by a realm
|
Sending messages to streams which new members cannot create and post to.
|
||||||
admin fails.
|
|
||||||
"""
|
"""
|
||||||
user_profile = self.example_user("hamlet")
|
admin_profile = self.example_user("iago")
|
||||||
self.login(user_profile.email)
|
self.login(admin_profile.email)
|
||||||
|
|
||||||
|
do_set_realm_property(admin_profile.realm, 'waiting_period_threshold', 10)
|
||||||
|
admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
|
||||||
|
admin_profile.save()
|
||||||
|
self.assertTrue(admin_profile.is_new_member)
|
||||||
|
self.assertTrue(admin_profile.is_realm_admin)
|
||||||
|
|
||||||
stream_name = "Verona"
|
stream_name = "Verona"
|
||||||
stream = get_stream(stream_name, user_profile.realm)
|
stream = get_stream(stream_name, admin_profile.realm)
|
||||||
stream.is_announcement_only = True
|
stream.stream_post_policy = Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS
|
||||||
stream.save()
|
stream.save()
|
||||||
result = self.client_post("/json/messages", {"type": "stream",
|
|
||||||
"to": stream_name,
|
|
||||||
"client": "test suite",
|
|
||||||
"content": "Test message",
|
|
||||||
"topic": "Test topic"})
|
|
||||||
self.assert_json_error(result, "Only organization administrators can send to this stream.")
|
|
||||||
|
|
||||||
# Non admin owned bot fail to send to announcement only stream
|
# Admins and their owned bots can send to STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
|
||||||
non_admin_owned_bot = self.create_test_bot(
|
# even if the admin is a new user
|
||||||
short_name='whatever',
|
self._send_and_verify_message(admin_profile.email, stream_name)
|
||||||
user_profile=user_profile,
|
admin_owned_bot = self.create_test_bot(
|
||||||
|
short_name='whatever1',
|
||||||
|
full_name='whatever1',
|
||||||
|
user_profile=admin_profile,
|
||||||
)
|
)
|
||||||
result = self.api_post(non_admin_owned_bot.email,
|
self._send_and_verify_message(admin_owned_bot.email, stream_name)
|
||||||
"/api/v1/messages", {"type": "stream",
|
|
||||||
"to": stream_name,
|
|
||||||
"client": "test suite",
|
|
||||||
"content": "Test message",
|
|
||||||
"topic": "Test topic"})
|
|
||||||
self.assert_json_error(result, "Only organization administrators can send to this stream.")
|
|
||||||
|
|
||||||
# Bots without owner (except cross realm bot) fail to send to announcement only stream
|
non_admin_profile = self.example_user("hamlet")
|
||||||
|
self.login(non_admin_profile.email)
|
||||||
|
|
||||||
|
non_admin_profile.date_joined = timezone_now() - datetime.timedelta(days=9)
|
||||||
|
non_admin_profile.save()
|
||||||
|
self.assertTrue(non_admin_profile.is_new_member)
|
||||||
|
self.assertFalse(non_admin_profile.is_realm_admin)
|
||||||
|
|
||||||
|
# Non admins and their owned bots can send to STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
|
||||||
|
# if the user is not a new member
|
||||||
|
self._send_and_verify_message(non_admin_profile.email, stream_name,
|
||||||
|
"New members cannot send to this stream.")
|
||||||
|
non_admin_owned_bot = self.create_test_bot(
|
||||||
|
short_name='whatever2',
|
||||||
|
full_name='whatever2',
|
||||||
|
user_profile=non_admin_profile,
|
||||||
|
)
|
||||||
|
self._send_and_verify_message(non_admin_owned_bot.email, stream_name,
|
||||||
|
"New members cannot send to this stream.")
|
||||||
|
|
||||||
|
# Bots without owner (except cross realm bot) cannot send to announcement only stream
|
||||||
bot_without_owner = do_create_user(
|
bot_without_owner = do_create_user(
|
||||||
email='free-bot@zulip.testserver',
|
email='free-bot@zulip.testserver',
|
||||||
password='',
|
password='',
|
||||||
realm=user_profile.realm,
|
realm=non_admin_profile.realm,
|
||||||
full_name='freebot',
|
full_name='freebot',
|
||||||
short_name='freebot',
|
short_name='freebot',
|
||||||
bot_type=UserProfile.DEFAULT_BOT,
|
bot_type=UserProfile.DEFAULT_BOT,
|
||||||
)
|
)
|
||||||
result = self.api_post(bot_without_owner.email,
|
self._send_and_verify_message(bot_without_owner.email, stream_name,
|
||||||
"/api/v1/messages", {"type": "stream",
|
"New members cannot send to this stream.")
|
||||||
"to": stream_name,
|
|
||||||
"client": "test suite",
|
# Cross realm bots should be allowed (through internal_send_message)
|
||||||
"content": "Test message",
|
notification_bot = get_system_bot("notification-bot@zulip.com")
|
||||||
"topic": "Test topic"})
|
internal_send_stream_message(stream.realm, notification_bot, stream,
|
||||||
self.assert_json_error(result, "Only organization administrators can send to this stream.")
|
'Test topic', 'Test message by notification bot')
|
||||||
|
self.assertEqual(self.get_last_message().content, 'Test message by notification bot')
|
||||||
|
|
||||||
def test_api_message_with_default_to(self) -> None:
|
def test_api_message_with_default_to(self) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -142,7 +142,7 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
[{"name": stream_name,
|
[{"name": stream_name,
|
||||||
"description": stream_description,
|
"description": stream_description,
|
||||||
"invite_only": True,
|
"invite_only": True,
|
||||||
"is_announcement_only": True}
|
"stream_post_policy": Stream.STREAM_POST_POLICY_ADMINS}
|
||||||
for (stream_name, stream_description) in zip(stream_names, stream_descriptions)])
|
for (stream_name, stream_description) in zip(stream_names, stream_descriptions)])
|
||||||
|
|
||||||
self.assertEqual(len(new_streams), 3)
|
self.assertEqual(len(new_streams), 3)
|
||||||
|
@ -154,7 +154,7 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
self.assertEqual(actual_stream_descriptions, set(stream_descriptions))
|
self.assertEqual(actual_stream_descriptions, set(stream_descriptions))
|
||||||
for stream in new_streams:
|
for stream in new_streams:
|
||||||
self.assertTrue(stream.invite_only)
|
self.assertTrue(stream.invite_only)
|
||||||
self.assertTrue(stream.is_announcement_only)
|
self.assertTrue(stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS)
|
||||||
|
|
||||||
new_streams, existing_streams = create_streams_if_needed(
|
new_streams, existing_streams = create_streams_if_needed(
|
||||||
realm,
|
realm,
|
||||||
|
@ -264,7 +264,7 @@ class TestCreateStreams(ZulipTestCase):
|
||||||
"invite_only": 'false',
|
"invite_only": 'false',
|
||||||
"announce": 'true',
|
"announce": 'true',
|
||||||
"principals": '["iago@zulip.com", "AARON@zulip.com", "cordelia@zulip.com", "hamlet@zulip.com"]',
|
"principals": '["iago@zulip.com", "AARON@zulip.com", "cordelia@zulip.com", "hamlet@zulip.com"]',
|
||||||
"is_announcement_only": 'false'
|
"stream_post_policy": '1'
|
||||||
}
|
}
|
||||||
|
|
||||||
response = self.client_post("/json/users/me/subscriptions", data)
|
response = self.client_post("/json/users/me/subscriptions", data)
|
||||||
|
@ -780,7 +780,7 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
{'description': ujson.dumps('Test description')})
|
{'description': ujson.dumps('Test description')})
|
||||||
self.assert_json_error(result, 'Must be an organization administrator')
|
self.assert_json_error(result, 'Must be an organization administrator')
|
||||||
|
|
||||||
def test_change_stream_announcement_only(self) -> None:
|
def test_change_to_stream_post_policy_admins(self) -> None:
|
||||||
user_profile = self.example_user('hamlet')
|
user_profile = self.example_user('hamlet')
|
||||||
self.login(user_profile.email)
|
self.login(user_profile.email)
|
||||||
|
|
||||||
|
@ -792,9 +792,9 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
{'is_announcement_only': ujson.dumps(True)})
|
{'is_announcement_only': ujson.dumps(True)})
|
||||||
self.assert_json_success(result)
|
self.assert_json_success(result)
|
||||||
stream = get_stream('stream_name1', user_profile.realm)
|
stream = get_stream('stream_name1', user_profile.realm)
|
||||||
self.assertEqual(True, stream.is_announcement_only)
|
self.assertTrue(stream.stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS)
|
||||||
|
|
||||||
def test_change_stream_announcement_only_requires_realm_admin(self) -> None:
|
def test_change_stream_post_policy_requires_realm_admin(self) -> None:
|
||||||
user_profile = self.example_user('hamlet')
|
user_profile = self.example_user('hamlet')
|
||||||
self.login(user_profile.email)
|
self.login(user_profile.email)
|
||||||
|
|
||||||
|
@ -803,7 +803,17 @@ class StreamAdminTest(ZulipTestCase):
|
||||||
|
|
||||||
stream_id = get_stream('stream_name1', user_profile.realm).id
|
stream_id = get_stream('stream_name1', user_profile.realm).id
|
||||||
result = self.client_patch('/json/streams/%d' % (stream_id,),
|
result = self.client_patch('/json/streams/%d' % (stream_id,),
|
||||||
{'is_announcement_only': ujson.dumps(True)})
|
{'stream_post_policy': ujson.dumps(
|
||||||
|
Stream.STREAM_POST_POLICY_ADMINS)})
|
||||||
|
self.assert_json_error(result, 'Must be an organization administrator')
|
||||||
|
|
||||||
|
do_set_realm_property(user_profile.realm, 'waiting_period_threshold', 10)
|
||||||
|
self.assertTrue(user_profile.is_new_member)
|
||||||
|
|
||||||
|
stream_id = get_stream('stream_name1', user_profile.realm).id
|
||||||
|
result = self.client_patch('/json/streams/%d' % (stream_id,),
|
||||||
|
{'stream_post_policy': ujson.dumps(
|
||||||
|
Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS)})
|
||||||
self.assert_json_error(result, 'Must be an organization administrator')
|
self.assert_json_error(result, 'Must be an organization administrator')
|
||||||
|
|
||||||
def set_up_stream_for_deletion(self, stream_name: str, invite_only: bool=False,
|
def set_up_stream_for_deletion(self, stream_name: str, invite_only: bool=False,
|
||||||
|
@ -2570,7 +2580,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.assertEqual(add_event['event']['op'], 'add')
|
self.assertEqual(add_event['event']['op'], 'add')
|
||||||
self.assertEqual(add_event['users'], [self.example_user("iago").id])
|
self.assertEqual(add_event['users'], [self.example_user("iago").id])
|
||||||
|
|
||||||
def test_subscibe_to_announce_only_stream(self) -> None:
|
def test_subscribe_to_stream_post_policy_admins_stream(self) -> None:
|
||||||
"""
|
"""
|
||||||
Members can subscribe to streams where only admins can post
|
Members can subscribe to streams where only admins can post
|
||||||
but not create those streams, only realm admins can
|
but not create those streams, only realm admins can
|
||||||
|
@ -2581,7 +2591,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
|
|
||||||
streams_raw = [{
|
streams_raw = [{
|
||||||
'name': 'new_stream',
|
'name': 'new_stream',
|
||||||
'is_announcement_only': True,
|
'stream_post_policy': Stream.STREAM_POST_POLICY_ADMINS,
|
||||||
}]
|
}]
|
||||||
with self.assertRaisesRegex(
|
with self.assertRaisesRegex(
|
||||||
JsonableError, "User cannot create a stream with these settings."):
|
JsonableError, "User cannot create a stream with these settings."):
|
||||||
|
@ -2592,7 +2602,56 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
self.assert_length(result[0], 0)
|
self.assert_length(result[0], 0)
|
||||||
self.assert_length(result[1], 1)
|
self.assert_length(result[1], 1)
|
||||||
self.assertEqual(result[1][0].name, 'new_stream')
|
self.assertEqual(result[1][0].name, 'new_stream')
|
||||||
self.assertEqual(result[1][0].is_announcement_only, True)
|
self.assertTrue(result[1][0].stream_post_policy == Stream.STREAM_POST_POLICY_ADMINS)
|
||||||
|
|
||||||
|
def test_subscribe_to_stream_post_policy_restrict_new_members_stream(self) -> None:
|
||||||
|
"""
|
||||||
|
New members can subscribe to streams where they can neither post
|
||||||
|
nor create those streams, only realm admins can can.
|
||||||
|
"""
|
||||||
|
new_member_email = self.nonreg_email('test')
|
||||||
|
self.register(new_member_email, "test")
|
||||||
|
new_member = self.nonreg_user('test')
|
||||||
|
|
||||||
|
do_set_realm_property(new_member.realm, 'waiting_period_threshold', 10)
|
||||||
|
streams_raw = [{
|
||||||
|
'name': 'new_stream',
|
||||||
|
'stream_post_policy': Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS,
|
||||||
|
}]
|
||||||
|
|
||||||
|
# new members cannot create STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams.
|
||||||
|
self.assertTrue(new_member.is_new_member)
|
||||||
|
with self.assertRaisesRegex(
|
||||||
|
JsonableError, "User cannot create a stream with these settings."):
|
||||||
|
list_to_streams(streams_raw, new_member, autocreate=True)
|
||||||
|
|
||||||
|
# Non admins can create STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams.
|
||||||
|
# However, they must not be a new user.
|
||||||
|
non_admin = self.example_user("AARON")
|
||||||
|
non_admin.date_joined = timezone_now() - timedelta(days=11)
|
||||||
|
non_admin.save()
|
||||||
|
self.assertFalse(non_admin.is_new_member)
|
||||||
|
self.assertFalse(non_admin.is_realm_admin)
|
||||||
|
result = list_to_streams(streams_raw, non_admin, autocreate=True)
|
||||||
|
self.assert_length(result[0], 0)
|
||||||
|
self.assert_length(result[1], 1)
|
||||||
|
self.assertEqual(result[1][0].name, 'new_stream')
|
||||||
|
self.assertTrue(result[1][0].stream_post_policy == Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS)
|
||||||
|
|
||||||
|
streams_raw = [{
|
||||||
|
'name': 'newer_stream',
|
||||||
|
'stream_post_policy': Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS,
|
||||||
|
}]
|
||||||
|
|
||||||
|
# Admins can create STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS streams,
|
||||||
|
# irrespective of whether they are new members or not.
|
||||||
|
admin = self.example_user("iago")
|
||||||
|
self.assertTrue(admin.is_realm_admin)
|
||||||
|
result = list_to_streams(streams_raw, admin, autocreate=True)
|
||||||
|
self.assert_length(result[0], 0)
|
||||||
|
self.assert_length(result[1], 1)
|
||||||
|
self.assertEqual(result[1][0].name, 'newer_stream')
|
||||||
|
self.assertTrue(result[1][0].stream_post_policy == Stream.STREAM_POST_POLICY_RESTRICT_NEW_MEMBERS)
|
||||||
|
|
||||||
def test_guest_user_subscribe(self) -> None:
|
def test_guest_user_subscribe(self) -> None:
|
||||||
"""Guest users cannot subscribe themselves to anything"""
|
"""Guest users cannot subscribe themselves to anything"""
|
||||||
|
@ -2611,7 +2670,7 @@ class SubscriptionAPITest(ZulipTestCase):
|
||||||
'invite_only': False,
|
'invite_only': False,
|
||||||
'history_public_to_subscribers': None,
|
'history_public_to_subscribers': None,
|
||||||
'name': 'new_stream',
|
'name': 'new_stream',
|
||||||
'is_announcement_only': False
|
'stream_post_policy': Stream.STREAM_POST_POLICY_EVERYONE
|
||||||
}]
|
}]
|
||||||
|
|
||||||
with self.assertRaisesRegex(JsonableError, "User cannot create streams."):
|
with self.assertRaisesRegex(JsonableError, "User cannot create streams."):
|
||||||
|
|
|
@ -17,19 +17,18 @@ from zerver.lib.actions import bulk_remove_subscriptions, \
|
||||||
bulk_add_subscriptions, do_send_messages, get_subscriber_emails, do_rename_stream, \
|
bulk_add_subscriptions, do_send_messages, get_subscriber_emails, do_rename_stream, \
|
||||||
do_deactivate_stream, do_change_stream_invite_only, do_add_default_stream, \
|
do_deactivate_stream, do_change_stream_invite_only, do_add_default_stream, \
|
||||||
do_change_stream_description, do_get_streams, \
|
do_change_stream_description, do_get_streams, \
|
||||||
do_remove_default_stream, \
|
do_remove_default_stream, do_change_stream_post_policy, do_delete_messages, \
|
||||||
do_create_default_stream_group, do_add_streams_to_default_stream_group, \
|
do_create_default_stream_group, do_add_streams_to_default_stream_group, \
|
||||||
do_remove_streams_from_default_stream_group, do_remove_default_stream_group, \
|
do_remove_streams_from_default_stream_group, do_remove_default_stream_group, \
|
||||||
do_change_default_stream_group_description, do_change_default_stream_group_name, \
|
do_change_default_stream_group_description, do_change_default_stream_group_name
|
||||||
do_change_stream_announcement_only, \
|
|
||||||
do_delete_messages
|
|
||||||
from zerver.lib.response import json_success, json_error
|
from zerver.lib.response import json_success, json_error
|
||||||
from zerver.lib.streams import access_stream_by_id, access_stream_by_name, \
|
from zerver.lib.streams import access_stream_by_id, access_stream_by_name, \
|
||||||
check_stream_name, check_stream_name_available, filter_stream_authorization, \
|
check_stream_name, check_stream_name_available, filter_stream_authorization, \
|
||||||
list_to_streams, access_stream_for_delete_or_update, access_default_stream_group_by_id
|
list_to_streams, access_stream_for_delete_or_update, access_default_stream_group_by_id
|
||||||
from zerver.lib.topic import get_topic_history_for_stream, messages_for_topic
|
from zerver.lib.topic import get_topic_history_for_stream, messages_for_topic
|
||||||
from zerver.lib.validator import check_string, check_int, check_list, check_dict, \
|
from zerver.lib.validator import check_string, check_int, check_list, check_dict, \
|
||||||
check_bool, check_variable_type, check_capped_string, check_color, check_dict_only
|
check_bool, check_variable_type, check_capped_string, check_color, check_dict_only, \
|
||||||
|
check_int_in
|
||||||
from zerver.models import UserProfile, Stream, Realm, UserMessage, \
|
from zerver.models import UserProfile, Stream, Realm, UserMessage, \
|
||||||
get_system_bot, get_active_user
|
get_system_bot, get_active_user
|
||||||
|
|
||||||
|
@ -150,6 +149,8 @@ def update_stream_backend(
|
||||||
Stream.MAX_DESCRIPTION_LENGTH), default=None),
|
Stream.MAX_DESCRIPTION_LENGTH), default=None),
|
||||||
is_private: Optional[bool]=REQ(validator=check_bool, default=None),
|
is_private: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
is_announcement_only: Optional[bool]=REQ(validator=check_bool, default=None),
|
is_announcement_only: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
|
stream_post_policy: Optional[int]=REQ(validator=check_int_in(
|
||||||
|
Stream.STREAM_POST_POLICY_TYPES), default=None),
|
||||||
history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
|
history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
new_name: Optional[str]=REQ(validator=check_string, default=None),
|
new_name: Optional[str]=REQ(validator=check_string, default=None),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
|
@ -171,7 +172,15 @@ def update_stream_backend(
|
||||||
check_stream_name_available(user_profile.realm, new_name)
|
check_stream_name_available(user_profile.realm, new_name)
|
||||||
do_rename_stream(stream, new_name, user_profile)
|
do_rename_stream(stream, new_name, user_profile)
|
||||||
if is_announcement_only is not None:
|
if is_announcement_only is not None:
|
||||||
do_change_stream_announcement_only(stream, is_announcement_only)
|
# is_announcement_only is a legacy way to specify
|
||||||
|
# stream_post_policy. We can probably just delete this code,
|
||||||
|
# since we're not aware of clients that used it, but we're
|
||||||
|
# keeping it for backwards-compatibility for now.
|
||||||
|
stream_post_policy = Stream.STREAM_POST_POLICY_EVERYONE
|
||||||
|
if is_announcement_only:
|
||||||
|
stream_post_policy = Stream.STREAM_POST_POLICY_ADMINS
|
||||||
|
if stream_post_policy is not None:
|
||||||
|
do_change_stream_post_policy(stream, stream_post_policy)
|
||||||
|
|
||||||
# But we require even realm administrators to be actually
|
# But we require even realm administrators to be actually
|
||||||
# subscribed to make a private stream public.
|
# subscribed to make a private stream public.
|
||||||
|
@ -296,7 +305,8 @@ def add_subscriptions_backend(
|
||||||
])
|
])
|
||||||
)),
|
)),
|
||||||
invite_only: bool=REQ(validator=check_bool, default=False),
|
invite_only: bool=REQ(validator=check_bool, default=False),
|
||||||
is_announcement_only: bool=REQ(validator=check_bool, default=False),
|
stream_post_policy: int=REQ(validator=check_int_in(
|
||||||
|
Stream.STREAM_POST_POLICY_TYPES), default=Stream.STREAM_POST_POLICY_EVERYONE),
|
||||||
history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
|
history_public_to_subscribers: Optional[bool]=REQ(validator=check_bool, default=None),
|
||||||
announce: bool=REQ(validator=check_bool, default=False),
|
announce: bool=REQ(validator=check_bool, default=False),
|
||||||
principals: List[str]=REQ(validator=check_list(check_string), default=[]),
|
principals: List[str]=REQ(validator=check_list(check_string), default=[]),
|
||||||
|
@ -319,7 +329,7 @@ def add_subscriptions_backend(
|
||||||
# Strip the stream name here.
|
# Strip the stream name here.
|
||||||
stream_dict_copy['name'] = stream_dict_copy['name'].strip()
|
stream_dict_copy['name'] = stream_dict_copy['name'].strip()
|
||||||
stream_dict_copy["invite_only"] = invite_only
|
stream_dict_copy["invite_only"] = invite_only
|
||||||
stream_dict_copy["is_announcement_only"] = is_announcement_only
|
stream_dict_copy["stream_post_policy"] = stream_post_policy
|
||||||
stream_dict_copy["history_public_to_subscribers"] = history_public_to_subscribers
|
stream_dict_copy["history_public_to_subscribers"] = history_public_to_subscribers
|
||||||
stream_dicts.append(stream_dict_copy)
|
stream_dicts.append(stream_dict_copy)
|
||||||
|
|
||||||
|
|
|
@ -526,7 +526,8 @@ class Command(BaseCommand):
|
||||||
zulip_stream_dict = {
|
zulip_stream_dict = {
|
||||||
"devel": {"description": "For developing"},
|
"devel": {"description": "For developing"},
|
||||||
"all": {"description": "For **everything**"},
|
"all": {"description": "For **everything**"},
|
||||||
"announce": {"description": "For announcements", 'is_announcement_only': True},
|
"announce": {"description": "For announcements",
|
||||||
|
'stream_post_policy': Stream.STREAM_POST_POLICY_ADMINS},
|
||||||
"design": {"description": "For design"},
|
"design": {"description": "For design"},
|
||||||
"support": {"description": "For support"},
|
"support": {"description": "For support"},
|
||||||
"social": {"description": "For socializing"},
|
"social": {"description": "For socializing"},
|
||||||
|
|
Loading…
Reference in New Issue