settings: Replace invite_by_admins_policy with invite_to_realm_policy.

This commit replaces invite_by_admins_policy, which was a bool field,
with a new enum field invite_by_realm_policy.

Though the final goal is to add moderators and full members option
using COMMON_POLICY_TYPES, but this will be done in a separate
commit to make this easy for review.
This commit is contained in:
sahil839 2021-04-02 22:17:08 +05:30 committed by Tim Abbott
parent d4248cf060
commit 4c8339fa8c
17 changed files with 165 additions and 35 deletions

View File

@ -192,7 +192,7 @@ export function dispatch_normal_event(event) {
disallow_disposable_email_addresses: noop, disallow_disposable_email_addresses: noop,
inline_image_preview: noop, inline_image_preview: noop,
inline_url_embed_preview: noop, inline_url_embed_preview: noop,
invite_by_admins_only: noop, invite_to_realm_policy: noop,
invite_required: noop, invite_required: noop,
mandatory_topics: noop, mandatory_topics: noop,
message_content_edit_limit_seconds: noop, message_content_edit_limit_seconds: noop,

View File

@ -123,6 +123,11 @@ export const create_stream_policy_values = {
export const invite_to_stream_policy_values = create_stream_policy_values; export const invite_to_stream_policy_values = create_stream_policy_values;
export const invite_to_realm_policy_values = {
by_members: 1,
by_admins_only: 2,
};
export const user_group_edit_policy_values = { export const user_group_edit_policy_values = {
by_admins_only: { by_admins_only: {
order: 1, order: 1,

View File

@ -187,12 +187,18 @@ function get_property_value(property_name) {
if (property_name === "realm_user_invite_restriction") { if (property_name === "realm_user_invite_restriction") {
if (!page_params.realm_invite_required) { if (!page_params.realm_invite_required) {
if (page_params.realm_invite_by_admins_only) { if (
page_params.realm_invite_to_realm_policy ===
settings_config.invite_to_realm_policy_values.by_admins_only
) {
return "no_invite_required_by_admins_only"; return "no_invite_required_by_admins_only";
} }
return "no_invite_required"; return "no_invite_required";
} }
if (page_params.realm_invite_by_admins_only) { if (
page_params.realm_invite_to_realm_policy ===
settings_config.invite_to_realm_policy_values.by_admins_only
) {
return "by_admins_only"; return "by_admins_only";
} }
return "by_anyone"; return "by_anyone";
@ -451,7 +457,7 @@ export function sync_realm_settings(property) {
property = "message_content_delete_limit_minutes"; property = "message_content_delete_limit_minutes";
} else if (property === "allow_message_deleting") { } else if (property === "allow_message_deleting") {
property = "msg_delete_limit_setting"; property = "msg_delete_limit_setting";
} else if (property === "invite_required" || property === "invite_by_admins_only") { } else if (property === "invite_required" || property === "invite_to_realm_policy") {
property = "user_invite_restriction"; property = "user_invite_restriction";
} }
const element = $(`#id_realm_${CSS.escape(property)}`); const element = $(`#id_realm_${CSS.escape(property)}`);
@ -814,16 +820,20 @@ export function build_page() {
const user_invite_restriction = $("#id_realm_user_invite_restriction").val(); const user_invite_restriction = $("#id_realm_user_invite_restriction").val();
if (user_invite_restriction === "no_invite_required") { if (user_invite_restriction === "no_invite_required") {
data.invite_required = false; data.invite_required = false;
data.invite_by_admins_only = false; data.invite_to_realm_policy =
settings_config.invite_to_realm_policy_values.by_members;
} else if (user_invite_restriction === "no_invite_required_by_admins_only") { } else if (user_invite_restriction === "no_invite_required_by_admins_only") {
data.invite_required = false; data.invite_required = false;
data.invite_by_admins_only = true; data.invite_to_realm_policy =
settings_config.invite_to_realm_policy_values.by_admins_only;
} else if (user_invite_restriction === "by_admins_only") { } else if (user_invite_restriction === "by_admins_only") {
data.invite_required = true; data.invite_required = true;
data.invite_by_admins_only = true; data.invite_to_realm_policy =
settings_config.invite_to_realm_policy_values.by_admins_only;
} else { } else {
data.invite_required = true; data.invite_required = true;
data.invite_by_admins_only = false; data.invite_to_realm_policy =
settings_config.invite_to_realm_policy_values.by_members;
} }
const waiting_period_threshold = $("#id_realm_waiting_period_setting").val(); const waiting_period_threshold = $("#id_realm_waiting_period_setting").val();

View File

@ -10,6 +10,11 @@ below features are supported.
## Changes in Zulip 4.0 ## Changes in Zulip 4.0
**Feature level 50**
* [`POST /register`](/api/register-queue): Replaced `invite_by_admins_only`
field with an integer field `invite_to_realm_policy`.
**Feature level 49** **Feature level 49**
* Added new [`POST /realm/playground`](/api/add-playground) and * Added new [`POST /realm/playground`](/api/add-playground) and

View File

@ -30,7 +30,7 @@ DESKTOP_WARNING_VERSION = "5.2.0"
# #
# Changes should be accompanied by documentation explaining what the # Changes should be accompanied by documentation explaining what the
# new level means in templates/zerver/api/changelog.md. # new level means in templates/zerver/api/changelog.md.
API_FEATURE_LEVEL = 49 API_FEATURE_LEVEL = 50
# Bump the minor PROVISION_VERSION to indicate that folks should provision # Bump the minor PROVISION_VERSION to indicate that folks should provision
# only when going from an old version of the code to a newer version. Bump # only when going from an old version of the code to a newer version. Bump

View File

@ -349,7 +349,7 @@ def compute_show_invites_and_add_streams(user_profile: Optional[UserProfile]) ->
if user_profile.is_realm_admin: if user_profile.is_realm_admin:
return True, True return True, True
if user_profile.realm.invite_by_admins_only: if user_profile.realm.invite_to_realm_policy == Realm.INVITE_TO_REALM_ADMINS_ONLY:
return False, True return False, True
return True, True return True, True

View File

@ -0,0 +1,18 @@
# Generated by Django 3.1.7 on 2021-04-01 19:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("zerver", "0315_realmplayground"),
]
operations = [
migrations.AddField(
model_name="realm",
name="invite_to_realm_policy",
field=models.PositiveSmallIntegerField(default=1),
),
]

View File

@ -0,0 +1,30 @@
# Generated by Django 3.1.7 on 2021-04-01 19:27
from django.db import migrations
from django.db.backends.postgresql.schema import DatabaseSchemaEditor
from django.db.migrations.state import StateApps
def migrate_to_invite_to_realm_policy(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
Realm = apps.get_model("zerver", "Realm")
Realm.INVITE_TO_REALM_POLICY_MEMBERS_ONLY = 1
Realm.INVITE_TO_REALM_POLICY_ADMINS_ONLY = 2
Realm.objects.filter(invite_by_admins_only=False).update(
invite_to_realm_policy=Realm.INVITE_TO_REALM_POLICY_MEMBERS_ONLY
)
Realm.objects.filter(invite_by_admins_only=True).update(
invite_to_realm_policy=Realm.INVITE_TO_REALM_POLICY_ADMINS_ONLY
)
class Migration(migrations.Migration):
dependencies = [
("zerver", "0316_realm_invite_to_realm_policy"),
]
operations = [
migrations.RunPython(
migrate_to_invite_to_realm_policy, reverse_code=migrations.RunPython.noop, elidable=True
),
]

View File

@ -0,0 +1,17 @@
# Generated by Django 3.1.7 on 2021-04-01 19:32
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("zerver", "0317_migrate_to_invite_to_realm_policy"),
]
operations = [
migrations.RemoveField(
model_name="realm",
name="invite_by_admins_only",
),
]

View File

@ -224,8 +224,18 @@ class Realm(models.Model):
# See RealmDomain for the domains that apply for a given organization. # See RealmDomain for the domains that apply for a given organization.
emails_restricted_to_domains: bool = models.BooleanField(default=False) emails_restricted_to_domains: bool = models.BooleanField(default=False)
INVITE_TO_REALM_MEMBERS_ONLY = 1
INVITE_TO_REALM_ADMINS_ONLY = 2
INVITE_TO_REALM_POLICY_TYPES = [
INVITE_TO_REALM_MEMBERS_ONLY,
INVITE_TO_REALM_ADMINS_ONLY,
]
invite_required: bool = models.BooleanField(default=True) invite_required: bool = models.BooleanField(default=True)
invite_by_admins_only: bool = models.BooleanField(default=False) invite_to_realm_policy: int = models.PositiveSmallIntegerField(
default=INVITE_TO_REALM_MEMBERS_ONLY
)
_max_invites: Optional[int] = models.IntegerField(null=True, db_column="max_invites") _max_invites: Optional[int] = models.IntegerField(null=True, db_column="max_invites")
disallow_disposable_email_addresses: bool = models.BooleanField(default=True) disallow_disposable_email_addresses: bool = models.BooleanField(default=True)
authentication_methods: BitHandler = BitField( authentication_methods: BitHandler = BitField(
@ -466,7 +476,7 @@ class Realm(models.Model):
email_address_visibility=int, email_address_visibility=int,
email_changes_disabled=bool, email_changes_disabled=bool,
invite_required=bool, invite_required=bool,
invite_by_admins_only=bool, invite_to_realm_policy=int,
inline_image_preview=bool, inline_image_preview=bool,
inline_url_embed_preview=bool, inline_url_embed_preview=bool,
mandatory_topics=bool, mandatory_topics=bool,

View File

@ -3017,12 +3017,17 @@ paths:
type: boolean type: boolean
description: | description: |
Whether an invitation is required to join this organization. Whether an invitation is required to join this organization.
invite_by_admins_only: invite_to_realm_policy:
type: boolean type: integer
description: | description: |
If true, only organization administrators can invite new users to join. Policy for [who can invite new users](/help/invite-new-users#change-who-can-send-invitations)
If false, any [Member](/help/roles-and-permissions) can invite new users to join the organization:
to join the organization. [Help center article](/help/invite-new-users#change-who-can-send-invitations).
* 1 = Members only
* 2 = Administrators only
**Changes**: New in Zulip 4.0 (feature level 50) replacing the
previous `invite_by_admins_only` boolean.
inline_image_preview: inline_image_preview:
type: boolean type: boolean
description: | description: |
@ -7762,14 +7767,19 @@ paths:
Present if `realm` is present in `fetch_event_types`. Present if `realm` is present in `fetch_event_types`.
Whether an invitation is required to join this organization. Whether an invitation is required to join this organization.
realm_invite_by_admins_only: realm_invite_to_realm_policy:
type: boolean type: integer
description: | description: |
Present if `realm` is present in `fetch_event_types`. Present if `realm` is present in `fetch_event_types`.
If true, only organization administrators can invite new users to join. Policy for [who can invite new users](/help/invite-new-users#change-who-can-send-invitations)
If false, any [Member](/help/roles-and-permissions) can invite new users to join the organization:
to join the organization. [Help center article](/help/invite-new-users#change-who-can-send-invitations).
* 1 = Members only
* 2 = Administrators only
**Changes**: New in Zulip 4.0 (feature level 50) replacing the
previous `realm_invite_by_admins_only` boolean.
realm_inline_image_preview: realm_inline_image_preview:
type: boolean type: boolean
description: | description: |

View File

@ -1872,6 +1872,7 @@ class RealmPropertyActionTest(BaseAction):
], ],
default_code_block_language=["python", "javascript"], default_code_block_language=["python", "javascript"],
message_content_delete_limit_seconds=[1000, 1100, 1200], message_content_delete_limit_seconds=[1000, 1100, 1200],
invite_to_realm_policy=[2, 1],
) )
vals = test_values.get(name) vals = test_values.get(name)

View File

@ -158,8 +158,8 @@ class HomeTest(ZulipTestCase):
"realm_incoming_webhook_bots", "realm_incoming_webhook_bots",
"realm_inline_image_preview", "realm_inline_image_preview",
"realm_inline_url_embed_preview", "realm_inline_url_embed_preview",
"realm_invite_by_admins_only",
"realm_invite_required", "realm_invite_required",
"realm_invite_to_realm_policy",
"realm_invite_to_stream_policy", "realm_invite_to_stream_policy",
"realm_is_zephyr_mirror_realm", "realm_is_zephyr_mirror_realm",
"realm_logo_source", "realm_logo_source",
@ -670,7 +670,7 @@ class HomeTest(ZulipTestCase):
user_profile = self.example_user("hamlet") user_profile = self.example_user("hamlet")
realm = user_profile.realm realm = user_profile.realm
realm.invite_by_admins_only = True realm.invite_to_realm_policy = Realm.INVITE_TO_REALM_ADMINS_ONLY
realm.save() realm.save()
self.login_user(user_profile) self.login_user(user_profile)
@ -689,12 +689,14 @@ class HomeTest(ZulipTestCase):
user_profile = self.example_user("polonius") user_profile = self.example_user("polonius")
realm = user_profile.realm realm = user_profile.realm
realm.invite_by_admins_only = False realm.invite_to_realm_policy = Realm.INVITE_TO_REALM_MEMBERS_ONLY
realm.save() realm.save()
self.login_user(user_profile) self.login_user(user_profile)
self.assertFalse(user_profile.is_realm_admin) self.assertFalse(user_profile.is_realm_admin)
self.assertFalse(get_realm("zulip").invite_by_admins_only) self.assertEqual(
get_realm("zulip").invite_to_realm_policy, Realm.INVITE_TO_REALM_MEMBERS_ONLY
)
result = self._get_home_page() result = self._get_home_page()
html = result.content.decode("utf-8") html = result.content.decode("utf-8")
self.assertNotIn("Invite more users", html) self.assertNotIn("Invite more users", html)
@ -1078,7 +1080,7 @@ class HomeTest(ZulipTestCase):
user = self.example_user("iago") user = self.example_user("iago")
realm = user.realm realm = user.realm
realm.invite_by_admins_only = True realm.invite_to_realm_policy = Realm.INVITE_TO_REALM_ADMINS_ONLY
realm.save() realm.save()
show_invites, show_add_streams = compute_show_invites_and_add_streams(user) show_invites, show_add_streams = compute_show_invites_and_add_streams(user)
@ -1089,7 +1091,7 @@ class HomeTest(ZulipTestCase):
user = self.example_user("hamlet") user = self.example_user("hamlet")
realm = user.realm realm = user.realm
realm.invite_by_admins_only = True realm.invite_to_realm_policy = Realm.INVITE_TO_REALM_ADMINS_ONLY
realm.save() realm.save()
show_invites, show_add_streams = compute_show_invites_and_add_streams(user) show_invites, show_add_streams = compute_show_invites_and_add_streams(user)

View File

@ -551,6 +551,18 @@ class RealmTest(ZulipTestCase):
result = self.client_patch("/json/realm", req) result = self.client_patch("/json/realm", req)
self.assert_json_error(result, "Invalid invite_to_stream_policy") self.assert_json_error(result, "Invalid invite_to_stream_policy")
def test_change_invite_to_realm_policy(self) -> None:
# We need an admin user.
self.login("iago")
req = dict(invite_to_realm_policy=orjson.dumps(Realm.INVITE_TO_REALM_ADMINS_ONLY).decode())
result = self.client_patch("/json/realm", req)
self.assert_json_success(result)
invalid_value = 10
req = dict(invite_to_realm_policy=orjson.dumps(invalid_value).decode())
result = self.client_patch("/json/realm", req)
self.assert_json_error(result, "Invalid invite_to_realm_policy")
def test_user_group_edit_policy(self) -> None: def test_user_group_edit_policy(self) -> None:
# We need an admin user. # We need an admin user.
self.login("iago") self.login("iago")
@ -610,6 +622,7 @@ class RealmTest(ZulipTestCase):
private_message_policy=10, private_message_policy=10,
message_content_delete_limit_seconds=-10, message_content_delete_limit_seconds=-10,
wildcard_mention_policy=10, wildcard_mention_policy=10,
invite_to_realm_policy=10,
) )
# We need an admin user. # We need an admin user.
@ -874,6 +887,10 @@ class RealmAPITest(ZulipTestCase):
), ),
], ],
message_content_delete_limit_seconds=[1000, 1100, 1200], message_content_delete_limit_seconds=[1000, 1100, 1200],
invite_to_realm_policy=[
Realm.INVITE_TO_REALM_ADMINS_ONLY,
Realm.INVITE_TO_REALM_MEMBERS_ONLY,
],
) )
vals = test_values.get(name) vals = test_values.get(name)

View File

@ -1234,10 +1234,10 @@ class InviteUserTest(InviteUserBase):
def test_require_realm_admin(self) -> None: def test_require_realm_admin(self) -> None:
""" """
The invite_by_admins_only realm setting works properly. The invite_to_realm_policy realm setting works properly.
""" """
realm = get_realm("zulip") realm = get_realm("zulip")
realm.invite_by_admins_only = True realm.invite_to_realm_policy = Realm.INVITE_TO_REALM_ADMINS_ONLY
realm.save() realm.save()
self.login("hamlet") self.login("hamlet")
@ -2433,8 +2433,8 @@ class MultiuseInviteTest(ZulipTestCase):
def test_only_admin_can_create_multiuse_link_api_call(self) -> None: def test_only_admin_can_create_multiuse_link_api_call(self) -> None:
self.login("iago") self.login("iago")
# Only admins should be able to create multiuse invites even if # Only admins should be able to create multiuse invites even if
# invite_by_admins_only is set to False. # invite_to_realm_policy is set to Realm.INVITE_TO_REALM_MEMBERS_ONLY.
self.realm.invite_by_admins_only = False self.realm.invite_to_realm_policy = Realm.INVITE_TO_REALM_MEMBERS_ONLY
self.realm.save() self.realm.save()
result = self.client_post("/json/invites/multiuse") result = self.client_post("/json/invites/multiuse")

View File

@ -18,7 +18,7 @@ from zerver.lib.request import REQ, JsonableError, has_request_variables
from zerver.lib.response import json_error, json_success from zerver.lib.response import json_error, json_success
from zerver.lib.streams import access_stream_by_id from zerver.lib.streams import access_stream_by_id
from zerver.lib.validator import check_int, check_list from zerver.lib.validator import check_int, check_list
from zerver.models import MultiuseInvite, PreregistrationUser, Stream, UserProfile from zerver.models import MultiuseInvite, PreregistrationUser, Realm, Stream, UserProfile
def check_if_owner_required(invited_as: int, user_profile: UserProfile) -> None: def check_if_owner_required(invited_as: int, user_profile: UserProfile) -> None:
@ -39,7 +39,10 @@ def invite_users_backend(
stream_ids: List[int] = REQ(validator=check_list(check_int)), stream_ids: List[int] = REQ(validator=check_list(check_int)),
) -> HttpResponse: ) -> HttpResponse:
if user_profile.realm.invite_by_admins_only and not user_profile.is_realm_admin: if (
user_profile.realm.invite_to_realm_policy == Realm.INVITE_TO_REALM_ADMINS_ONLY
and not user_profile.is_realm_admin
):
raise OrganizationAdministratorRequired() raise OrganizationAdministratorRequired()
if invite_as not in PreregistrationUser.INVITE_AS.values(): if invite_as not in PreregistrationUser.INVITE_AS.values():
return json_error(_("Must be invited as an valid type of user")) return json_error(_("Must be invited as an valid type of user"))

View File

@ -46,7 +46,9 @@ def update_realm(
emails_restricted_to_domains: Optional[bool] = REQ(validator=check_bool, default=None), emails_restricted_to_domains: Optional[bool] = REQ(validator=check_bool, default=None),
disallow_disposable_email_addresses: Optional[bool] = REQ(validator=check_bool, default=None), disallow_disposable_email_addresses: Optional[bool] = REQ(validator=check_bool, default=None),
invite_required: Optional[bool] = REQ(validator=check_bool, default=None), invite_required: Optional[bool] = REQ(validator=check_bool, default=None),
invite_by_admins_only: Optional[bool] = REQ(validator=check_bool, default=None), invite_to_realm_policy: Optional[int] = REQ(
validator=check_int_in(Realm.INVITE_TO_REALM_POLICY_TYPES), default=None
),
name_changes_disabled: Optional[bool] = REQ(validator=check_bool, default=None), name_changes_disabled: Optional[bool] = REQ(validator=check_bool, default=None),
email_changes_disabled: Optional[bool] = REQ(validator=check_bool, default=None), email_changes_disabled: Optional[bool] = REQ(validator=check_bool, default=None),
avatar_changes_disabled: Optional[bool] = REQ(validator=check_bool, default=None), avatar_changes_disabled: Optional[bool] = REQ(validator=check_bool, default=None),