mirror of
https://github.com/django/django.git
synced 2025-08-03 10:34:04 +00:00
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.
This commit is contained in:
parent
7a7c797234
commit
0385dad073
3 changed files with 67 additions and 15 deletions
|
@ -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.remote_field.through._meta.get_field('to_a').related_model, model_a_old)
|
||||
self.assertIs(field_to_a_old.remote_field.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.remote_field.through._meta.get_field('to_a').related_model, model_a_new)
|
||||
self.assertIs(field_to_a_new.remote_field.through._meta.get_field('from_a').related_model, model_a_new)
|
||||
|
||||
def test_equality(self):
|
||||
"""
|
||||
Tests that == and != are implemented correctly.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue