Files
upstream-zulip/zerver/migrations/0804_backfill_user_created_audit_logs.py
upstream-mirror 64898b2c79
Some checks failed
Code scanning / CodeQL (push) Failing after 15s
Zulip production suite / Ubuntu 22.04 production build (push) Failing after 26s
Zulip production suite / Debian 12 production install with custom db name and user (push) Has been skipped
Zulip production suite / Ubuntu 22.04 production install and PostgreSQL upgrade with pgroonga (push) Has been skipped
Zulip production suite / Ubuntu 24.04 production install (push) Has been skipped
Zulip production suite / Ubuntu 26.04 production install (push) Has been skipped
Zulip production suite / Debian 13 production install (push) Has been skipped
Zulip production suite / 7.0 Version Upgrade (push) Has been skipped
Zulip production suite / 8.0 Version Upgrade (push) Has been skipped
Zulip production suite / 6.0 Version Upgrade (push) Has been skipped
Zulip production suite / 10.0 Version Upgrade (push) Has been skipped
Zulip production suite / 9.0 Version Upgrade (push) Has been skipped
Zulip production suite / 12.0 Version Upgrade (push) Has been skipped
Zulip production suite / 11.0 Version Upgrade (push) Has been skipped
GitHub Actions Security Analysis with zizmor / zizmor (push) Failing after 3s
Zulip CI / Debian 12 (Python 3.11, backend + documentation) (push) Failing after 26s
Zulip CI / Ubuntu 22.04 (Python 3.10, backend + frontend) (push) Failing after 3s
Zulip CI / Ubuntu 24.04 (Python 3.12, backend) (push) Failing after 25s
Zulip CI / Ubuntu 26.04 (Python 3.14, backend) (push) Failing after 25s
Zulip CI / Debian 13 (Python 3.13, backend) (push) Failing after 28s
Zulip production suite / Required jobs (push) Has been cancelled
Zulip CI / Required jobs (push) Has been cancelled
API Documentation Update Check / check-feature-level-updated (push) Has been cancelled
API Documentation Update Check / notify-if-api-docs-changed (push) Has been cancelled
snapshot of https://github.com/zulip/zulip.git @ 48c69911c7dd4e03b20552eb3bd77ad8151c1357 (2026-09-15)
2026-09-15 15:05:30 +05:00

121 lines
4.8 KiB
Python

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 Exists, OuterRef
from django.utils.timezone import now as timezone_now
def backfill_missing_user_created_deactivated_events(
apps: StateApps, schema_editor: BaseDatabaseSchemaEditor
) -> None:
"""Backfill USER_CREATED and USER_DEACTIVATED RealmAuditLog entries for
users that lack them, presumably due to having been imported via a
third-party importer such as Slack before create_audit_logs_for_users was
added to the import system.
"""
Realm = apps.get_model("zerver", "Realm")
UserProfile = apps.get_model("zerver", "UserProfile")
RealmAuditLog = apps.get_model("zerver", "RealmAuditLog")
USER_CREATED = 101
USER_ACTIVATED = 102
USER_DEACTIVATED = 103
USER_REACTIVATED = 104
for realm in Realm.objects.all().iterator():
with transaction.atomic():
has_user_created_event = Exists(
RealmAuditLog.objects.filter(
realm=realm,
event_type=USER_CREATED,
modified_user_id=OuterRef("id"),
)
)
users_missing_created_event = list(
UserProfile.objects.filter(realm=realm)
.filter(~has_user_created_event)
.values("id", "date_joined", "is_active")
)
if not users_missing_created_event:
continue
inactive_user_ids = [
user["id"] for user in users_missing_created_event if not user["is_active"]
]
if inactive_user_ids:
# For inactive users, we also need to figure out if they are missing
# a USER_DEACTIVATED record. We check that by finding the latest event
# about activations and verifying whether it is a USER_DEACTIVATED
# event.
#
# Note: We are purposely only searching in the subset of users that were
# already missing the USER_CREATED event - as those are the users affected
# by the bug in imports from 3rd party tools.
# This migration is NOT intended to audit the integrity of the activation
# events sequencing in general. The scope of this backfill is limited
# to the specific importer bug.
latest_activation_event_type = {
row["modified_user_id"]: row["event_type"]
for row in RealmAuditLog.objects.filter(
realm=realm,
modified_user_id__in=inactive_user_ids,
event_type__in=[USER_ACTIVATED, USER_DEACTIVATED, USER_REACTIVATED],
)
.order_by("modified_user_id", "-event_time", "-id")
.distinct("modified_user_id")
.values("modified_user_id", "event_type")
}
else:
latest_activation_event_type = {}
now = timezone_now()
audit_logs_to_backfill = []
for user in users_missing_created_event:
audit_logs_to_backfill.append(
RealmAuditLog(
realm_id=realm.id,
modified_user_id=user["id"],
event_type=USER_CREATED,
event_time=user["date_joined"],
backfilled=True,
)
)
if user["is_active"]:
continue
if latest_activation_event_type.get(user["id"]) == USER_DEACTIVATED:
# If the last activation event is USER_DEACTIVATED, then the state
# is correct already.
continue
audit_logs_to_backfill.append(
RealmAuditLog(
realm_id=realm.id,
modified_user_id=user["id"],
event_type=USER_DEACTIVATED,
event_time=now,
backfilled=True,
)
)
print(
f"Backfilling {len(audit_logs_to_backfill)} audit log entries for "
f"{len(users_missing_created_event)} users in realm {realm.string_id}"
)
RealmAuditLog.objects.bulk_create(audit_logs_to_backfill, batch_size=1000)
class Migration(migrations.Migration):
atomic = False
dependencies = [
("zerver", "0803_delete_orphan_recipients_and_huddles"),
]
operations = [
migrations.RunPython(
backfill_missing_user_created_deactivated_events,
reverse_code=migrations.RunPython.noop,
elidable=True,
)
]