Skip to content

Commit

Permalink
fix(migrations): Allow m2m columns to be deleted without first making…
Browse files Browse the repository at this point in the history
… them not null (#81209)

These columns don't actually exist on the table, they just reference a
bridging table.
  • Loading branch information
wedamija authored Nov 22, 2024
1 parent 942eb63 commit a89abbe
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from django.db import migrations, models

from sentry.db.models import FlexibleForeignKey
from sentry.new_migrations.migrations import CheckedMigration


class Migration(CheckedMigration):

initial = True
checked = False

dependencies = []

operations = [
migrations.CreateModel(
name="OtherTable",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False)),
],
),
migrations.CreateModel(
name="M2MTable",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False)),
(
"alert_rule",
FlexibleForeignKey(
on_delete=models.deletion.CASCADE,
to="good_flow_delete_field_pending_with_not_null_m2m_app.othertable",
),
),
],
),
migrations.CreateModel(
name="TestTable",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False)),
(
"excluded_projects",
models.ManyToManyField(
through="good_flow_delete_field_pending_with_not_null_m2m_app.M2MTable",
to="good_flow_delete_field_pending_with_not_null_m2m_app.othertable",
),
),
],
),
migrations.AddField(
model_name="m2mtable",
name="test_table",
field=FlexibleForeignKey(
on_delete=models.deletion.CASCADE,
to="good_flow_delete_field_pending_with_not_null_m2m_app.testtable",
),
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from sentry.new_migrations.migrations import CheckedMigration
from sentry.new_migrations.monkey.fields import SafeRemoveField
from sentry.new_migrations.monkey.state import DeletionAction


class Migration(CheckedMigration):
dependencies = [
("good_flow_delete_field_pending_with_not_null_m2m_app", "0001_initial"),
]

operations = [
SafeRemoveField(
model_name="testtable",
name="excluded_projects",
deletion_action=DeletionAction.MOVE_TO_PENDING,
),
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from django.db import models

from sentry.db.models import FlexibleForeignKey


class OtherTable(models.Model):
pass


class M2MTable(models.Model):
alert_rule = FlexibleForeignKey(OtherTable)
test_table = FlexibleForeignKey(
"good_flow_delete_field_pending_with_not_null_m2m_app.TestTable"
)


class TestTable(models.Model):
excluded_projects = models.ManyToManyField(OtherTable, through=M2MTable)
8 changes: 6 additions & 2 deletions src/sentry/new_migrations/monkey/fields.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django.db.migrations import RemoveField
from django.db.models import Field
from django.db.models import Field, ManyToManyField
from django.db.models.fields import NOT_PROVIDED
from django_zero_downtime_migrations.backends.postgres.schema import UnsafeOperationException

Expand Down Expand Up @@ -37,7 +37,11 @@ def state_forwards(self, app_label: str, state: SentryProjectState) -> None: #
f"Foreign key db constraint must be removed before dropping {app_label}.{self.model_name_lower}.{self.name}. "
"More info: https://develop.sentry.dev/api-server/application-domains/database-migrations/#deleting-columns"
)
if not field.null and field.db_default is NOT_PROVIDED:
if (
not isinstance(field, ManyToManyField)
and not field.null
and field.db_default is NOT_PROVIDED
):
raise UnsafeOperationException(
f"Field {app_label}.{self.model_name_lower}.{self.name} must either be nullable or have a db_default before dropping. "
"More info: https://develop.sentry.dev/api-server/application-domains/database-migrations/#deleting-columns"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,15 @@ def test(self):
self.run_migration()


class DeletionFieldGoodDeletePendingWithNotNullM2M(BaseSafeMigrationTest):
app = "good_flow_delete_field_pending_with_not_null_m2m_app"
migrate_from = "0001"
migrate_to = "0002"

def test(self):
self.run_migration()


class ColExistsMixin:
app = ""

Expand Down

0 comments on commit a89abbe

Please sign in to comment.