mirror of
https://github.com/django/django.git
synced 2025-10-24 06:06:09 +00:00
Fixed #22397 -- Issues removing M2M field with explicit through model
Changed the migration autodetector to remove models last so that FK and M2M fields will not be left as dangling references. Added a check in the migration state renderer to error out in the presence of dangling references instead of leaving them as strings. Fixed a bug in the sqlite backend to handle the deletion of M2M fields with "through" models properly (i.e., do nothing successfully). Thanks to melinath for report, loic for tests and andrewgodwin and charettes for assistance with architecture.
This commit is contained in:
committed by
Simon Charette
parent
26d118c3fe
commit
956bd64424
@@ -1,9 +1,12 @@
|
||||
import unittest
|
||||
from django.db import connection, models, migrations, router
|
||||
|
||||
from django.db import connection, migrations, models, router
|
||||
from django.db.migrations.migration import Migration
|
||||
from django.db.migrations.state import ProjectState
|
||||
from django.db.models.fields import NOT_PROVIDED
|
||||
from django.db.transaction import atomic
|
||||
from django.db.utils import IntegrityError
|
||||
from django.db.migrations.state import ProjectState
|
||||
|
||||
from .test_base import MigrationTestBase
|
||||
|
||||
|
||||
@@ -15,15 +18,16 @@ class OperationTests(MigrationTestBase):
|
||||
"""
|
||||
|
||||
def apply_operations(self, app_label, project_state, operations):
|
||||
new_state = project_state.clone()
|
||||
for operation in operations:
|
||||
operation.state_forwards(app_label, new_state)
|
||||
|
||||
# Set up the database
|
||||
migration = Migration('name', app_label)
|
||||
migration.operations = operations
|
||||
with connection.schema_editor() as editor:
|
||||
for operation in operations:
|
||||
operation.database_forwards(app_label, editor, project_state, new_state)
|
||||
return new_state
|
||||
return migration.apply(project_state, editor)
|
||||
|
||||
def unapply_operations(self, app_label, project_state, operations):
|
||||
migration = Migration('name', app_label)
|
||||
migration.operations = operations
|
||||
with connection.schema_editor() as editor:
|
||||
return migration.unapply(project_state, editor)
|
||||
|
||||
def set_up_test_model(self, app_label, second_model=False, related_model=False, mti_model=False):
|
||||
"""
|
||||
@@ -396,6 +400,38 @@ class OperationTests(MigrationTestBase):
|
||||
Pony = new_apps.get_model("test_alflmm", "Pony")
|
||||
self.assertTrue(Pony._meta.get_field('stables').blank)
|
||||
|
||||
def test_remove_field_m2m(self):
|
||||
project_state = self.set_up_test_model("test_rmflmm", second_model=True)
|
||||
|
||||
project_state = self.apply_operations("test_rmflmm", project_state, operations=[
|
||||
migrations.AddField("Pony", "stables", models.ManyToManyField("Stable", related_name="ponies"))
|
||||
])
|
||||
self.assertTableExists("test_rmflmm_pony_stables")
|
||||
|
||||
operations = [migrations.RemoveField("Pony", "stables")]
|
||||
self.apply_operations("test_rmflmm", project_state, operations=operations)
|
||||
self.assertTableNotExists("test_rmflmm_pony_stables")
|
||||
|
||||
# And test reversal
|
||||
self.unapply_operations("test_rmflmm", project_state, operations=operations)
|
||||
self.assertTableExists("test_rmflmm_pony_stables")
|
||||
|
||||
def test_remove_field_m2m_with_through(self):
|
||||
project_state = self.set_up_test_model("test_rmflmmwt", second_model=True)
|
||||
|
||||
self.assertTableNotExists("test_rmflmmwt_ponystables")
|
||||
project_state = self.apply_operations("test_rmflmmwt", project_state, operations=[
|
||||
migrations.CreateModel("PonyStables", fields=[
|
||||
("pony", models.ForeignKey('test_rmflmmwt.Pony')),
|
||||
("stable", models.ForeignKey('test_rmflmmwt.Stable')),
|
||||
]),
|
||||
migrations.AddField("Pony", "stables", models.ManyToManyField("Stable", related_name="ponies", through='test_rmflmmwt.PonyStables'))
|
||||
])
|
||||
self.assertTableExists("test_rmflmmwt_ponystables")
|
||||
|
||||
operations = [migrations.RemoveField("Pony", "stables")]
|
||||
self.apply_operations("test_rmflmmwt", project_state, operations=operations)
|
||||
|
||||
def test_remove_field(self):
|
||||
"""
|
||||
Tests the RemoveField operation.
|
||||
|
||||
Reference in New Issue
Block a user