mirror of https://github.com/zulip/zulip.git
update_realm: Allow demo orgs to be converted to regular orgs.
This commit adds support to the `PATCH /realm` endpoint for converting a demo organization to a regular organization. This is a part of #19523.
This commit is contained in:
parent
98415808ba
commit
29b354346b
|
@ -11,6 +11,12 @@ below features are supported.
|
||||||
|
|
||||||
## Changes in Zulip 5.0
|
## Changes in Zulip 5.0
|
||||||
|
|
||||||
|
**Feature level 104**
|
||||||
|
|
||||||
|
* [`PATCH /realm`]: Added `string_id` parameter for changing an
|
||||||
|
organization's subdomain. Currently, this is only allowed for
|
||||||
|
changing changing a demo organization to a normal one.
|
||||||
|
|
||||||
**Feature level 103**
|
**Feature level 103**
|
||||||
|
|
||||||
* [`POST /register`](/api/register-queue): Added `create_web_public_stream_policy`
|
* [`POST /register`](/api/register-queue): Added `create_web_public_stream_policy`
|
||||||
|
|
|
@ -33,7 +33,7 @@ DESKTOP_WARNING_VERSION = "5.4.3"
|
||||||
# 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, as well as
|
# new level means in templates/zerver/api/changelog.md, as well as
|
||||||
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
# "**Changes**" entries in the endpoint's documentation in `zulip.yaml`.
|
||||||
API_FEATURE_LEVEL = 103
|
API_FEATURE_LEVEL = 104
|
||||||
|
|
||||||
# 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
|
||||||
|
|
|
@ -1116,10 +1116,21 @@ def do_reactivate_realm(realm: Realm) -> None:
|
||||||
def do_change_realm_subdomain(
|
def do_change_realm_subdomain(
|
||||||
realm: Realm, new_subdomain: str, *, acting_user: Optional[UserProfile]
|
realm: Realm, new_subdomain: str, *, acting_user: Optional[UserProfile]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Changing a realm's subdomain is a highly disruptive operation,
|
||||||
|
because all existing clients will need to be updated to point to
|
||||||
|
the new URL. Further, requests to fetch data frmo existing event
|
||||||
|
queues will fail with an authentication error when this change
|
||||||
|
happens (because the old subdomain is no longer associated with
|
||||||
|
the realm), making it hard for us to provide a graceful update
|
||||||
|
experience for clients.
|
||||||
|
"""
|
||||||
old_subdomain = realm.subdomain
|
old_subdomain = realm.subdomain
|
||||||
old_uri = realm.uri
|
old_uri = realm.uri
|
||||||
|
# If the realm had been a demo organization scheduled for
|
||||||
|
# deleting, clear that state.
|
||||||
|
realm.demo_organization_scheduled_deletion_date = None
|
||||||
realm.string_id = new_subdomain
|
realm.string_id = new_subdomain
|
||||||
realm.save(update_fields=["string_id"])
|
realm.save(update_fields=["string_id", "demo_organization_scheduled_deletion_date"])
|
||||||
RealmAuditLog.objects.create(
|
RealmAuditLog.objects.create(
|
||||||
realm=realm,
|
realm=realm,
|
||||||
event_type=RealmAuditLog.REALM_SUBDOMAIN_CHANGED,
|
event_type=RealmAuditLog.REALM_SUBDOMAIN_CHANGED,
|
||||||
|
|
|
@ -219,7 +219,7 @@ class Realm(models.Model):
|
||||||
string_id: str = models.CharField(max_length=MAX_REALM_SUBDOMAIN_LENGTH, unique=True)
|
string_id: str = models.CharField(max_length=MAX_REALM_SUBDOMAIN_LENGTH, unique=True)
|
||||||
|
|
||||||
date_created: datetime.datetime = models.DateTimeField(default=timezone_now)
|
date_created: datetime.datetime = models.DateTimeField(default=timezone_now)
|
||||||
demo_organization_scheduled_deletion_date: datetime.datetime = models.DateTimeField(
|
demo_organization_scheduled_deletion_date: Optional[datetime.datetime] = models.DateTimeField(
|
||||||
default=None, null=True
|
default=None, null=True
|
||||||
)
|
)
|
||||||
deactivated: bool = models.BooleanField(default=False)
|
deactivated: bool = models.BooleanField(default=False)
|
||||||
|
|
|
@ -171,6 +171,37 @@ class RealmTest(ZulipTestCase):
|
||||||
realm = get_realm("zulip")
|
realm = get_realm("zulip")
|
||||||
self.assertNotEqual(realm.description, new_description)
|
self.assertNotEqual(realm.description, new_description)
|
||||||
|
|
||||||
|
def test_realm_convert_demo_realm(self) -> None:
|
||||||
|
data = dict(string_id="coolrealm")
|
||||||
|
|
||||||
|
self.login("iago")
|
||||||
|
result = self.client_patch("/json/realm", data)
|
||||||
|
self.assert_json_error(result, "Must be an organization owner")
|
||||||
|
|
||||||
|
self.login("desdemona")
|
||||||
|
result = self.client_patch("/json/realm", data)
|
||||||
|
self.assert_json_error(result, "Must be a demo organization.")
|
||||||
|
|
||||||
|
data = dict(string_id="lear")
|
||||||
|
self.login("desdemona")
|
||||||
|
realm = get_realm("zulip")
|
||||||
|
realm.demo_organization_scheduled_deletion_date = timezone_now() + datetime.timedelta(
|
||||||
|
days=30
|
||||||
|
)
|
||||||
|
realm.save()
|
||||||
|
result = self.client_patch("/json/realm", data)
|
||||||
|
self.assert_json_error(result, "Subdomain unavailable. Please choose a different one.")
|
||||||
|
|
||||||
|
# Now try to change the string_id to something available.
|
||||||
|
data = dict(string_id="coolrealm")
|
||||||
|
result = self.client_patch("/json/realm", data)
|
||||||
|
self.assert_json_success(result)
|
||||||
|
json = orjson.loads(result.content)
|
||||||
|
self.assertEqual(json["realm_uri"], "http://coolrealm.testserver")
|
||||||
|
realm = get_realm("coolrealm")
|
||||||
|
self.assertIsNone(realm.demo_organization_scheduled_deletion_date)
|
||||||
|
self.assertEqual(realm.string_id, data["string_id"])
|
||||||
|
|
||||||
def test_realm_name_length(self) -> None:
|
def test_realm_name_length(self) -> None:
|
||||||
new_name = "A" * (Realm.MAX_REALM_NAME_LENGTH + 1)
|
new_name = "A" * (Realm.MAX_REALM_NAME_LENGTH + 1)
|
||||||
data = dict(name=new_name)
|
data = dict(name=new_name)
|
||||||
|
|
|
@ -10,6 +10,7 @@ from confirmation.models import Confirmation, ConfirmationKeyException, get_obje
|
||||||
from zerver.decorator import require_realm_admin, require_realm_owner
|
from zerver.decorator import require_realm_admin, require_realm_owner
|
||||||
from zerver.forms import check_subdomain_available as check_subdomain
|
from zerver.forms import check_subdomain_available as check_subdomain
|
||||||
from zerver.lib.actions import (
|
from zerver.lib.actions import (
|
||||||
|
do_change_realm_subdomain,
|
||||||
do_deactivate_realm,
|
do_deactivate_realm,
|
||||||
do_reactivate_realm,
|
do_reactivate_realm,
|
||||||
do_set_realm_authentication_methods,
|
do_set_realm_authentication_methods,
|
||||||
|
@ -133,6 +134,10 @@ def update_realm(
|
||||||
digest_weekday: Optional[int] = REQ(
|
digest_weekday: Optional[int] = REQ(
|
||||||
json_validator=check_int_in(Realm.DIGEST_WEEKDAY_VALUES), default=None
|
json_validator=check_int_in(Realm.DIGEST_WEEKDAY_VALUES), default=None
|
||||||
),
|
),
|
||||||
|
string_id: Optional[str] = REQ(
|
||||||
|
str_validator=check_capped_string(Realm.MAX_REALM_SUBDOMAIN_LENGTH),
|
||||||
|
default=None,
|
||||||
|
),
|
||||||
) -> HttpResponse:
|
) -> HttpResponse:
|
||||||
realm = user_profile.realm
|
realm = user_profile.realm
|
||||||
|
|
||||||
|
@ -273,6 +278,21 @@ def update_realm(
|
||||||
else:
|
else:
|
||||||
data["default_code_block_language"] = default_code_block_language
|
data["default_code_block_language"] = default_code_block_language
|
||||||
|
|
||||||
|
if string_id is not None:
|
||||||
|
if not user_profile.is_realm_owner:
|
||||||
|
raise OrganizationOwnerRequired()
|
||||||
|
|
||||||
|
if realm.demo_organization_scheduled_deletion_date is None:
|
||||||
|
raise JsonableError(_("Must be a demo organization."))
|
||||||
|
|
||||||
|
try:
|
||||||
|
check_subdomain(string_id)
|
||||||
|
except ValidationError as err:
|
||||||
|
raise JsonableError(str(err.message))
|
||||||
|
|
||||||
|
do_change_realm_subdomain(realm, string_id, acting_user=user_profile)
|
||||||
|
data["realm_uri"] = realm.uri
|
||||||
|
|
||||||
return json_success(data)
|
return json_success(data)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue