Thanks to bendavis78 for the test and diagnostic work.
This commit is contained in:
parent
9980f67154
commit
7b17350a1b
|
@ -44,11 +44,13 @@ class ProjectState(object):
|
||||||
# Any apps in self.real_apps should have all their models included
|
# Any apps in self.real_apps should have all their models included
|
||||||
# in the render. We don't use the original model instances as there
|
# in the render. We don't use the original model instances as there
|
||||||
# are some variables that refer to the Apps object.
|
# are some variables that refer to the Apps object.
|
||||||
|
# FKs/M2Ms from real apps are also not included as they just
|
||||||
|
# mess things up with partial states (due to lack of dependencies)
|
||||||
real_models = []
|
real_models = []
|
||||||
for app_label in self.real_apps:
|
for app_label in self.real_apps:
|
||||||
app = global_apps.get_app_config(app_label)
|
app = global_apps.get_app_config(app_label)
|
||||||
for model in app.get_models():
|
for model in app.get_models():
|
||||||
real_models.append(ModelState.from_model(model))
|
real_models.append(ModelState.from_model(model, exclude_rels=True))
|
||||||
# Populate the app registry with a stub for each application.
|
# Populate the app registry with a stub for each application.
|
||||||
app_labels = set(model_state.app_label for model_state in self.models.values())
|
app_labels = set(model_state.app_label for model_state in self.models.values())
|
||||||
self.apps = Apps([AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels))])
|
self.apps = Apps([AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels))])
|
||||||
|
@ -155,13 +157,15 @@ class ModelState(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_model(cls, model):
|
def from_model(cls, model, exclude_rels=False):
|
||||||
"""
|
"""
|
||||||
Feed me a model, get a ModelState representing it out.
|
Feed me a model, get a ModelState representing it out.
|
||||||
"""
|
"""
|
||||||
# Deconstruct the fields
|
# Deconstruct the fields
|
||||||
fields = []
|
fields = []
|
||||||
for field in model._meta.local_fields:
|
for field in model._meta.local_fields:
|
||||||
|
if getattr(field, "rel", None) and exclude_rels:
|
||||||
|
continue
|
||||||
name, path, args, kwargs = field.deconstruct()
|
name, path, args, kwargs = field.deconstruct()
|
||||||
field_class = import_string(path)
|
field_class = import_string(path)
|
||||||
try:
|
try:
|
||||||
|
@ -173,17 +177,18 @@ class ModelState(object):
|
||||||
model._meta.object_name,
|
model._meta.object_name,
|
||||||
e,
|
e,
|
||||||
))
|
))
|
||||||
for field in model._meta.local_many_to_many:
|
if not exclude_rels:
|
||||||
name, path, args, kwargs = field.deconstruct()
|
for field in model._meta.local_many_to_many:
|
||||||
field_class = import_string(path)
|
name, path, args, kwargs = field.deconstruct()
|
||||||
try:
|
field_class = import_string(path)
|
||||||
fields.append((name, field_class(*args, **kwargs)))
|
try:
|
||||||
except TypeError as e:
|
fields.append((name, field_class(*args, **kwargs)))
|
||||||
raise TypeError("Couldn't reconstruct m2m field %s on %s: %s" % (
|
except TypeError as e:
|
||||||
name,
|
raise TypeError("Couldn't reconstruct m2m field %s on %s: %s" % (
|
||||||
model._meta.object_name,
|
name,
|
||||||
e,
|
model._meta.object_name,
|
||||||
))
|
e,
|
||||||
|
))
|
||||||
# Extract the options
|
# Extract the options
|
||||||
options = {}
|
options = {}
|
||||||
for name in DEFAULT_NAMES:
|
for name in DEFAULT_NAMES:
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
|
||||||
|
migrations.CreateModel(
|
||||||
|
"Author",
|
||||||
|
[
|
||||||
|
("id", models.AutoField(primary_key=True)),
|
||||||
|
("name", models.CharField(max_length=255)),
|
||||||
|
("slug", models.SlugField(null=True)),
|
||||||
|
("age", models.IntegerField(default=0)),
|
||||||
|
("silly_field", models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
migrations.CreateModel(
|
||||||
|
"Tribble",
|
||||||
|
[
|
||||||
|
("id", models.AutoField(primary_key=True)),
|
||||||
|
("fluffy", models.BooleanField(default=True)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
]
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
|
||||||
|
migrations.CreateModel(
|
||||||
|
"OtherAuthor",
|
||||||
|
[
|
||||||
|
("id", models.AutoField(primary_key=True)),
|
||||||
|
("name", models.CharField(max_length=255)),
|
||||||
|
("slug", models.SlugField(null=True)),
|
||||||
|
("age", models.IntegerField(default=0)),
|
||||||
|
("silly_field", models.BooleanField(default=False)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
|
||||||
|
]
|
|
@ -0,0 +1,12 @@
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class OtherAuthor(models.Model):
|
||||||
|
id = models.AutoField(primary_key=True)
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
slug = models.SlugField(null=True)
|
||||||
|
age = models.IntegerField(default=0)
|
||||||
|
silly_field = models.BooleanField(default=False)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
app_label = "migrated_unapplied_app"
|
|
@ -0,0 +1,9 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class SillyModel(models.Model):
|
||||||
|
silly_field = models.BooleanField(default=False)
|
||||||
|
silly_tribble = models.ForeignKey("migrations.Tribble")
|
||||||
|
is_trouble = models.BooleanField(default=True)
|
|
@ -102,6 +102,29 @@ class MigrateTests(MigrationTestBase):
|
||||||
call_command("sqlmigrate", "migrations", "0001", stdout=stdout, backwards=True)
|
call_command("sqlmigrate", "migrations", "0001", stdout=stdout, backwards=True)
|
||||||
self.assertIn("drop table", stdout.getvalue().lower())
|
self.assertIn("drop table", stdout.getvalue().lower())
|
||||||
|
|
||||||
|
@override_system_checks([])
|
||||||
|
@override_settings(
|
||||||
|
INSTALLED_APPS=[
|
||||||
|
"migrations.migrations_test_apps.migrated_app",
|
||||||
|
"migrations.migrations_test_apps.migrated_unapplied_app",
|
||||||
|
"migrations.migrations_test_apps.unmigrated_app"])
|
||||||
|
def test_regression_22823_unmigrated_fk_to_migrated_model(self):
|
||||||
|
"""
|
||||||
|
https://code.djangoproject.com/ticket/22823
|
||||||
|
|
||||||
|
Assuming you have 3 apps, `A`, `B`, and `C`, such that:
|
||||||
|
|
||||||
|
* `A` has migrations
|
||||||
|
* `B` has a migration we want to apply
|
||||||
|
* `C` has no migrations, but has an FK to `A`
|
||||||
|
|
||||||
|
When we try to migrate "B", an exception occurs because the
|
||||||
|
"B" was not included in the ProjectState that is used to detect
|
||||||
|
soft-applied migrations.
|
||||||
|
"""
|
||||||
|
stdout = six.StringIO()
|
||||||
|
call_command("migrate", "migrated_unapplied_app", stdout=stdout)
|
||||||
|
|
||||||
|
|
||||||
class MakeMigrationsTests(MigrationTestBase):
|
class MakeMigrationsTests(MigrationTestBase):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue