Permalink
Browse files

[1.7.x] Fixed #22823 (and partly #22563) - FKs from unmigrated apps b…

…reaking state.

Thanks to bendavis78 for the test and diagnostic work.
  • Loading branch information...
1 parent 84714df commit 961c9d6c6b1975fba8eb1492288e0fc49652c43d @andrewgodwin andrewgodwin committed Jun 12, 2014
@@ -44,11 +44,13 @@ def render(self, include_real=None, ignore_swappable=False, skip_cache=False):
# 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
# 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 = []
for app_label in self.real_apps:
app = global_apps.get_app_config(app_label)
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.
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))])
@@ -155,13 +157,15 @@ def __init__(self, app_label, name, fields, options=None, bases=None):
)
@classmethod
- def from_model(cls, model):
+ def from_model(cls, model, exclude_rels=False):
"""
Feed me a model, get a ModelState representing it out.
"""
# Deconstruct the fields
fields = []
for field in model._meta.local_fields:
+ if getattr(field, "rel", None) and exclude_rels:
+ continue
name, path, args, kwargs = field.deconstruct()
field_class = import_string(path)
try:
@@ -173,17 +177,18 @@ def from_model(cls, model):
model._meta.object_name,
e,
))
- for field in model._meta.local_many_to_many:
- name, path, args, kwargs = field.deconstruct()
- field_class = import_string(path)
- try:
- fields.append((name, field_class(*args, **kwargs)))
- except TypeError as e:
- raise TypeError("Couldn't reconstruct m2m field %s on %s: %s" % (
- name,
- model._meta.object_name,
- e,
- ))
+ if not exclude_rels:
+ for field in model._meta.local_many_to_many:
+ name, path, args, kwargs = field.deconstruct()
+ field_class = import_string(path)
+ try:
+ fields.append((name, field_class(*args, **kwargs)))
+ except TypeError as e:
+ raise TypeError("Couldn't reconstruct m2m field %s on %s: %s" % (
+ name,
+ model._meta.object_name,
+ e,
+ ))
# Extract the options
options = {}
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 @@ def test_sqlmigrate(self):
call_command("sqlmigrate", "migrations", "0001", stdout=stdout, backwards=True)
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):
"""

0 comments on commit 961c9d6

Please sign in to comment.