confirmation: Replace RealmCreationKey - use Confirmation instead.

Fixes #20028.

There's no reason to have a special `RealmCreationKey` class - the
`Confirmation` system already does this job.

This is somewhat complicated by the need to write a migration for
`RealmCreationKey`->`Confirmation` for pre-existing, valid objects, to
avoid breaking realm creation links that haven't been used yet.
This commit is contained in:
Mateusz Mandera
2025-07-06 02:13:02 +08:00
committed by Tim Abbott
parent 072f234269
commit 40b1f6eb4e
13 changed files with 238 additions and 77 deletions

View File

@@ -0,0 +1,112 @@
# Generated by Django 5.2.3 on 2025-07-04 20:33
from datetime import timedelta
from django.conf import settings
from django.db import migrations, transaction
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
from django.db.migrations.state import StateApps
from django.db.models import Max
def migrate_realmcreationkey_to_realmcreationstatus(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
"""
This migration is for switching from using a separate RealmCreationKey class
for realm creation keys to just using the Confirmation system.
The aim is to iterate through all the existing RealmCreationKey and create
a corresponding Confirmation+RealmCreationStatus.
For validity of these objects, we only need to worry about RealmCreationKeys
expired due to time. This is taken care of by making sure we set expiry_date
on the Confirmation we're creating.
The way the RealmCreationKey system worked was to .delete() the RealmCreationKey
objects when they were used - so those simply no longer exist and we don't need to
worry about this case.
"""
CAN_CREATE_REALM = 11
Confirmation = apps.get_model("confirmation", "Confirmation")
ContentType = apps.get_model("contenttypes", "ContentType")
RealmCreationKey = apps.get_model("confirmation", "RealmCreationKey")
RealmCreationStatus = apps.get_model("zerver", "RealmCreationStatus")
realm_creation_status_content_type, created = ContentType.objects.get_or_create(
model="realmcreationstatus", app_label="zerver"
)
BATCH_SIZE = 10000
max_id = RealmCreationKey.objects.aggregate(Max("id"))["id__max"]
if max_id is None:
# Nothing to do.
return
lower_bound = 1
while lower_bound <= max_id + (BATCH_SIZE / 2):
upper_bound = lower_bound + BATCH_SIZE - 1
creation_keys = RealmCreationKey.objects.filter(id__range=(lower_bound, upper_bound))
creation_statuses_to_create = []
confirmations_to_create = []
for creation_key in creation_keys:
# These keys were generated in the same way as keys for Confirmation objects,
# so we can copy them over without breaking anything.
key = creation_key.creation_key
date_created = creation_key.date_created
presume_email_valid = creation_key.presume_email_valid
creation_status = RealmCreationStatus(
status=0, date_created=date_created, presume_email_valid=presume_email_valid
)
confirmation = Confirmation(
content_type=realm_creation_status_content_type,
type=CAN_CREATE_REALM,
confirmation_key=key,
date_sent=date_created,
expiry_date=date_created
+ timedelta(days=settings.CAN_CREATE_REALM_LINK_VALIDITY_DAYS),
)
# To attach the Confirmations to RealmCreationStatus objects we need to set the
# confirmation.object_id to their respective ids. But we haven't saved
# the RealmCreationStatus objs to the database yet - so we don't have ids.
#
# After we .bulk_create() them, their .id attributes will be populated.
# So for now we just link the RealmCreationStatus to the Confirmation
# via a temporary ._object attribute - which we'll use later to set
# the .object_id as intended.
confirmation._object = creation_status
creation_statuses_to_create.append(creation_status)
confirmations_to_create.append(confirmation)
with transaction.atomic():
RealmCreationStatus.objects.bulk_create(creation_statuses_to_create)
# Now the objects in creation_statuses_to_create have had their .id
# attrs populated. For every confirmation, confirmation._object
# points to its corresponding RealmCreationStatus - so now we can
# set the confirmation.object_id values and clear out the temporary
# ._object attr.
for confirmation in confirmations_to_create:
confirmation.object_id = confirmation._object.id
delattr(confirmation, "_object")
Confirmation.objects.bulk_create(confirmations_to_create)
lower_bound += BATCH_SIZE
class Migration(migrations.Migration):
atomic = False
dependencies = [
("confirmation", "0015_alter_confirmation_object_id"),
("zerver", "0743_realmcreationstatus"),
]
operations = [
migrations.RunPython(
migrate_realmcreationkey_to_realmcreationstatus,
reverse_code=migrations.RunPython.noop,
elidable=True,
),
]

View File

@@ -0,0 +1,15 @@
# Generated by Django 5.2.3 on 2025-07-04 19:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("confirmation", "0016_realmcreationkey_to_realmcreationstatus"),
]
operations = [
migrations.DeleteModel(
name="RealmCreationKey",
),
]