summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarkus Holtermann <info@markusholtermann.eu>2015-04-03 18:42:44 +0200
committerMarkus Holtermann <info@markusholtermann.eu>2015-04-21 12:06:11 +0200
commit0cacb8f8ba94b06a78dcdfa22dc406a8a20a7f88 (patch)
treee83617279600d789bf791ebdbb9b9d8a6b55a5cd
parent697317f3340b1d1f40586262098ad4dbae9aebaf (diff)
[1.8.x] Fixed #24573 -- Considered new related models for reloading
Thanks tttomekkk for the report. Backport of b93690c4652a108b4305c0df34509bfc267fd927 from master
-rw-r--r--django/db/migrations/state.py15
-rw-r--r--docs/releases/1.8.1.txt3
-rw-r--r--tests/migrations/test_state.py53
3 files changed, 69 insertions, 2 deletions
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index 5754bd3a97..ad2adc57e8 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -101,12 +101,25 @@ class ProjectState(object):
# Get all outgoing references from the model to be rendered
model_state = self.models[(app_label, model_name)]
+ # Directly related models are the models pointed to by ForeignKeys,
+ # OneToOneFields, and ManyToManyFields.
+ direct_related_models = set()
for name, field in model_state.fields:
if field.is_relation:
if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
continue
rel_app_label, rel_model_name = _get_app_label_and_model_name(field.rel.to, app_label)
- related_models.add((rel_app_label, rel_model_name.lower()))
+ direct_related_models.add((rel_app_label, rel_model_name.lower()))
+
+ # For all direct related models recursively get all related models.
+ related_models.update(direct_related_models)
+ for rel_app_label, rel_model_name in direct_related_models:
+ try:
+ rel_model = self.apps.get_model(rel_app_label, rel_model_name)
+ except LookupError:
+ pass
+ else:
+ related_models.update(get_related_models_recursive(rel_model))
# Include the model itself
related_models.add((app_label, model_name))
diff --git a/docs/releases/1.8.1.txt b/docs/releases/1.8.1.txt
index b226b0928c..54382d5c33 100644
--- a/docs/releases/1.8.1.txt
+++ b/docs/releases/1.8.1.txt
@@ -63,6 +63,9 @@ Bugfixes
* Fixed JavaScript path of ``contrib.admin``’s related field widget when using
alternate static file storages (:ticket:`24655`).
+* Fixed a migration crash when adding new relations to models
+ (:ticket:`24573`).
+
Optimizations
=============
diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py
index 663815d6f9..33016fdfce 100644
--- a/tests/migrations/test_state.py
+++ b/tests/migrations/test_state.py
@@ -1,7 +1,7 @@
from django.apps.registry import Apps
from django.db import models
from django.db.migrations.operations import (
- AlterField, DeleteModel, RemoveField,
+ AddField, AlterField, DeleteModel, RemoveField,
)
from django.db.migrations.state import (
InvalidBasesError, ModelState, ProjectState, get_related_models_recursive,
@@ -368,6 +368,57 @@ class StateTests(TestCase):
project_state.add_model(ModelState.from_model(B))
self.assertEqual(len(project_state.apps.get_models()), 2)
+ def test_add_relations(self):
+ """
+ #24573 - Adding relations to existing models should reload the
+ referenced models too.
+ """
+ class A(models.Model):
+ class Meta:
+ app_label = 'something'
+
+ class B(A):
+ class Meta:
+ app_label = 'something'
+
+ class C(models.Model):
+ class Meta:
+ app_label = 'something'
+
+ project_state = ProjectState()
+ project_state.add_model(ModelState.from_model(A))
+ project_state.add_model(ModelState.from_model(B))
+ project_state.add_model(ModelState.from_model(C))
+
+ project_state.apps # We need to work with rendered models
+
+ old_state = project_state.clone()
+ model_a_old = old_state.apps.get_model('something', 'A')
+ model_b_old = old_state.apps.get_model('something', 'B')
+ model_c_old = old_state.apps.get_model('something', 'C')
+ # Check that the relations between the old models are correct
+ self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old)
+ self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old)
+
+ operation = AddField('c', 'to_a', models.OneToOneField('something.A', related_name='from_c'))
+ operation.state_forwards('something', project_state)
+ model_a_new = project_state.apps.get_model('something', 'A')
+ model_b_new = project_state.apps.get_model('something', 'B')
+ model_c_new = project_state.apps.get_model('something', 'C')
+
+ # Check that all models have changed
+ self.assertIsNot(model_a_old, model_a_new)
+ self.assertIsNot(model_b_old, model_b_new)
+ self.assertIsNot(model_c_old, model_c_new)
+ # Check that the relations between the old models still hold
+ self.assertIs(model_a_old._meta.get_field('b').related_model, model_b_old)
+ self.assertIs(model_b_old._meta.get_field('a_ptr').related_model, model_a_old)
+ # Check that the relations between the new models correct
+ self.assertIs(model_a_new._meta.get_field('b').related_model, model_b_new)
+ self.assertIs(model_b_new._meta.get_field('a_ptr').related_model, model_a_new)
+ self.assertIs(model_a_new._meta.get_field('from_c').related_model, model_c_new)
+ self.assertIs(model_c_new._meta.get_field('to_a').related_model, model_a_new)
+
def test_remove_relations(self):
"""
#24225 - Tests that relations between models are updated while