1
0
mirror of https://github.com/django/django.git synced 2025-06-07 04:29:12 +00:00

Refs #35704 -- Used copy.replace() in Operation.reduce() methods.

This commit is contained in:
Adam Johnson 2024-08-22 19:27:23 +01:00 committed by Mariusz Felisiak
parent 2d34ebe49a
commit c07ba43c4b
4 changed files with 71 additions and 127 deletions

View File

@ -1,6 +1,7 @@
import enum import enum
from django.db import router from django.db import router
from django.utils.inspect import get_func_args
class OperationCategory(str, enum.Enum): class OperationCategory(str, enum.Enum):
@ -52,6 +53,16 @@ class Operation:
self._constructor_args = (args, kwargs) self._constructor_args = (args, kwargs)
return self return self
def __replace__(self, /, **changes):
args = [
changes.pop(name, value)
for name, value in zip(
get_func_args(self.__class__),
self._constructor_args[0],
)
]
return self.__class__(*args, **(self._constructor_args[1] | changes))
def deconstruct(self): def deconstruct(self):
""" """
Return a 3-tuple of class import path (or just name if it lives Return a 3-tuple of class import path (or just name if it lives

View File

@ -1,5 +1,6 @@
from django.db.migrations.utils import field_references from django.db.migrations.utils import field_references
from django.db.models import NOT_PROVIDED from django.db.models import NOT_PROVIDED
from django.utils.copy import replace
from django.utils.functional import cached_property from django.utils.functional import cached_property
from .base import Operation, OperationCategory from .base import Operation, OperationCategory
@ -134,8 +135,8 @@ class AddField(FieldOperation):
): ):
if isinstance(operation, AlterField): if isinstance(operation, AlterField):
return [ return [
AddField( replace(
model_name=self.model_name, self,
name=operation.name, name=operation.name,
field=operation.field, field=operation.field,
), ),
@ -143,13 +144,7 @@ class AddField(FieldOperation):
elif isinstance(operation, RemoveField): elif isinstance(operation, RemoveField):
return [] return []
elif isinstance(operation, RenameField): elif isinstance(operation, RenameField):
return [ return [replace(self, name=operation.new_name)]
AddField(
model_name=self.model_name,
name=operation.new_name,
field=self.field,
),
]
return super().reduce(operation, app_label) return super().reduce(operation, app_label)
@ -264,11 +259,7 @@ class AlterField(FieldOperation):
): ):
return [ return [
operation, operation,
AlterField( replace(self, name=operation.new_name),
model_name=self.model_name,
name=operation.new_name,
field=self.field,
),
] ]
return super().reduce(operation, app_label) return super().reduce(operation, app_label)
@ -350,13 +341,7 @@ class RenameField(FieldOperation):
and self.is_same_model_operation(operation) and self.is_same_model_operation(operation)
and self.new_name_lower == operation.old_name_lower and self.new_name_lower == operation.old_name_lower
): ):
return [ return [replace(self, new_name=operation.new_name)]
RenameField(
self.model_name,
self.old_name,
operation.new_name,
),
]
# Skip `FieldOperation.reduce` as we want to run `references_field` # Skip `FieldOperation.reduce` as we want to run `references_field`
# against self.old_name and self.new_name. # against self.old_name and self.new_name.
return super(FieldOperation, self).reduce(operation, app_label) or not ( return super(FieldOperation, self).reduce(operation, app_label) or not (

View File

@ -1,8 +1,11 @@
from copy import copy
from django.db import models from django.db import models
from django.db.migrations.operations.base import Operation, OperationCategory from django.db.migrations.operations.base import Operation, OperationCategory
from django.db.migrations.state import ModelState from django.db.migrations.state import ModelState
from django.db.migrations.utils import field_references, resolve_relation from django.db.migrations.utils import field_references, resolve_relation
from django.db.models.options import normalize_together from django.db.models.options import normalize_together
from django.utils.copy import replace
from django.utils.functional import cached_property from django.utils.functional import cached_property
from .fields import AddField, AlterField, FieldOperation, RemoveField, RenameField from .fields import AddField, AlterField, FieldOperation, RemoveField, RenameField
@ -156,15 +159,7 @@ class CreateModel(ModelOperation):
isinstance(operation, RenameModel) isinstance(operation, RenameModel)
and self.name_lower == operation.old_name_lower and self.name_lower == operation.old_name_lower
): ):
return [ return [replace(self, name=operation.new_name)]
CreateModel(
operation.new_name,
fields=self.fields,
options=self.options,
bases=self.bases,
managers=self.managers,
),
]
elif ( elif (
isinstance(operation, AlterModelOptions) isinstance(operation, AlterModelOptions)
and self.name_lower == operation.name_lower and self.name_lower == operation.name_lower
@ -173,42 +168,20 @@ class CreateModel(ModelOperation):
for key in operation.ALTER_OPTION_KEYS: for key in operation.ALTER_OPTION_KEYS:
if key not in operation.options: if key not in operation.options:
options.pop(key, None) options.pop(key, None)
return [ return [replace(self, options=options)]
CreateModel(
self.name,
fields=self.fields,
options=options,
bases=self.bases,
managers=self.managers,
),
]
elif ( elif (
isinstance(operation, AlterModelManagers) isinstance(operation, AlterModelManagers)
and self.name_lower == operation.name_lower and self.name_lower == operation.name_lower
): ):
return [ return [replace(self, managers=operation.managers)]
CreateModel(
self.name,
fields=self.fields,
options=self.options,
bases=self.bases,
managers=operation.managers,
),
]
elif ( elif (
isinstance(operation, AlterModelTable) isinstance(operation, AlterModelTable)
and self.name_lower == operation.name_lower and self.name_lower == operation.name_lower
): ):
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields, options={**self.options, "db_table": operation.table},
options={
**self.options,
"db_table": operation.table,
},
bases=self.bases,
managers=self.managers,
), ),
] ]
elif ( elif (
@ -216,15 +189,12 @@ class CreateModel(ModelOperation):
and self.name_lower == operation.name_lower and self.name_lower == operation.name_lower
): ):
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields,
options={ options={
**self.options, **self.options,
"db_table_comment": operation.table_comment, "db_table_comment": operation.table_comment,
}, },
bases=self.bases,
managers=self.managers,
), ),
] ]
elif ( elif (
@ -232,15 +202,12 @@ class CreateModel(ModelOperation):
and self.name_lower == operation.name_lower and self.name_lower == operation.name_lower
): ):
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields,
options={ options={
**self.options, **self.options,
**{operation.option_name: operation.option_value}, **{operation.option_name: operation.option_value},
}, },
bases=self.bases,
managers=self.managers,
), ),
] ]
elif ( elif (
@ -248,15 +215,12 @@ class CreateModel(ModelOperation):
and self.name_lower == operation.name_lower and self.name_lower == operation.name_lower
): ):
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields,
options={ options={
**self.options, **self.options,
"order_with_respect_to": operation.order_with_respect_to, "order_with_respect_to": operation.order_with_respect_to,
}, },
bases=self.bases,
managers=self.managers,
), ),
] ]
elif ( elif (
@ -265,25 +229,19 @@ class CreateModel(ModelOperation):
): ):
if isinstance(operation, AddField): if isinstance(operation, AddField):
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields + [(operation.name, operation.field)], fields=self.fields + [(operation.name, operation.field)],
options=self.options,
bases=self.bases,
managers=self.managers,
), ),
] ]
elif isinstance(operation, AlterField): elif isinstance(operation, AlterField):
return [ return [
CreateModel( replace(
self.name, self,
fields=[ fields=[
(n, operation.field if n == operation.name else v) (n, operation.field if n == operation.name else v)
for n, v in self.fields for n, v in self.fields
], ],
options=self.options,
bases=self.bases,
managers=self.managers,
), ),
] ]
elif isinstance(operation, RemoveField): elif isinstance(operation, RemoveField):
@ -308,16 +266,14 @@ class CreateModel(ModelOperation):
if order_with_respect_to == operation.name_lower: if order_with_respect_to == operation.name_lower:
del options["order_with_respect_to"] del options["order_with_respect_to"]
return [ return [
CreateModel( replace(
self.name, self,
fields=[ fields=[
(n, v) (n, v)
for n, v in self.fields for n, v in self.fields
if n.lower() != operation.name_lower if n.lower() != operation.name_lower
], ],
options=options, options=options,
bases=self.bases,
managers=self.managers,
), ),
] ]
elif isinstance(operation, RenameField): elif isinstance(operation, RenameField):
@ -336,15 +292,13 @@ class CreateModel(ModelOperation):
if order_with_respect_to == operation.old_name: if order_with_respect_to == operation.old_name:
options["order_with_respect_to"] = operation.new_name options["order_with_respect_to"] = operation.new_name
return [ return [
CreateModel( replace(
self.name, self,
fields=[ fields=[
(operation.new_name if n == operation.old_name else n, v) (operation.new_name if n == operation.old_name else n, v)
for n, v in self.fields for n, v in self.fields
], ],
options=options, options=options,
bases=self.bases,
managers=self.managers,
), ),
] ]
elif ( elif (
@ -353,9 +307,8 @@ class CreateModel(ModelOperation):
): ):
if isinstance(operation, AddIndex): if isinstance(operation, AddIndex):
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields,
options={ options={
**self.options, **self.options,
"indexes": [ "indexes": [
@ -363,8 +316,6 @@ class CreateModel(ModelOperation):
operation.index, operation.index,
], ],
}, },
bases=self.bases,
managers=self.managers,
), ),
] ]
elif isinstance(operation, RemoveIndex): elif isinstance(operation, RemoveIndex):
@ -374,22 +325,18 @@ class CreateModel(ModelOperation):
if index.name != operation.name if index.name != operation.name
] ]
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields,
options={ options={
**self.options, **self.options,
"indexes": options_indexes, "indexes": options_indexes,
}, },
bases=self.bases,
managers=self.managers,
), ),
] ]
elif isinstance(operation, AddConstraint): elif isinstance(operation, AddConstraint):
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields,
options={ options={
**self.options, **self.options,
"constraints": [ "constraints": [
@ -397,8 +344,6 @@ class CreateModel(ModelOperation):
operation.constraint, operation.constraint,
], ],
}, },
bases=self.bases,
managers=self.managers,
), ),
] ]
elif isinstance(operation, RemoveConstraint): elif isinstance(operation, RemoveConstraint):
@ -408,15 +353,12 @@ class CreateModel(ModelOperation):
if constraint.name != operation.name if constraint.name != operation.name
] ]
return [ return [
CreateModel( replace(
self.name, self,
fields=self.fields,
options={ options={
**self.options, **self.options,
"constraints": options_constraints, "constraints": options_constraints,
}, },
bases=self.bases,
managers=self.managers,
), ),
] ]
return super().reduce(operation, app_label) return super().reduce(operation, app_label)
@ -567,12 +509,7 @@ class RenameModel(ModelOperation):
isinstance(operation, RenameModel) isinstance(operation, RenameModel)
and self.new_name_lower == operation.old_name_lower and self.new_name_lower == operation.old_name_lower
): ):
return [ return [replace(self, new_name=operation.new_name)]
RenameModel(
self.old_name,
operation.new_name,
),
]
# Skip `ModelOperation.reduce` as we want to run `references_model` # Skip `ModelOperation.reduce` as we want to run `references_model`
# against self.new_name. # against self.new_name.
return super(ModelOperation, self).reduce( return super(ModelOperation, self).reduce(
@ -990,8 +927,9 @@ class AddIndex(IndexOperation):
if isinstance(operation, RemoveIndex) and self.index.name == operation.name: if isinstance(operation, RemoveIndex) and self.index.name == operation.name:
return [] return []
if isinstance(operation, RenameIndex) and self.index.name == operation.old_name: if isinstance(operation, RenameIndex) and self.index.name == operation.old_name:
self.index.name = operation.new_name index = copy(self.index)
return [self.__class__(model_name=self.model_name, index=self.index)] index.name = operation.new_name
return [replace(self, index=index)]
return super().reduce(operation, app_label) return super().reduce(operation, app_label)
@ -1183,14 +1121,7 @@ class RenameIndex(IndexOperation):
and operation.old_name and operation.old_name
and self.new_name_lower == operation.old_name_lower and self.new_name_lower == operation.old_name_lower
): ):
return [ return [replace(self, new_name=operation.new_name)]
RenameIndex(
self.model_name,
new_name=operation.new_name,
old_name=self.old_name,
old_fields=self.old_fields,
)
]
return super().reduce(operation, app_label) return super().reduce(operation, app_label)
@ -1247,7 +1178,7 @@ class AddConstraint(IndexOperation):
and self.model_name_lower == operation.model_name_lower and self.model_name_lower == operation.model_name_lower
and self.constraint.name == operation.name and self.constraint.name == operation.name
): ):
return [AddConstraint(self.model_name, operation.constraint)] return [replace(self, constraint=operation.constraint)]
return super().reduce(operation, app_label) return super().reduce(operation, app_label)

17
django/utils/copy.py Normal file
View File

@ -0,0 +1,17 @@
from django.utils.version import PY313
if PY313:
from copy import replace
else:
# Backport of copy.replace() from Python 3.13.
def replace(obj, /, **changes):
"""Return a new object replacing specified fields with new values.
This is especially useful for immutable objects, like named tuples or
frozen dataclasses.
"""
cls = obj.__class__
func = getattr(cls, "__replace__", None)
if func is None:
raise TypeError(f"replace() does not support {cls.__name__} objects")
return func(obj, **changes)