[1.8.x] Fixed #24573 -- Considered new related models for reloading

Thanks tttomekkk for the report.

Backport of b93690c465 from master
This commit is contained in:
Markus Holtermann 2015-04-03 18:42:44 +02:00
parent 697317f334
commit 0cacb8f8ba
3 changed files with 69 additions and 2 deletions

View File

@ -101,12 +101,25 @@ class ProjectState(object):
# Get all outgoing references from the model to be rendered # Get all outgoing references from the model to be rendered
model_state = self.models[(app_label, model_name)] 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: for name, field in model_state.fields:
if field.is_relation: if field.is_relation:
if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT: if field.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT:
continue continue
rel_app_label, rel_model_name = _get_app_label_and_model_name(field.rel.to, app_label) 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 # Include the model itself
related_models.add((app_label, model_name)) related_models.add((app_label, model_name))

View File

@ -63,6 +63,9 @@ Bugfixes
* Fixed JavaScript path of ``contrib.admin``s related field widget when using * Fixed JavaScript path of ``contrib.admin``s related field widget when using
alternate static file storages (:ticket:`24655`). alternate static file storages (:ticket:`24655`).
* Fixed a migration crash when adding new relations to models
(:ticket:`24573`).
Optimizations Optimizations
============= =============

View File

@ -1,7 +1,7 @@
from django.apps.registry import Apps from django.apps.registry import Apps
from django.db import models from django.db import models
from django.db.migrations.operations import ( from django.db.migrations.operations import (
AlterField, DeleteModel, RemoveField, AddField, AlterField, DeleteModel, RemoveField,
) )
from django.db.migrations.state import ( from django.db.migrations.state import (
InvalidBasesError, ModelState, ProjectState, get_related_models_recursive, InvalidBasesError, ModelState, ProjectState, get_related_models_recursive,
@ -368,6 +368,57 @@ class StateTests(TestCase):
project_state.add_model(ModelState.from_model(B)) project_state.add_model(ModelState.from_model(B))
self.assertEqual(len(project_state.apps.get_models()), 2) 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): def test_remove_relations(self):
""" """
#24225 - Tests that relations between models are updated while #24225 - Tests that relations between models are updated while