diff options
| author | Patryk Zawadzki <patrys@room-303.com> | 2015-04-03 23:36:35 +0200 |
|---|---|---|
| committer | Markus Holtermann <info@markusholtermann.eu> | 2015-04-07 14:56:33 +0200 |
| commit | 9f632dc702a1b8b4c8044fb34bb3d5f2445dd4ee (patch) | |
| tree | 4f3a64f3b14f35789238c607b6aeea566dd42075 /tests | |
| parent | f5681f1cbf02b9d4e0ef1efaab4998f4ceaffbf5 (diff) | |
[1.8.x] Fixed #24513 -- Made sure a model is only rendered once during reloads
This also prevents state modifications from corrupting previous states.
Previously, when a model defining a relation was unregistered first,
clearing the cache would cause its related models' _meta to be cleared
and would result in the old models losing track of their relations.
Backport of 0385dad073270c37f8c4a5f13edce43f2a69ba8a from master
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/migrations/test_state.py | 53 |
1 files changed, 52 insertions, 1 deletions
diff --git a/tests/migrations/test_state.py b/tests/migrations/test_state.py index aad50df739..663815d6f9 100644 --- a/tests/migrations/test_state.py +++ b/tests/migrations/test_state.py @@ -1,6 +1,8 @@ from django.apps.registry import Apps from django.db import models -from django.db.migrations.operations import DeleteModel, RemoveField +from django.db.migrations.operations import ( + AlterField, DeleteModel, RemoveField, +) from django.db.migrations.state import ( InvalidBasesError, ModelState, ProjectState, get_related_models_recursive, ) @@ -413,6 +415,55 @@ class StateTests(TestCase): self.assertEqual(len(model_a_old._meta.related_objects), 1) self.assertEqual(len(model_a_new._meta.related_objects), 0) + def test_self_relation(self): + """ + #24513 - Modifying an object pointing to itself would cause it to be + rendered twice and thus breaking its related M2M through objects. + """ + class A(models.Model): + to_a = models.ManyToManyField('something.A', symmetrical=False) + + class Meta: + app_label = "something" + + def get_model_a(state): + return [mod for mod in state.apps.get_models() if mod._meta.model_name == 'a'][0] + + project_state = ProjectState() + project_state.add_model((ModelState.from_model(A))) + self.assertEqual(len(get_model_a(project_state)._meta.related_objects), 1) + old_state = project_state.clone() + + operation = AlterField( + model_name="a", + name="to_a", + field=models.ManyToManyField("something.A", symmetrical=False, blank=True) + ) + # At this point the model would be rendered twice causing its related + # M2M through objects to point to an old copy and thus breaking their + # attribute lookup. + operation.state_forwards("something", project_state) + + model_a_old = get_model_a(old_state) + model_a_new = get_model_a(project_state) + self.assertIsNot(model_a_old, model_a_new) + + # Tests that the old model's _meta is still consistent + field_to_a_old = model_a_old._meta.get_field("to_a") + self.assertEqual(field_to_a_old.m2m_field_name(), "from_a") + self.assertEqual(field_to_a_old.m2m_reverse_field_name(), "to_a") + self.assertIs(field_to_a_old.related_model, model_a_old) + self.assertIs(field_to_a_old.rel.through._meta.get_field('to_a').related_model, model_a_old) + self.assertIs(field_to_a_old.rel.through._meta.get_field('from_a').related_model, model_a_old) + + # Tests that the new model's _meta is still consistent + field_to_a_new = model_a_new._meta.get_field("to_a") + self.assertEqual(field_to_a_new.m2m_field_name(), "from_a") + self.assertEqual(field_to_a_new.m2m_reverse_field_name(), "to_a") + self.assertIs(field_to_a_new.related_model, model_a_new) + self.assertIs(field_to_a_new.rel.through._meta.get_field('to_a').related_model, model_a_new) + self.assertIs(field_to_a_new.rel.through._meta.get_field('from_a').related_model, model_a_new) + def test_equality(self): """ Tests that == and != are implemented correctly. |
