Generate Django database migrations for Sentry. Use when creating migrations, adding/removing columns or tables, adding indexes, or resolving migration conflicts.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Generate migrations automatically based on model changes:
sentry django makemigrationsFor a specific app:
sentry django makemigrations <app_name>Generate an empty migration (for data migrations or custom work):
sentry django makemigrations <app_name> --empty__init__.pysentry django sqlmigrate <app_name> <migration_name> to verify the SQLsentry django migrate <app_name> — Sentry's migration framework runs its safety checks on apply, so this catches unsafe ops (missing is_post_deployment, unsafe column changes, etc.) before CI does.When editing a generated migration (e.g. swapping DeleteModel for SafeDeleteModel), leave the auto-generated is_post_deployment comment block in place. It documents a non-obvious flag with concrete guidance for future migration authors — useful context, not fluff. Only remove a comment if it's stale or contradicts the code.
Don't write tests that only exercise Django's ORM. Standard operations — create/update/delete, cascading deletes, unique-constraint enforcement — are provided by Django and Postgres and are assumed to work. Test your logic (business rules, signal receivers, custom managers/validation), not the framework's.
The exception to the above: a migration that backfills or transforms data is your logic, and it must have a test. Use the TestMigrations base class from sentry.testutils.cases; tests live in tests/sentry/migrations/.
Set app, migrate_from (the migration just before yours), and migrate_to (yours). Seed pre-migration rows in setup_before_migration(self, apps) using the historical model registry (apps.get_model("sentry", "MyModel")) — not a direct from sentry.models... import, since the current model may not match the schema at migrate_from. Then assert the post-migration state.
Write exactly one test_* method. setUp runs the full migrate-down → seed → migrate-up cycle on every test method, so each extra method pays for another round trip with no added coverage. Cover multiple cases by seeding all of them in setup_before_migration and asserting each in the single test body.
from sentry.testutils.cases import TestMigrations
class BackfillFooTest(TestMigrations):
app = "sentry"
migrate_from = "0123_before"
migrate_to = "0124_backfill_foo"
def setup_before_migration(self, apps):
Foo = apps.get_model("sentry", "Foo")
self.empty = Foo.objects.create(value=None)
self.already_set = Foo.objects.create(value="kept")
def test_backfill(self):
self.empty.refresh_from_db()
self.already_set.refresh_from_db()
assert self.empty.value == "expected"
assert self.already_set.value == "kept"app and connection: app is the Django app label whose migration you're testing — "sentry" by default, but set it to e.g. "workflow_engine" when the migration lives in that app's migrations/ directory. connection is the database alias, "default" by default; set it to whichever connection the model's table actually lives on. Both must match where the migration and its tables actually live, or the migrate up/down will run against the wrong database.
Run these tests locally with the --migrations and --reuse-db flags. On the first run, it will be necessary to use --create-db along with --reuse-db to get the database in a good state.
db_default=<value> instead of default=<value> for columns with defaultsnull=Truedb_default setFor large tables, set is_post_deployment = True on the migration as index creation may exceed the 5s timeout.
null=True) if not alreadyRemoveField with SafeRemoveField(..., deletion_action=DeletionAction.MOVE_TO_PENDING)SafeRemoveField(..., deletion_action=DeletionAction.DELETE)Two-phase process — the historical_silo_assignments entry must be added in phase 1.
Phase 1 — Remove the model class (MOVE_TO_PENDING)
DeleteModel with SafeDeleteModel(..., deletion_action=DeletionAction.MOVE_TO_PENDING)historical_silo_assignments in src/sentry/db/router.py (or getsentry/db/router.py for getsentry models). Pick the silo the model used — usually SiloMode.CELL.Phase 2 — Drop the table (DELETE)
After phase 1 has deployed, create a second migration with SafeDeleteModel(..., deletion_action=DeletionAction.DELETE). Leave the historical entry in place — the table-drop migration relies on it to resolve the silo.
Don't rename in Postgres. Use db_column or Meta.db_table to keep the old name.
If migrations_lockfile.txt conflicts:
bin/update-migration <migration_name>This renames your migration, updates dependencies, and fixes the lockfile.
b4b3565
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.