mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Fixed #31499 -- Stored ModelState.fields into a dict.
This allows the removal of its O(n) .get_field_by_name method and many other awkward access patterns. While fields were initially stored in a list to preserve the initial model definiton field ordering the auto-detector doesn't take field ordering into account and no operations exists to reorder fields of a model. This makes the preservation of the field ordering completely superflous because field reorganization after the creation of the model state wouldn't be taken into account.
This commit is contained in:
committed by
Mariusz Felisiak
parent
696024fb73
commit
06889d6206
@@ -93,7 +93,7 @@ class MigrationAutodetector:
|
||||
of course, the related fields change during renames).
|
||||
"""
|
||||
fields_def = []
|
||||
for name, field in sorted(fields):
|
||||
for name, field in sorted(fields.items()):
|
||||
deconstruction = self.deep_deconstruct(field)
|
||||
if field.remote_field and field.remote_field.model:
|
||||
del deconstruction[2]['to']
|
||||
@@ -208,17 +208,17 @@ class MigrationAutodetector:
|
||||
self.kept_unmanaged_keys = self.old_unmanaged_keys & self.new_unmanaged_keys
|
||||
self.through_users = {}
|
||||
self.old_field_keys = {
|
||||
(app_label, model_name, x)
|
||||
(app_label, model_name, field_name)
|
||||
for app_label, model_name in self.kept_model_keys
|
||||
for x, y in self.from_state.models[
|
||||
for field_name in self.from_state.models[
|
||||
app_label,
|
||||
self.renamed_models.get((app_label, model_name), model_name)
|
||||
].fields
|
||||
}
|
||||
self.new_field_keys = {
|
||||
(app_label, model_name, x)
|
||||
(app_label, model_name, field_name)
|
||||
for app_label, model_name in self.kept_model_keys
|
||||
for x, y in self.to_state.models[app_label, model_name].fields
|
||||
for field_name in self.to_state.models[app_label, model_name].fields
|
||||
}
|
||||
|
||||
def _generate_through_model_map(self):
|
||||
@@ -226,7 +226,7 @@ class MigrationAutodetector:
|
||||
for app_label, model_name in sorted(self.old_model_keys):
|
||||
old_model_name = self.renamed_models.get((app_label, model_name), model_name)
|
||||
old_model_state = self.from_state.models[app_label, old_model_name]
|
||||
for field_name, field in old_model_state.fields:
|
||||
for field_name in old_model_state.fields:
|
||||
old_field = self.old_apps.get_model(app_label, old_model_name)._meta.get_field(field_name)
|
||||
if (hasattr(old_field, "remote_field") and getattr(old_field.remote_field, "through", None) and
|
||||
not old_field.remote_field.through._meta.auto_created):
|
||||
@@ -576,7 +576,7 @@ class MigrationAutodetector:
|
||||
app_label,
|
||||
operations.CreateModel(
|
||||
name=model_state.name,
|
||||
fields=[d for d in model_state.fields if d[0] not in related_fields],
|
||||
fields=[d for d in model_state.fields.items() if d[0] not in related_fields],
|
||||
options=model_state.options,
|
||||
bases=model_state.bases,
|
||||
managers=model_state.managers,
|
||||
@@ -820,7 +820,7 @@ class MigrationAutodetector:
|
||||
field_dec = self.deep_deconstruct(field)
|
||||
for rem_app_label, rem_model_name, rem_field_name in sorted(self.old_field_keys - self.new_field_keys):
|
||||
if rem_app_label == app_label and rem_model_name == model_name:
|
||||
old_field = old_model_state.get_field_by_name(rem_field_name)
|
||||
old_field = old_model_state.fields[rem_field_name]
|
||||
old_field_dec = self.deep_deconstruct(old_field)
|
||||
if field.remote_field and field.remote_field.model and 'to' in old_field_dec[2]:
|
||||
old_rel_to = old_field_dec[2]['to']
|
||||
|
||||
@@ -89,7 +89,7 @@ class AddField(FieldOperation):
|
||||
field.default = NOT_PROVIDED
|
||||
else:
|
||||
field = self.field
|
||||
state.models[app_label, self.model_name_lower].fields.append((self.name, field))
|
||||
state.models[app_label, self.model_name_lower].fields[self.name] = field
|
||||
# Delay rendering of relationships if it's not a relational field
|
||||
delay = not field.is_relation
|
||||
state.reload_model(app_label, self.model_name_lower, delay=delay)
|
||||
@@ -154,14 +154,8 @@ class RemoveField(FieldOperation):
|
||||
)
|
||||
|
||||
def state_forwards(self, app_label, state):
|
||||
new_fields = []
|
||||
old_field = None
|
||||
for name, instance in state.models[app_label, self.model_name_lower].fields:
|
||||
if name != self.name:
|
||||
new_fields.append((name, instance))
|
||||
else:
|
||||
old_field = instance
|
||||
state.models[app_label, self.model_name_lower].fields = new_fields
|
||||
model_state = state.models[app_label, self.model_name_lower]
|
||||
old_field = model_state.fields.pop(self.name)
|
||||
# Delay rendering of relationships if it's not a relational field
|
||||
delay = not old_field.is_relation
|
||||
state.reload_model(app_label, self.model_name_lower, delay=delay)
|
||||
@@ -217,11 +211,8 @@ class AlterField(FieldOperation):
|
||||
field.default = NOT_PROVIDED
|
||||
else:
|
||||
field = self.field
|
||||
state.models[app_label, self.model_name_lower].fields = [
|
||||
(n, field if n == self.name else f)
|
||||
for n, f in
|
||||
state.models[app_label, self.model_name_lower].fields
|
||||
]
|
||||
model_state = state.models[app_label, self.model_name_lower]
|
||||
model_state.fields[self.name] = field
|
||||
# TODO: investigate if old relational fields must be reloaded or if it's
|
||||
# sufficient if the new field is (#27737).
|
||||
# Delay rendering of relationships if it's not a relational field and
|
||||
@@ -299,11 +290,14 @@ class RenameField(FieldOperation):
|
||||
model_state = state.models[app_label, self.model_name_lower]
|
||||
# Rename the field
|
||||
fields = model_state.fields
|
||||
found = None
|
||||
for index, (name, field) in enumerate(fields):
|
||||
if not found and name == self.old_name:
|
||||
fields[index] = (self.new_name, field)
|
||||
found = field
|
||||
try:
|
||||
found = fields.pop(self.old_name)
|
||||
except KeyError:
|
||||
raise FieldDoesNotExist(
|
||||
"%s.%s has no field named '%s'" % (app_label, self.model_name, self.old_name)
|
||||
)
|
||||
fields[self.new_name] = found
|
||||
for field in fields.values():
|
||||
# Fix from_fields to refer to the new field.
|
||||
from_fields = getattr(field, 'from_fields', None)
|
||||
if from_fields:
|
||||
@@ -311,10 +305,6 @@ class RenameField(FieldOperation):
|
||||
self.new_name if from_field_name == self.old_name else from_field_name
|
||||
for from_field_name in from_fields
|
||||
])
|
||||
if found is None:
|
||||
raise FieldDoesNotExist(
|
||||
"%s.%s has no field named '%s'" % (app_label, self.model_name, self.old_name)
|
||||
)
|
||||
# Fix index/unique_together to refer to the new field
|
||||
options = model_state.options
|
||||
for option in ('index_together', 'unique_together'):
|
||||
|
||||
@@ -310,7 +310,7 @@ class RenameModel(ModelOperation):
|
||||
old_model_tuple = (app_label, self.old_name_lower)
|
||||
new_remote_model = '%s.%s' % (app_label, self.new_name)
|
||||
to_reload = set()
|
||||
for model_state, index, name, field, reference in get_references(state, old_model_tuple):
|
||||
for model_state, name, field, reference in get_references(state, old_model_tuple):
|
||||
changed_field = None
|
||||
if reference.to:
|
||||
changed_field = field.clone()
|
||||
@@ -320,7 +320,7 @@ class RenameModel(ModelOperation):
|
||||
changed_field = field.clone()
|
||||
changed_field.remote_field.through = new_remote_model
|
||||
if changed_field:
|
||||
model_state.fields[index] = name, changed_field
|
||||
model_state.fields[name] = changed_field
|
||||
to_reload.add((model_state.app_label, model_state.name_lower))
|
||||
# Reload models related to old model before removing the old model.
|
||||
state.reload_models(to_reload, delay=True)
|
||||
|
||||
@@ -83,17 +83,17 @@ def field_references(
|
||||
|
||||
def get_references(state, model_tuple, field_tuple=()):
|
||||
"""
|
||||
Generator of (model_state, index, name, field, reference) referencing
|
||||
Generator of (model_state, name, field, reference) referencing
|
||||
provided context.
|
||||
|
||||
If field_tuple is provided only references to this particular field of
|
||||
model_tuple will be generated.
|
||||
"""
|
||||
for state_model_tuple, model_state in state.models.items():
|
||||
for index, (name, field) in enumerate(model_state.fields):
|
||||
for name, field in model_state.fields.items():
|
||||
reference = field_references(state_model_tuple, field, model_tuple, *field_tuple)
|
||||
if reference:
|
||||
yield model_state, index, name, field, reference
|
||||
yield model_state, name, field, reference
|
||||
|
||||
|
||||
def field_is_referenced(state, model_tuple, field_tuple):
|
||||
|
||||
@@ -125,7 +125,7 @@ class ProjectState:
|
||||
# Directly related models are the models pointed to by ForeignKeys,
|
||||
# OneToOneFields, and ManyToManyFields.
|
||||
direct_related_models = set()
|
||||
for name, field in model_state.fields:
|
||||
for field in model_state.fields.values():
|
||||
if field.is_relation:
|
||||
if field.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT:
|
||||
continue
|
||||
@@ -359,16 +359,13 @@ class ModelState:
|
||||
def __init__(self, app_label, name, fields, options=None, bases=None, managers=None):
|
||||
self.app_label = app_label
|
||||
self.name = name
|
||||
self.fields = fields
|
||||
self.fields = dict(fields)
|
||||
self.options = options or {}
|
||||
self.options.setdefault('indexes', [])
|
||||
self.options.setdefault('constraints', [])
|
||||
self.bases = bases or (models.Model,)
|
||||
self.managers = managers or []
|
||||
# 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.")
|
||||
for name, field in fields:
|
||||
for name, field in self.fields.items():
|
||||
# Sanity-check that fields are NOT already bound to a model.
|
||||
if hasattr(field, 'model'):
|
||||
raise ValueError(
|
||||
@@ -544,7 +541,7 @@ class ModelState:
|
||||
return self.__class__(
|
||||
app_label=self.app_label,
|
||||
name=self.name,
|
||||
fields=list(self.fields),
|
||||
fields=dict(self.fields),
|
||||
# Since options are shallow-copied here, operations such as
|
||||
# AddIndex must replace their option (e.g 'indexes') rather
|
||||
# than mutating it.
|
||||
@@ -566,8 +563,8 @@ class ModelState:
|
||||
)
|
||||
except LookupError:
|
||||
raise InvalidBasesError("Cannot resolve one or more bases from %r" % (self.bases,))
|
||||
# Turn fields into a dict for the body, add other bits
|
||||
body = {name: field.clone() for name, field in self.fields}
|
||||
# Clone fields for the body, add other bits.
|
||||
body = {name: field.clone() for name, field in self.fields.items()}
|
||||
body['Meta'] = meta
|
||||
body['__module__'] = "__fake__"
|
||||
|
||||
@@ -576,12 +573,6 @@ class ModelState:
|
||||
# Then, make a Model object (apps.register_model is called in __new__)
|
||||
return type(self.name, bases, body)
|
||||
|
||||
def get_field_by_name(self, name):
|
||||
for fname, field in self.fields:
|
||||
if fname == name:
|
||||
return field
|
||||
raise ValueError("No field called %s on model %s" % (name, self.name))
|
||||
|
||||
def get_index_by_name(self, name):
|
||||
for index in self.options['indexes']:
|
||||
if index.name == name:
|
||||
@@ -602,8 +593,13 @@ class ModelState:
|
||||
(self.app_label == other.app_label) and
|
||||
(self.name == other.name) and
|
||||
(len(self.fields) == len(other.fields)) and
|
||||
all((k1 == k2 and (f1.deconstruct()[1:] == f2.deconstruct()[1:]))
|
||||
for (k1, f1), (k2, f2) in zip(sorted(self.fields), sorted(other.fields))) and
|
||||
all(
|
||||
k1 == k2 and f1.deconstruct()[1:] == f2.deconstruct()[1:]
|
||||
for (k1, f1), (k2, f2) in zip(
|
||||
sorted(self.fields.items()),
|
||||
sorted(other.fields.items()),
|
||||
)
|
||||
) and
|
||||
(self.options == other.options) and
|
||||
(self.bases == other.bases) and
|
||||
(self.managers == other.managers)
|
||||
|
||||
Reference in New Issue
Block a user