Fixed #33471 -- Made AlterField operation a noop when changing "choices".
This also allows customizing attributes of fields that don't affect a column definition.
This commit is contained in:
parent
6991880109
commit
65effbdb10
|
@ -1376,22 +1376,9 @@ class BaseDatabaseSchemaEditor:
|
||||||
# - changing only a field name
|
# - changing only a field name
|
||||||
# - changing an attribute that doesn't affect the schema
|
# - changing an attribute that doesn't affect the schema
|
||||||
# - adding only a db_column and the column name is not changed
|
# - adding only a db_column and the column name is not changed
|
||||||
non_database_attrs = [
|
for attr in old_field.non_db_attrs:
|
||||||
"blank",
|
|
||||||
"db_column",
|
|
||||||
"editable",
|
|
||||||
"error_messages",
|
|
||||||
"help_text",
|
|
||||||
"limit_choices_to",
|
|
||||||
# Database-level options are not supported, see #21961.
|
|
||||||
"on_delete",
|
|
||||||
"related_name",
|
|
||||||
"related_query_name",
|
|
||||||
"validators",
|
|
||||||
"verbose_name",
|
|
||||||
]
|
|
||||||
for attr in non_database_attrs:
|
|
||||||
old_kwargs.pop(attr, None)
|
old_kwargs.pop(attr, None)
|
||||||
|
for attr in new_field.non_db_attrs:
|
||||||
new_kwargs.pop(attr, None)
|
new_kwargs.pop(attr, None)
|
||||||
return self.quote_name(old_field.column) != self.quote_name(
|
return self.quote_name(old_field.column) != self.quote_name(
|
||||||
new_field.column
|
new_field.column
|
||||||
|
|
|
@ -140,6 +140,24 @@ class Field(RegisterLookupMixin):
|
||||||
system_check_deprecated_details = None
|
system_check_deprecated_details = None
|
||||||
system_check_removed_details = None
|
system_check_removed_details = None
|
||||||
|
|
||||||
|
# Attributes that don't affect a column definition.
|
||||||
|
# These attributes are ignored when altering the field.
|
||||||
|
non_db_attrs = (
|
||||||
|
"blank",
|
||||||
|
"choices",
|
||||||
|
"db_column",
|
||||||
|
"editable",
|
||||||
|
"error_messages",
|
||||||
|
"help_text",
|
||||||
|
"limit_choices_to",
|
||||||
|
# Database-level options are not supported, see #21961.
|
||||||
|
"on_delete",
|
||||||
|
"related_name",
|
||||||
|
"related_query_name",
|
||||||
|
"validators",
|
||||||
|
"verbose_name",
|
||||||
|
)
|
||||||
|
|
||||||
# Field flags
|
# Field flags
|
||||||
hidden = False
|
hidden = False
|
||||||
|
|
||||||
|
|
|
@ -314,6 +314,26 @@ reconstructing the field::
|
||||||
new_instance = MyField(*args, **kwargs)
|
new_instance = MyField(*args, **kwargs)
|
||||||
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)
|
self.assertEqual(my_field_instance.some_attribute, new_instance.some_attribute)
|
||||||
|
|
||||||
|
.. _custom-field-non_db_attrs:
|
||||||
|
|
||||||
|
Field attributes not affecting database column definition
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
You can override ``Field.non_db_attrs`` to customize attributes of a field that
|
||||||
|
don't affect a column definition. It's used during model migrations to detect
|
||||||
|
no-op ``AlterField`` operations.
|
||||||
|
|
||||||
|
For example::
|
||||||
|
|
||||||
|
class CommaSepField(models.Field):
|
||||||
|
|
||||||
|
@property
|
||||||
|
def non_db_attrs(self):
|
||||||
|
return super().non_db_attrs + ("separator",)
|
||||||
|
|
||||||
|
|
||||||
Changing a custom field's base class
|
Changing a custom field's base class
|
||||||
------------------------------------
|
------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -288,6 +288,10 @@ Models
|
||||||
on MariaDB and MySQL. For databases that do not support ``XOR``, the query
|
on MariaDB and MySQL. For databases that do not support ``XOR``, the query
|
||||||
will be converted to an equivalent using ``AND``, ``OR``, and ``NOT``.
|
will be converted to an equivalent using ``AND``, ``OR``, and ``NOT``.
|
||||||
|
|
||||||
|
* The new :ref:`Field.non_db_attrs <custom-field-non_db_attrs>` attribute
|
||||||
|
allows customizing attributes of fields that don't affect a column
|
||||||
|
definition.
|
||||||
|
|
||||||
Requests and Responses
|
Requests and Responses
|
||||||
~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -3961,6 +3961,20 @@ class SchemaTests(TransactionTestCase):
|
||||||
with connection.schema_editor() as editor, self.assertNumQueries(0):
|
with connection.schema_editor() as editor, self.assertNumQueries(0):
|
||||||
editor.alter_field(Book, new_field, old_field, strict=True)
|
editor.alter_field(Book, new_field, old_field, strict=True)
|
||||||
|
|
||||||
|
def test_alter_field_choices_noop(self):
|
||||||
|
with connection.schema_editor() as editor:
|
||||||
|
editor.create_model(Author)
|
||||||
|
old_field = Author._meta.get_field("name")
|
||||||
|
new_field = CharField(
|
||||||
|
choices=(("Jane", "Jane"), ("Joe", "Joe")),
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
|
new_field.set_attributes_from_name("name")
|
||||||
|
with connection.schema_editor() as editor, self.assertNumQueries(0):
|
||||||
|
editor.alter_field(Author, old_field, new_field, strict=True)
|
||||||
|
with connection.schema_editor() as editor, self.assertNumQueries(0):
|
||||||
|
editor.alter_field(Author, new_field, old_field, strict=True)
|
||||||
|
|
||||||
def test_add_textfield_unhashable_default(self):
|
def test_add_textfield_unhashable_default(self):
|
||||||
# Create the table
|
# Create the table
|
||||||
with connection.schema_editor() as editor:
|
with connection.schema_editor() as editor:
|
||||||
|
|
Loading…
Reference in New Issue