summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2014-11-05 10:43:31 +0100
committerClaude Paroz <claude@2xlibre.net>2015-01-02 15:19:36 +0100
commita159b1facd222c20dde12f1e0117643af528d511 (patch)
tree64fadd6c79714f41bbdd5efb0a5f3d2331c88ccb /django/db
parent4ee06ec3fc8e94d164afbd2f9c880c60c658a9ac (diff)
Replaced migration state render() by apps cached property
Refs #23745.
Diffstat (limited to 'django/db')
-rw-r--r--django/db/migrations/autodetector.py4
-rw-r--r--django/db/migrations/executor.py2
-rw-r--r--django/db/migrations/operations/fields.py24
-rw-r--r--django/db/migrations/operations/models.py45
-rw-r--r--django/db/migrations/operations/special.py4
-rw-r--r--django/db/migrations/state.py126
6 files changed, 99 insertions, 106 deletions
diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py
index 440b83880c..d96f09b0be 100644
--- a/django/db/migrations/autodetector.py
+++ b/django/db/migrations/autodetector.py
@@ -108,8 +108,8 @@ class MigrationAutodetector(object):
# Prepare some old/new state and model lists, separating
# proxy models and ignoring unmigrated apps.
- self.old_apps = self.from_state.render(ignore_swappable=True)
- self.new_apps = self.to_state.render()
+ self.old_apps = self.from_state.apps
+ self.new_apps = self.to_state.apps
self.old_model_keys = []
self.old_proxy_keys = []
self.old_unmanaged_keys = []
diff --git a/django/db/migrations/executor.py b/django/db/migrations/executor.py
index f5811bb795..4a73546ba8 100644
--- a/django/db/migrations/executor.py
+++ b/django/db/migrations/executor.py
@@ -137,7 +137,7 @@ class MigrationExecutor(object):
on initial migrations (as it only looks for CreateModel).
"""
project_state = self.loader.project_state((migration.app_label, migration.name), at_end=True)
- apps = project_state.render()
+ apps = project_state.apps
found_create_migration = False
# Bail if the migration isn't the first one in its app
if [name for app, name in migration.dependencies if app == migration.app_label]:
diff --git a/django/db/migrations/operations/fields.py b/django/db/migrations/operations/fields.py
index fd3f62de1f..ba123de9c3 100644
--- a/django/db/migrations/operations/fields.py
+++ b/django/db/migrations/operations/fields.py
@@ -40,9 +40,9 @@ class AddField(Operation):
state.models[app_label, self.model_name.lower()].fields.append((self.name, field))
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- to_model = to_state.render().get_model(app_label, self.model_name)
+ to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
- from_model = from_state.render().get_model(app_label, self.model_name)
+ from_model = from_state.apps.get_model(app_label, self.model_name)
field = to_model._meta.get_field_by_name(self.name)[0]
if not self.preserve_default:
field.default = self.field.default
@@ -54,7 +54,7 @@ class AddField(Operation):
field.default = NOT_PROVIDED
def database_backwards(self, app_label, schema_editor, from_state, to_state):
- from_model = from_state.render().get_model(app_label, self.model_name)
+ from_model = from_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, from_model):
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
@@ -96,14 +96,14 @@ class RemoveField(Operation):
state.models[app_label, self.model_name.lower()].fields = new_fields
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- from_model = from_state.render().get_model(app_label, self.model_name)
+ from_model = from_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, from_model):
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name(self.name)[0])
def database_backwards(self, app_label, schema_editor, from_state, to_state):
- to_model = to_state.render().get_model(app_label, self.model_name)
+ to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
- from_model = from_state.render().get_model(app_label, self.model_name)
+ from_model = from_state.apps.get_model(app_label, self.model_name)
schema_editor.add_field(from_model, to_model._meta.get_field_by_name(self.name)[0])
def describe(self):
@@ -152,9 +152,9 @@ class AlterField(Operation):
]
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- to_model = to_state.render().get_model(app_label, self.model_name)
+ to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
- from_model = from_state.render().get_model(app_label, self.model_name)
+ from_model = from_state.apps.get_model(app_label, self.model_name)
from_field = from_model._meta.get_field_by_name(self.name)[0]
to_field = to_model._meta.get_field_by_name(self.name)[0]
# If the field is a relatedfield with an unresolved rel.to, just
@@ -222,9 +222,9 @@ class RenameField(Operation):
]
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- to_model = to_state.render().get_model(app_label, self.model_name)
+ to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
- from_model = from_state.render().get_model(app_label, self.model_name)
+ from_model = from_state.apps.get_model(app_label, self.model_name)
schema_editor.alter_field(
from_model,
from_model._meta.get_field_by_name(self.old_name)[0],
@@ -232,9 +232,9 @@ class RenameField(Operation):
)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
- to_model = to_state.render().get_model(app_label, self.model_name)
+ to_model = to_state.apps.get_model(app_label, self.model_name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
- from_model = from_state.render().get_model(app_label, self.model_name)
+ from_model = from_state.apps.get_model(app_label, self.model_name)
schema_editor.alter_field(
from_model,
from_model._meta.get_field_by_name(self.new_name)[0],
diff --git a/django/db/migrations/operations/models.py b/django/db/migrations/operations/models.py
index 2436701919..eda30f88e8 100644
--- a/django/db/migrations/operations/models.py
+++ b/django/db/migrations/operations/models.py
@@ -49,14 +49,12 @@ class CreateModel(Operation):
)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- apps = to_state.render()
- model = apps.get_model(app_label, self.name)
+ model = to_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.create_model(model)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
- apps = from_state.render()
- model = apps.get_model(app_label, self.name)
+ model = from_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.delete_model(model)
@@ -103,14 +101,12 @@ class DeleteModel(Operation):
del state.models[app_label, self.name.lower()]
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- apps = from_state.render()
- model = apps.get_model(app_label, self.name)
+ model = from_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.delete_model(model)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
- apps = to_state.render()
- model = apps.get_model(app_label, self.name)
+ model = to_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, model):
schema_editor.create_model(model)
@@ -143,7 +139,7 @@ class RenameModel(Operation):
def state_forwards(self, app_label, state):
# Get all of the related objects we need to repoint
- apps = state.render(skip_cache=True)
+ apps = state.apps
model = apps.get_model(app_label, self.old_name)
related_objects = model._meta.get_all_related_objects()
related_m2m_objects = model._meta.get_all_related_many_to_many_objects()
@@ -168,13 +164,12 @@ class RenameModel(Operation):
field.rel.to = "%s.%s" % (app_label, self.new_name)
new_fields.append((name, field))
state.models[related_key].fields = new_fields
+ del state.apps # FIXME: this should be replaced by a logic in state (update_model?)
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- new_apps = to_state.render()
- new_model = new_apps.get_model(app_label, self.new_name)
+ new_model = to_state.apps.get_model(app_label, self.new_name)
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
- old_apps = from_state.render()
- old_model = old_apps.get_model(app_label, self.old_name)
+ old_model = from_state.apps.get_model(app_label, self.old_name)
# Move the main table
schema_editor.alter_db_table(
new_model,
@@ -194,7 +189,7 @@ class RenameModel(Operation):
related_object.model._meta.app_label,
related_object.model._meta.object_name.lower(),
)
- to_field = new_apps.get_model(
+ to_field = to_state.apps.get_model(
*related_key
)._meta.get_field_by_name(related_object.field.name)[0]
schema_editor.alter_field(
@@ -242,11 +237,9 @@ class AlterModelTable(Operation):
state.models[app_label, self.name.lower()].options["db_table"] = self.table
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- new_apps = to_state.render()
- new_model = new_apps.get_model(app_label, self.name)
+ new_model = to_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
- old_apps = from_state.render()
- old_model = old_apps.get_model(app_label, self.name)
+ old_model = from_state.apps.get_model(app_label, self.name)
schema_editor.alter_db_table(
new_model,
old_model._meta.db_table,
@@ -299,11 +292,9 @@ class AlterUniqueTogether(Operation):
model_state.options[self.option_name] = self.unique_together
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- new_apps = to_state.render()
- new_model = new_apps.get_model(app_label, self.name)
+ new_model = to_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
- old_apps = from_state.render()
- old_model = old_apps.get_model(app_label, self.name)
+ old_model = from_state.apps.get_model(app_label, self.name)
schema_editor.alter_unique_together(
new_model,
getattr(old_model._meta, self.option_name, set()),
@@ -348,11 +339,9 @@ class AlterIndexTogether(Operation):
model_state.options[self.option_name] = self.index_together
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- new_apps = to_state.render()
- new_model = new_apps.get_model(app_label, self.name)
+ new_model = to_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, new_model):
- old_apps = from_state.render()
- old_model = old_apps.get_model(app_label, self.name)
+ old_model = from_state.apps.get_model(app_label, self.name)
schema_editor.alter_index_together(
new_model,
getattr(old_model._meta, self.option_name, set()),
@@ -394,9 +383,9 @@ class AlterOrderWithRespectTo(Operation):
model_state.options['order_with_respect_to'] = self.order_with_respect_to
def database_forwards(self, app_label, schema_editor, from_state, to_state):
- to_model = to_state.render().get_model(app_label, self.name)
+ to_model = to_state.apps.get_model(app_label, self.name)
if self.allowed_to_migrate(schema_editor.connection.alias, to_model):
- from_model = from_state.render().get_model(app_label, self.name)
+ from_model = from_state.apps.get_model(app_label, self.name)
# Remove a field if we need to
if from_model._meta.order_with_respect_to and not to_model._meta.order_with_respect_to:
schema_editor.remove_field(from_model, from_model._meta.get_field_by_name("_order")[0])
diff --git a/django/db/migrations/operations/special.py b/django/db/migrations/operations/special.py
index c7d438c2a0..96fd2cc63c 100644
--- a/django/db/migrations/operations/special.py
+++ b/django/db/migrations/operations/special.py
@@ -166,12 +166,12 @@ class RunPython(Operation):
# object, representing the versioned models as an app registry.
# We could try to override the global cache, but then people will still
# use direct imports, so we go with a documentation approach instead.
- self.code(from_state.render(), schema_editor)
+ self.code(from_state.apps, schema_editor)
def database_backwards(self, app_label, schema_editor, from_state, to_state):
if self.reverse_code is None:
raise NotImplementedError("You cannot reverse this operation")
- self.reverse_code(from_state.render(), schema_editor)
+ self.reverse_code(from_state.apps, schema_editor)
def describe(self):
return "Raw Python operation"
diff --git a/django/db/migrations/state.py b/django/db/migrations/state.py
index 871e9920fa..f2267b3536 100644
--- a/django/db/migrations/state.py
+++ b/django/db/migrations/state.py
@@ -9,6 +9,7 @@ from django.db.models.fields.proxy import OrderWrt
from django.conf import settings
from django.utils import six
from django.utils.encoding import force_text, smart_text
+from django.utils.functional import cached_property
from django.utils.module_loading import import_string
from django.utils.version import get_docs_version
@@ -26,7 +27,6 @@ class ProjectState(object):
def __init__(self, models=None, real_apps=None):
self.models = models or {}
- self.apps = None
# Apps to include from main registry, usually unmigrated ones
self.real_apps = real_apps or []
@@ -40,66 +40,9 @@ class ProjectState(object):
real_apps=self.real_apps,
)
- def render(self, include_real=None, ignore_swappable=False, skip_cache=False):
- "Turns the project state into actual models in a new Apps"
- if self.apps is None or skip_cache:
- # Any apps in self.real_apps should have all their models included
- # in the render. We don't use the original model instances as there
- # are some variables that refer to the Apps object.
- # FKs/M2Ms from real apps are also not included as they just
- # mess things up with partial states (due to lack of dependencies)
- real_models = []
- for app_label in self.real_apps:
- app = global_apps.get_app_config(app_label)
- for model in app.get_models():
- real_models.append(ModelState.from_model(model, exclude_rels=True))
- # Populate the app registry with a stub for each application.
- app_labels = set(model_state.app_label for model_state in self.models.values())
- self.apps = Apps([AppConfigStub(label) for label in sorted(self.real_apps + list(app_labels))])
- # We keep trying to render the models in a loop, ignoring invalid
- # base errors, until the size of the unrendered models doesn't
- # decrease by at least one, meaning there's a base dependency loop/
- # missing base.
- unrendered_models = list(self.models.values()) + real_models
- while unrendered_models:
- new_unrendered_models = []
- for model in unrendered_models:
- try:
- model.render(self.apps)
- except InvalidBasesError:
- new_unrendered_models.append(model)
- if len(new_unrendered_models) == len(unrendered_models):
- raise InvalidBasesError(
- "Cannot resolve bases for %r\nThis can happen if you are inheriting models from an "
- "app with migrations (e.g. contrib.auth)\n in an app with no migrations; see "
- "https://docs.djangoproject.com/en/%s/topics/migrations/#dependencies "
- "for more" % (new_unrendered_models, get_docs_version())
- )
- unrendered_models = new_unrendered_models
- # make sure apps has no dangling references
- if self.apps._pending_lookups:
- # There's some lookups left. See if we can first resolve them
- # ourselves - sometimes fields are added after class_prepared is sent
- for lookup_model, operations in self.apps._pending_lookups.items():
- try:
- model = self.apps.get_model(lookup_model[0], lookup_model[1])
- except LookupError:
- app_label = "%s.%s" % (lookup_model[0], lookup_model[1])
- if app_label == settings.AUTH_USER_MODEL and ignore_swappable:
- continue
- # Raise an error with a best-effort helpful message
- # (only for the first issue). Error message should look like:
- # "ValueError: Lookup failed for model referenced by
- # field migrations.Book.author: migrations.Author"
- msg = "Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}"
- raise ValueError(msg.format(field=operations[0][1], model=lookup_model))
- else:
- do_pending_lookups(model)
- try:
- return self.apps
- finally:
- if skip_cache:
- self.apps = None
+ @cached_property
+ def apps(self):
+ return StateApps(self.real_apps, self.models)
@classmethod
def from_apps(cls, apps):
@@ -139,6 +82,67 @@ class AppConfigStub(AppConfig):
self.models = all_models
+class StateApps(Apps):
+ """
+ Subclass of the global Apps registry class to better handle dynamic model
+ additions and removals.
+ """
+ def __init__(self, real_apps, models):
+ # Any apps in self.real_apps should have all their models included
+ # in the render. We don't use the original model instances as there
+ # are some variables that refer to the Apps object.
+ # FKs/M2Ms from real apps are also not included as they just
+ # mess things up with partial states (due to lack of dependencies)
+ real_models = []
+ for app_label in real_apps:
+ app = global_apps.get_app_config(app_label)
+ for model in app.get_models():
+ real_models.append(ModelState.from_model(model, exclude_rels=True))
+ # Populate the app registry with a stub for each application.
+ app_labels = {model_state.app_label for model_state in models.values()}
+ app_configs = [AppConfigStub(label) for label in sorted(real_apps + list(app_labels))]
+ super(StateApps, self).__init__(app_configs)
+
+ # We keep trying to render the models in a loop, ignoring invalid
+ # base errors, until the size of the unrendered models doesn't
+ # decrease by at least one, meaning there's a base dependency loop/
+ # missing base.
+ unrendered_models = list(models.values()) + real_models
+ while unrendered_models:
+ new_unrendered_models = []
+ for model in unrendered_models:
+ try:
+ model.render(self)
+ except InvalidBasesError:
+ new_unrendered_models.append(model)
+ if len(new_unrendered_models) == len(unrendered_models):
+ raise InvalidBasesError(
+ "Cannot resolve bases for %r\nThis can happen if you are inheriting models from an "
+ "app with migrations (e.g. contrib.auth)\n in an app with no migrations; see "
+ "https://docs.djangoproject.com/en/%s/topics/migrations/#dependencies "
+ "for more" % (new_unrendered_models, get_docs_version())
+ )
+ unrendered_models = new_unrendered_models
+
+ # If there are some lookups left, see if we can first resolve them
+ # ourselves - sometimes fields are added after class_prepared is sent
+ for lookup_model, operations in self._pending_lookups.items():
+ try:
+ model = self.get_model(lookup_model[0], lookup_model[1])
+ except LookupError:
+ app_label = "%s.%s" % (lookup_model[0], lookup_model[1])
+ if app_label == settings.AUTH_USER_MODEL and ignore_swappable:
+ continue
+ # Raise an error with a best-effort helpful message
+ # (only for the first issue). Error message should look like:
+ # "ValueError: Lookup failed for model referenced by
+ # field migrations.Book.author: migrations.Author"
+ msg = "Lookup failed for model referenced by field {field}: {model[0]}.{model[1]}"
+ raise ValueError(msg.format(field=operations[0][1], model=lookup_model))
+ else:
+ do_pending_lookups(model)
+
+
class ModelState(object):
"""
Represents a Django Model. We don't use the actual Model class