Commit b93690c4 authored by Markus Holtermann's avatar Markus Holtermann
Browse files

Fixed #24573 -- Considered new related models for reloading

Thanks tttomekkk for the report.
parent e4b7daec
Loading
Loading
Loading
Loading
+15 −2
Original line number Diff line number Diff line
@@ -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.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT:
                        continue
                    rel_app_label, rel_model_name = _get_app_label_and_model_name(field.remote_field.model, app_label)
                    related_models.add((rel_app_label, rel_model_name.lower()))
                    rel_app_label, rel_model_name = _get_app_label_and_model_name(field.related_model, app_label)
                    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))
+3 −0
Original line number Diff line number Diff line
@@ -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
=============

+52 −1
Original line number Diff line number Diff line
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,
@@ -385,6 +385,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