mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
Refs #35704 -- Used copy.replace() in Operation.reduce() methods.
This commit is contained in:
parent
8adb11a62f
commit
b02e5e122b
@ -1,6 +1,7 @@
|
||||
import enum
|
||||
|
||||
from django.db import router
|
||||
from django.utils.inspect import get_func_args
|
||||
|
||||
|
||||
class OperationCategory(str, enum.Enum):
|
||||
@ -52,6 +53,16 @@ class Operation:
|
||||
self._constructor_args = (args, kwargs)
|
||||
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):
|
||||
"""
|
||||
Return a 3-tuple of class import path (or just name if it lives
|
||||
|
@ -1,5 +1,6 @@
|
||||
from django.db.migrations.utils import field_references
|
||||
from django.db.models import NOT_PROVIDED
|
||||
from django.utils.copy import replace
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from .base import Operation, OperationCategory
|
||||
@ -134,8 +135,8 @@ class AddField(FieldOperation):
|
||||
):
|
||||
if isinstance(operation, AlterField):
|
||||
return [
|
||||
self.__class__(
|
||||
model_name=self.model_name,
|
||||
replace(
|
||||
self,
|
||||
name=operation.name,
|
||||
field=operation.field,
|
||||
),
|
||||
@ -144,10 +145,9 @@ class AddField(FieldOperation):
|
||||
return []
|
||||
elif isinstance(operation, RenameField):
|
||||
return [
|
||||
self.__class__(
|
||||
model_name=self.model_name,
|
||||
replace(
|
||||
self,
|
||||
name=operation.new_name,
|
||||
field=self.field,
|
||||
),
|
||||
]
|
||||
return super().reduce(operation, app_label)
|
||||
@ -264,10 +264,9 @@ class AlterField(FieldOperation):
|
||||
):
|
||||
return [
|
||||
operation,
|
||||
self.__class__(
|
||||
model_name=self.model_name,
|
||||
replace(
|
||||
self,
|
||||
name=operation.new_name,
|
||||
field=self.field,
|
||||
),
|
||||
]
|
||||
return super().reduce(operation, app_label)
|
||||
@ -351,11 +350,7 @@ class RenameField(FieldOperation):
|
||||
and self.new_name_lower == operation.old_name_lower
|
||||
):
|
||||
return [
|
||||
self.__class__(
|
||||
self.model_name,
|
||||
self.old_name,
|
||||
operation.new_name,
|
||||
),
|
||||
replace(self, new_name=operation.new_name),
|
||||
]
|
||||
# Skip `FieldOperation.reduce` as we want to run `references_field`
|
||||
# against self.old_name and self.new_name.
|
||||
|
@ -1,8 +1,11 @@
|
||||
from copy import copy
|
||||
|
||||
from django.db import models
|
||||
from django.db.migrations.operations.base import Operation, OperationCategory
|
||||
from django.db.migrations.state import ModelState
|
||||
from django.db.migrations.utils import field_references, resolve_relation
|
||||
from django.db.models.options import normalize_together
|
||||
from django.utils.copy import replace
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from .fields import AddField, AlterField, FieldOperation, RemoveField, RenameField
|
||||
@ -145,15 +148,7 @@ class CreateModel(ModelOperation):
|
||||
isinstance(operation, RenameModel)
|
||||
and self.name_lower == operation.old_name_lower
|
||||
):
|
||||
return [
|
||||
self.__class__(
|
||||
operation.new_name,
|
||||
fields=self.fields,
|
||||
options=self.options,
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
return [replace(self, name=operation.new_name)]
|
||||
elif (
|
||||
isinstance(operation, AlterModelOptions)
|
||||
and self.name_lower == operation.name_lower
|
||||
@ -162,42 +157,20 @@ class CreateModel(ModelOperation):
|
||||
for key in operation.ALTER_OPTION_KEYS:
|
||||
if key not in operation.options:
|
||||
options.pop(key, None)
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
options=options,
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
return [replace(self, options=options)]
|
||||
elif (
|
||||
isinstance(operation, AlterModelManagers)
|
||||
and self.name_lower == operation.name_lower
|
||||
):
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
options=self.options,
|
||||
bases=self.bases,
|
||||
managers=operation.managers,
|
||||
),
|
||||
]
|
||||
return [replace(self, managers=operation.managers)]
|
||||
elif (
|
||||
isinstance(operation, AlterModelTable)
|
||||
and self.name_lower == operation.name_lower
|
||||
):
|
||||
return [
|
||||
CreateModel(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
options={
|
||||
**self.options,
|
||||
"db_table": operation.table,
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
replace(
|
||||
self,
|
||||
options={**self.options, "db_table": operation.table},
|
||||
),
|
||||
]
|
||||
elif (
|
||||
@ -205,15 +178,12 @@ class CreateModel(ModelOperation):
|
||||
and self.name_lower == operation.name_lower
|
||||
):
|
||||
return [
|
||||
CreateModel(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
replace(
|
||||
self,
|
||||
options={
|
||||
**self.options,
|
||||
"db_table_comment": operation.table_comment,
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif (
|
||||
@ -221,15 +191,12 @@ class CreateModel(ModelOperation):
|
||||
and self.name_lower == operation.name_lower
|
||||
):
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
replace(
|
||||
self,
|
||||
options={
|
||||
**self.options,
|
||||
**{operation.option_name: operation.option_value},
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif (
|
||||
@ -237,15 +204,12 @@ class CreateModel(ModelOperation):
|
||||
and self.name_lower == operation.name_lower
|
||||
):
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
replace(
|
||||
self,
|
||||
options={
|
||||
**self.options,
|
||||
"order_with_respect_to": operation.order_with_respect_to,
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif (
|
||||
@ -254,25 +218,19 @@ class CreateModel(ModelOperation):
|
||||
):
|
||||
if isinstance(operation, AddField):
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
replace(
|
||||
self,
|
||||
fields=self.fields + [(operation.name, operation.field)],
|
||||
options=self.options,
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif isinstance(operation, AlterField):
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
replace(
|
||||
self,
|
||||
fields=[
|
||||
(n, operation.field if n == operation.name else v)
|
||||
for n, v in self.fields
|
||||
],
|
||||
options=self.options,
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif isinstance(operation, RemoveField):
|
||||
@ -297,16 +255,14 @@ class CreateModel(ModelOperation):
|
||||
if order_with_respect_to == operation.name_lower:
|
||||
del options["order_with_respect_to"]
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
replace(
|
||||
self,
|
||||
fields=[
|
||||
(n, v)
|
||||
for n, v in self.fields
|
||||
if n.lower() != operation.name_lower
|
||||
],
|
||||
options=options,
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif isinstance(operation, RenameField):
|
||||
@ -325,15 +281,13 @@ class CreateModel(ModelOperation):
|
||||
if order_with_respect_to == operation.old_name:
|
||||
options["order_with_respect_to"] = operation.new_name
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
replace(
|
||||
self,
|
||||
fields=[
|
||||
(operation.new_name if n == operation.old_name else n, v)
|
||||
for n, v in self.fields
|
||||
],
|
||||
options=options,
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif (
|
||||
@ -342,9 +296,8 @@ class CreateModel(ModelOperation):
|
||||
):
|
||||
if isinstance(operation, AddIndex):
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
replace(
|
||||
self,
|
||||
options={
|
||||
**self.options,
|
||||
"indexes": [
|
||||
@ -352,8 +305,6 @@ class CreateModel(ModelOperation):
|
||||
operation.index,
|
||||
],
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif isinstance(operation, RemoveIndex):
|
||||
@ -363,22 +314,18 @@ class CreateModel(ModelOperation):
|
||||
if index.name != operation.name
|
||||
]
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
replace(
|
||||
self,
|
||||
options={
|
||||
**self.options,
|
||||
"indexes": options_indexes,
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif isinstance(operation, AddConstraint):
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
replace(
|
||||
self,
|
||||
options={
|
||||
**self.options,
|
||||
"constraints": [
|
||||
@ -386,8 +333,6 @@ class CreateModel(ModelOperation):
|
||||
operation.constraint,
|
||||
],
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
elif isinstance(operation, RemoveConstraint):
|
||||
@ -397,15 +342,12 @@ class CreateModel(ModelOperation):
|
||||
if constraint.name != operation.name
|
||||
]
|
||||
return [
|
||||
self.__class__(
|
||||
self.name,
|
||||
fields=self.fields,
|
||||
replace(
|
||||
self,
|
||||
options={
|
||||
**self.options,
|
||||
"constraints": options_constraints,
|
||||
},
|
||||
bases=self.bases,
|
||||
managers=self.managers,
|
||||
),
|
||||
]
|
||||
return super().reduce(operation, app_label)
|
||||
@ -557,9 +499,9 @@ class RenameModel(ModelOperation):
|
||||
and self.new_name_lower == operation.old_name_lower
|
||||
):
|
||||
return [
|
||||
self.__class__(
|
||||
self.old_name,
|
||||
operation.new_name,
|
||||
replace(
|
||||
self,
|
||||
new_name=operation.new_name,
|
||||
),
|
||||
]
|
||||
# Skip `ModelOperation.reduce` as we want to run `references_model`
|
||||
@ -978,8 +920,9 @@ class AddIndex(IndexOperation):
|
||||
if isinstance(operation, RemoveIndex) and self.index.name == operation.name:
|
||||
return []
|
||||
if isinstance(operation, RenameIndex) and self.index.name == operation.old_name:
|
||||
self.index.name = operation.new_name
|
||||
return [self.__class__(model_name=self.model_name, index=self.index)]
|
||||
index = copy(self.index)
|
||||
index.name = operation.new_name
|
||||
return [replace(self, index=index)]
|
||||
return super().reduce(operation, app_label)
|
||||
|
||||
|
||||
@ -1172,11 +1115,9 @@ class RenameIndex(IndexOperation):
|
||||
and self.new_name_lower == operation.old_name_lower
|
||||
):
|
||||
return [
|
||||
self.__class__(
|
||||
self.model_name,
|
||||
replace(
|
||||
self,
|
||||
new_name=operation.new_name,
|
||||
old_name=self.old_name,
|
||||
old_fields=self.old_fields,
|
||||
)
|
||||
]
|
||||
return super().reduce(operation, app_label)
|
||||
|
17
django/utils/copy.py
Normal file
17
django/utils/copy.py
Normal 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)
|
Loading…
Reference in New Issue
Block a user