Fixed #24591 -- Optimized cloning of ModelState objects.
Changed ModelState.clone() to create a shallow copy of self.fields and self.managers.
This commit is contained in:
parent
c331eeb89c
commit
1a1f16d67d
|
@ -312,12 +312,23 @@ class ModelState(object):
|
|||
# Sanity-check that fields is NOT a dict. It must be ordered.
|
||||
if isinstance(self.fields, dict):
|
||||
raise ValueError("ModelState.fields cannot be a dict - it must be a list of 2-tuples.")
|
||||
# Sanity-check that fields are NOT already bound to a model.
|
||||
for name, field in fields:
|
||||
# Sanity-check that fields are NOT already bound to a model.
|
||||
if hasattr(field, 'model'):
|
||||
raise ValueError(
|
||||
'ModelState.fields cannot be bound to a model - "%s" is.' % name
|
||||
)
|
||||
# Sanity-check that relation fields are NOT referring to a model class.
|
||||
if field.is_relation and hasattr(field.related_model, '_meta'):
|
||||
raise ValueError(
|
||||
'ModelState.fields cannot refer to a model class - "%s.to" does. '
|
||||
'Use a string reference instead.' % name
|
||||
)
|
||||
if field.many_to_many and hasattr(field.remote_field.through, '_meta'):
|
||||
raise ValueError(
|
||||
'ModelState.fields cannot refer to a model class - "%s.through" does. '
|
||||
'Use a string reference instead.' % name
|
||||
)
|
||||
|
||||
@cached_property
|
||||
def name_lower(self):
|
||||
|
@ -502,10 +513,10 @@ class ModelState(object):
|
|||
return self.__class__(
|
||||
app_label=self.app_label,
|
||||
name=self.name,
|
||||
fields=list(self.construct_fields()),
|
||||
fields=list(self.fields),
|
||||
options=dict(self.options),
|
||||
bases=self.bases,
|
||||
managers=list(self.construct_managers()),
|
||||
managers=list(self.managers),
|
||||
)
|
||||
|
||||
def render(self, apps):
|
||||
|
|
|
@ -402,7 +402,8 @@ You can take this template and work from it, though we suggest looking at the
|
|||
built-in Django operations in ``django.db.migrations.operations`` - they're
|
||||
easy to read and cover a lot of the example usage of semi-internal aspects
|
||||
of the migration framework like ``ProjectState`` and the patterns used to get
|
||||
historical models.
|
||||
historical models, as well as ``ModelState`` and the patterns used to mutate
|
||||
historical models in ``state_forwards()``.
|
||||
|
||||
Some things to note:
|
||||
|
||||
|
@ -421,6 +422,16 @@ Some things to note:
|
|||
operations; this is part of the autodetection code and does not matter for
|
||||
custom operations.
|
||||
|
||||
.. warning::
|
||||
|
||||
For performance reasons, the :class:`~django.db.models.Field` instances in
|
||||
``ModelState.fields`` are reused across migrations. You must never change
|
||||
the attributes on these instances. If you need to mutate a field in
|
||||
``state_forwards()``, you must remove the old instance from
|
||||
``ModelState.fields`` and add a new instance in its place. The same is true
|
||||
for the :class:`~django.db.models.Manager` instances in
|
||||
``ModelState.managers``.
|
||||
|
||||
As a simple example, let's make an operation that loads PostgreSQL extensions
|
||||
(which contain some of PostgreSQL's more exciting features). It's simple enough;
|
||||
there's no model state changes, and all it does is run one command::
|
||||
|
|
|
@ -10,6 +10,7 @@ from django.test import SimpleTestCase, TestCase, override_settings
|
|||
|
||||
from .models import (
|
||||
FoodManager, FoodQuerySet, ModelWithCustomBase, NoMigrationFoodManager,
|
||||
UnicodeModel,
|
||||
)
|
||||
|
||||
|
||||
|
@ -695,6 +696,21 @@ class ModelStateTests(TestCase):
|
|||
'ModelState.fields cannot be bound to a model - "field" is.'):
|
||||
ModelState('app', 'Model', [('field', field)])
|
||||
|
||||
def test_sanity_check_to(self):
|
||||
field = models.ForeignKey(UnicodeModel)
|
||||
with self.assertRaisesMessage(ValueError,
|
||||
'ModelState.fields cannot refer to a model class - "field.to" does. '
|
||||
'Use a string reference instead.'):
|
||||
ModelState('app', 'Model', [('field', field)])
|
||||
|
||||
def test_sanity_check_through(self):
|
||||
field = models.ManyToManyField('UnicodeModel')
|
||||
field.remote_field.through = UnicodeModel
|
||||
with self.assertRaisesMessage(ValueError,
|
||||
'ModelState.fields cannot refer to a model class - "field.through" does. '
|
||||
'Use a string reference instead.'):
|
||||
ModelState('app', 'Model', [('field', field)])
|
||||
|
||||
def test_fields_immutability(self):
|
||||
"""
|
||||
Tests that rendering a model state doesn't alter its internal fields.
|
||||
|
|
Loading…
Reference in New Issue