Revert "[1.7.x] Fixed #23938 -- Added migration support for m2m to concrete fields and vice versa"
This reverts commit 1702bc52cc20ed0729893177fc8f4391b4b3183c. This doesn't work on stable/1.7.x because #23844 wasn't backported and we're not willing to do so because it's a large change.
This commit is contained in:
parent
1702bc52cc
commit
79645529e7
@ -778,71 +778,73 @@ class MigrationAutodetector(object):
|
|||||||
Fields that have been added
|
Fields that have been added
|
||||||
"""
|
"""
|
||||||
for app_label, model_name, field_name in sorted(self.new_field_keys - self.old_field_keys):
|
for app_label, model_name, field_name in sorted(self.new_field_keys - self.old_field_keys):
|
||||||
self._generate_added_field(app_label, model_name, field_name)
|
field = self.new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
|
||||||
|
# Fields that are foreignkeys/m2ms depend on stuff
|
||||||
def _generate_added_field(self, app_label, model_name, field_name):
|
dependencies = []
|
||||||
field = self.new_apps.get_model(app_label, model_name)._meta.get_field_by_name(field_name)[0]
|
if field.rel and field.rel.to:
|
||||||
# Fields that are foreignkeys/m2ms depend on stuff
|
# Account for FKs to swappable models
|
||||||
dependencies = []
|
swappable_setting = getattr(field, 'swappable_setting', None)
|
||||||
if field.rel and field.rel.to:
|
if swappable_setting is not None:
|
||||||
# Account for FKs to swappable models
|
dep_app_label = "__setting__"
|
||||||
swappable_setting = getattr(field, 'swappable_setting', None)
|
dep_object_name = swappable_setting
|
||||||
if swappable_setting is not None:
|
else:
|
||||||
dep_app_label = "__setting__"
|
dep_app_label = field.rel.to._meta.app_label
|
||||||
dep_object_name = swappable_setting
|
dep_object_name = field.rel.to._meta.object_name
|
||||||
|
dependencies = [(dep_app_label, dep_object_name, None, True)]
|
||||||
|
if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created:
|
||||||
|
dependencies.append((
|
||||||
|
field.rel.through._meta.app_label,
|
||||||
|
field.rel.through._meta.object_name,
|
||||||
|
None,
|
||||||
|
True
|
||||||
|
))
|
||||||
|
# You can't just add NOT NULL fields with no default or fields
|
||||||
|
# which don't allow empty strings as default.
|
||||||
|
if (not field.null and not field.has_default() and
|
||||||
|
not isinstance(field, models.ManyToManyField) and
|
||||||
|
not (field.blank and field.empty_strings_allowed)):
|
||||||
|
field = field.clone()
|
||||||
|
field.default = self.questioner.ask_not_null_addition(field_name, model_name)
|
||||||
|
self.add_operation(
|
||||||
|
app_label,
|
||||||
|
operations.AddField(
|
||||||
|
model_name=model_name,
|
||||||
|
name=field_name,
|
||||||
|
field=field,
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
dependencies=dependencies,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
dep_app_label = field.rel.to._meta.app_label
|
self.add_operation(
|
||||||
dep_object_name = field.rel.to._meta.object_name
|
app_label,
|
||||||
dependencies = [(dep_app_label, dep_object_name, None, True)]
|
operations.AddField(
|
||||||
if getattr(field.rel, "through", None) and not field.rel.through._meta.auto_created:
|
model_name=model_name,
|
||||||
dependencies.append((
|
name=field_name,
|
||||||
field.rel.through._meta.app_label,
|
field=field,
|
||||||
field.rel.through._meta.object_name,
|
),
|
||||||
None,
|
dependencies=dependencies,
|
||||||
True,
|
)
|
||||||
))
|
|
||||||
# You can't just add NOT NULL fields with no default or fields
|
|
||||||
# which don't allow empty strings as default.
|
|
||||||
preserve_default = True
|
|
||||||
if (not field.null and not field.has_default() and
|
|
||||||
not isinstance(field, models.ManyToManyField) and
|
|
||||||
not (field.blank and field.empty_strings_allowed)):
|
|
||||||
field = field.clone()
|
|
||||||
field.default = self.questioner.ask_not_null_addition(field_name, model_name)
|
|
||||||
preserve_default = False
|
|
||||||
self.add_operation(
|
|
||||||
app_label,
|
|
||||||
operations.AddField(
|
|
||||||
model_name=model_name,
|
|
||||||
name=field_name,
|
|
||||||
field=field,
|
|
||||||
preserve_default=preserve_default,
|
|
||||||
),
|
|
||||||
dependencies=dependencies,
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_removed_fields(self):
|
def generate_removed_fields(self):
|
||||||
"""
|
"""
|
||||||
Fields that have been removed.
|
Fields that have been removed.
|
||||||
"""
|
"""
|
||||||
for app_label, model_name, field_name in sorted(self.old_field_keys - self.new_field_keys):
|
for app_label, model_name, field_name in sorted(self.old_field_keys - self.new_field_keys):
|
||||||
self._generate_removed_field(app_label, model_name, field_name)
|
self.add_operation(
|
||||||
|
app_label,
|
||||||
def _generate_removed_field(self, app_label, model_name, field_name):
|
operations.RemoveField(
|
||||||
self.add_operation(
|
model_name=model_name,
|
||||||
app_label,
|
name=field_name,
|
||||||
operations.RemoveField(
|
),
|
||||||
model_name=model_name,
|
# We might need to depend on the removal of an
|
||||||
name=field_name,
|
# order_with_respect_to or index/unique_together operation;
|
||||||
),
|
# this is safely ignored if there isn't one
|
||||||
# We might need to depend on the removal of an
|
dependencies=[
|
||||||
# order_with_respect_to or index/unique_together operation;
|
(app_label, model_name, field_name, "order_wrt_unset"),
|
||||||
# this is safely ignored if there isn't one
|
(app_label, model_name, field_name, "foo_together_change"),
|
||||||
dependencies=[
|
],
|
||||||
(app_label, model_name, field_name, "order_wrt_unset"),
|
)
|
||||||
(app_label, model_name, field_name, "foo_together_change"),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
def generate_altered_fields(self):
|
def generate_altered_fields(self):
|
||||||
"""
|
"""
|
||||||
@ -866,30 +868,25 @@ class MigrationAutodetector(object):
|
|||||||
old_field_dec = self.deep_deconstruct(old_field)
|
old_field_dec = self.deep_deconstruct(old_field)
|
||||||
new_field_dec = self.deep_deconstruct(new_field)
|
new_field_dec = self.deep_deconstruct(new_field)
|
||||||
if old_field_dec != new_field_dec:
|
if old_field_dec != new_field_dec:
|
||||||
if (not isinstance(old_field, models.ManyToManyField) and
|
preserve_default = True
|
||||||
|
if (old_field.null and not new_field.null and not new_field.has_default() and
|
||||||
not isinstance(new_field, models.ManyToManyField)):
|
not isinstance(new_field, models.ManyToManyField)):
|
||||||
preserve_default = True
|
field = new_field.clone()
|
||||||
if (old_field.null and not new_field.null and not new_field.has_default() and
|
new_default = self.questioner.ask_not_null_alteration(field_name, model_name)
|
||||||
not isinstance(new_field, models.ManyToManyField)):
|
if new_default is not models.NOT_PROVIDED:
|
||||||
field = new_field.clone()
|
field.default = new_default
|
||||||
new_default = self.questioner.ask_not_null_alteration(field_name, model_name)
|
preserve_default = False
|
||||||
if new_default is not models.NOT_PROVIDED:
|
|
||||||
field.default = new_default
|
|
||||||
preserve_default = False
|
|
||||||
else:
|
|
||||||
field = new_field
|
|
||||||
self.add_operation(
|
|
||||||
app_label,
|
|
||||||
operations.AlterField(
|
|
||||||
model_name=model_name,
|
|
||||||
name=field_name,
|
|
||||||
field=field,
|
|
||||||
preserve_default=preserve_default,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
self._generate_removed_field(app_label, model_name, field_name)
|
field = new_field
|
||||||
self._generate_added_field(app_label, model_name, field_name)
|
self.add_operation(
|
||||||
|
app_label,
|
||||||
|
operations.AlterField(
|
||||||
|
model_name=model_name,
|
||||||
|
name=field_name,
|
||||||
|
field=field,
|
||||||
|
preserve_default=preserve_default,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
def _generate_altered_foo_together(self, operation):
|
def _generate_altered_foo_together(self, operation):
|
||||||
option_name = operation.option_name
|
option_name = operation.option_name
|
||||||
|
@ -181,6 +181,3 @@ Bugfixes
|
|||||||
|
|
||||||
* Supported strings escaped by third-party libraries with the ``__html__``
|
* Supported strings escaped by third-party libraries with the ``__html__``
|
||||||
convention in the template engine (:ticket:`23831`).
|
convention in the template engine (:ticket:`23831`).
|
||||||
|
|
||||||
* Fixed a migration crash when changing a ``ManyToManyField`` into a concrete
|
|
||||||
field and vice versa (:ticket:`23938`).
|
|
||||||
|
@ -116,10 +116,6 @@ class AutodetectorTests(TestCase):
|
|||||||
("id", models.AutoField(primary_key=True)),
|
("id", models.AutoField(primary_key=True)),
|
||||||
("publishers", models.ManyToManyField("testapp.Publisher", through="testapp.Contract")),
|
("publishers", models.ManyToManyField("testapp.Publisher", through="testapp.Contract")),
|
||||||
])
|
])
|
||||||
author_with_former_m2m = ModelState("testapp", "Author", [
|
|
||||||
("id", models.AutoField(primary_key=True)),
|
|
||||||
("publishers", models.CharField(max_length=100)),
|
|
||||||
])
|
|
||||||
author_with_options = ModelState("testapp", "Author", [
|
author_with_options = ModelState("testapp", "Author", [
|
||||||
("id", models.AutoField(primary_key=True)),
|
("id", models.AutoField(primary_key=True)),
|
||||||
], {
|
], {
|
||||||
@ -1278,39 +1274,6 @@ class AutodetectorTests(TestCase):
|
|||||||
self.assertOperationAttributes(changes, "testapp", 0, 3, name="publisher", model_name='contract')
|
self.assertOperationAttributes(changes, "testapp", 0, 3, name="publisher", model_name='contract')
|
||||||
self.assertOperationAttributes(changes, "testapp", 0, 4, name="Contract")
|
self.assertOperationAttributes(changes, "testapp", 0, 4, name="Contract")
|
||||||
|
|
||||||
def test_concrete_field_changed_to_many_to_many(self):
|
|
||||||
"""
|
|
||||||
#23938 - Tests that changing a concrete field into a ManyToManyField
|
|
||||||
first removes the concrete field and then adds the m2m field.
|
|
||||||
"""
|
|
||||||
before = self.make_project_state([self.author_with_former_m2m])
|
|
||||||
after = self.make_project_state([self.author_with_m2m, self.publisher])
|
|
||||||
autodetector = MigrationAutodetector(before, after)
|
|
||||||
changes = autodetector._detect_changes()
|
|
||||||
# Right number/type of migrations?
|
|
||||||
self.assertNumberMigrations(changes, "testapp", 1)
|
|
||||||
self.assertOperationTypes(changes, "testapp", 0, ["CreateModel", "RemoveField", "AddField"])
|
|
||||||
self.assertOperationAttributes(changes, 'testapp', 0, 0, name='Publisher')
|
|
||||||
self.assertOperationAttributes(changes, 'testapp', 0, 1, name="publishers", model_name='author')
|
|
||||||
self.assertOperationAttributes(changes, 'testapp', 0, 2, name="publishers", model_name='author')
|
|
||||||
|
|
||||||
def test_many_to_many_changed_to_concrete_field(self):
|
|
||||||
"""
|
|
||||||
#23938 - Tests that changing a ManyToManyField into a concrete field
|
|
||||||
first removes the m2m field and then adds the concrete field.
|
|
||||||
"""
|
|
||||||
before = self.make_project_state([self.author_with_m2m, self.publisher])
|
|
||||||
after = self.make_project_state([self.author_with_former_m2m])
|
|
||||||
autodetector = MigrationAutodetector(before, after)
|
|
||||||
changes = autodetector._detect_changes()
|
|
||||||
# Right number/type of migrations?
|
|
||||||
self.assertNumberMigrations(changes, "testapp", 1)
|
|
||||||
self.assertOperationTypes(changes, "testapp", 0, ["RemoveField", "AddField", "DeleteModel"])
|
|
||||||
self.assertOperationAttributes(changes, 'testapp', 0, 0, name="publishers", model_name='author')
|
|
||||||
self.assertOperationAttributes(changes, 'testapp', 0, 1, name="publishers", model_name='author')
|
|
||||||
self.assertOperationAttributes(changes, 'testapp', 0, 2, name='Publisher')
|
|
||||||
self.assertOperationFieldAttributes(changes, 'testapp', 0, 1, max_length=100)
|
|
||||||
|
|
||||||
def test_non_circular_foreignkey_dependency_removal(self):
|
def test_non_circular_foreignkey_dependency_removal(self):
|
||||||
"""
|
"""
|
||||||
If two models with a ForeignKey from one to the other are removed at the
|
If two models with a ForeignKey from one to the other are removed at the
|
||||||
|
Loading…
x
Reference in New Issue
Block a user