Opened 14 months ago
Closed 14 months ago
#34820 closed Bug (fixed)
Migrations crashes when changing ForeignObject properties.
Reported by: | puc_dong | Owned by: | puc_dong |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 4.2 |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Ready for checkin | |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description (last modified by )
Execute migrate to report an error
If the models.ForeignObject attribute is used, changing null or blank to generate a new migration record will result in an error message when executing the migrate again
File "/Users/donghao/test/test_migations/manage.py", line 22, in <module> execute_from_command_line(sys.argv) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line utility.execute() File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/__init__.py", line 436, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/base.py", line 412, in run_from_argv self.execute(*args, **cmd_options) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/base.py", line 458, in execute output = self.handle(*args, **options) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/base.py", line 106, in wrapper res = handle_func(*args, **kwargs) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/commands/migrate.py", line 356, in handle post_migrate_state = executor.migrate( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/executor.py", line 135, in migrate state = self._migrate_all_forwards( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/executor.py", line 167, in _migrate_all_forwards state = self.apply_migration( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/executor.py", line 252, in apply_migration state = migration.apply(state, schema_editor) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/migration.py", line 132, in apply operation.database_forwards( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/operations/fields.py", line 235, in database_forwards schema_editor.alter_field(from_model, from_field, to_field) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 785, in alter_field if not self._field_should_be_altered(old_field, new_field): File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 1530, in _field_should_be_altered return self.quote_name(old_field.column) != self.quote_name( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 204, in quote_name return self.connection.ops.quote_name(name) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/mysql/operations.py", line 184, in quote_name if name.startswith("`") and name.endswith("`"): AttributeError: 'NoneType' object has no attribute 'startswith'
models:
# app1: class Model1(models.Model): name = models.CharField(max_length=50) # app2: class Model2(models.Model): name = models.CharField(max_length=100, db_index=True) clu_id = models.BigIntegerField() scenes = models.ForeignObject(Model1, from_fields=['clu_id'], to_fields=['id'], null=True, related_name='model2_config', on_delete=models.DO_NOTHING)
Successfully executed makemigrations&migrate for the first time. When I add blank=True to the scenes field of Model2, and then execute makemigrations and migrate to report an error
All migration files:
app1.0001_initial.py
class Migration(migrations.Migration): initial = True dependencies = [ ] operations = [ migrations.CreateModel( name='Model1', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=50)), ], ), ]
app2.0001_initial.py
class Migration(migrations.Migration): initial = True dependencies = [ ('app_1', '0001_initial'), ] operations = [ migrations.CreateModel( name='Model2', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(db_index=True, max_length=100)), ('clu_id', models.BigIntegerField()), ('scenes', models.ForeignObject(from_fields=['clu_id'], null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='model2_config', to='app_1.model1', to_fields=['id'])), ], ), ]
app2.0002_alter_model2_scenes.py
class Migration(migrations.Migration): dependencies = [ ('app_1', '0001_initial'), ('app_2', '0001_initial'), ] operations = [ migrations.AlterField( model_name='model2', name='scenes', field=models.ForeignObject(blank=True, from_fields=('clu_id',), null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='model2_config', to='app_1.model1', to_fields=('id',)), ), ]
Change History (17)
comment:1 by , 14 months ago
Description: | modified (diff) |
---|
comment:2 by , 14 months ago
Description: | modified (diff) |
---|
follow-up: 6 comment:3 by , 14 months ago
Component: | Database layer (models, ORM) → Migrations |
---|---|
Needs tests: | set |
Resolution: | → needsinfo |
Status: | new → closed |
comment:4 by , 14 months ago
Component: | Migrations → Database layer (models, ORM) |
---|---|
Description: | modified (diff) |
Needs tests: | unset |
comment:5 by , 14 months ago
Resolution: | needsinfo |
---|---|
Status: | closed → new |
comment:6 by , 14 months ago
Replying to Mariusz Felisiak:
I'm not sure how to you reach a field without a
column
in migrations. I don't think you've explained the issue in enough detail to confirm a bug in Django, can you provide a sample project or test case that reproduces the issue?
I submitted my case code and process
comment:7 by , 14 months ago
(simpleui) ☁ test_migations python manage.py makemigrations System check identified some issues: WARNINGS: app_1.Model1: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App1Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. app_2.Model2: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App2Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. Migrations for 'app_1': app_1/migrations/0001_initial.py - Create model Model1 Migrations for 'app_2': app_2/migrations/0001_initial.py - Create model Model2 (simpleui) ☁ test_migations python manage.py migrate System check identified some issues: WARNINGS: app_1.Model1: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App1Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. app_2.Model2: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App2Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. Operations to perform: Apply all migrations: admin, app_1, app_2, auth, contenttypes, sessions Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying app_1.0001_initial... OK Applying app_2.0001_initial... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying sessions.0001_initial... OK **Add blank=True to the scenes field of Model2** (simpleui) ☁ test_migations python manage.py makemigrations System check identified some issues: WARNINGS: app_1.Model1: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App1Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. app_2.Model2: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App2Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. Migrations for 'app_2': app_2/migrations/0002_alter_model2_scenes.py - Alter field scenes on model2 (simpleui) ☁ test_migations python manage.py migrate System check identified some issues: WARNINGS: app_1.Model1: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App1Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. app_2.Model2: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'. HINT: Configure the DEFAULT_AUTO_FIELD setting or the App2Config.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'. Operations to perform: Apply all migrations: admin, app_1, app_2, auth, contenttypes, sessions Running migrations: Applying app_2.0002_alter_model2_scenes...Traceback (most recent call last): File "/Users/donghao/test/test_migations/manage.py", line 22, in <module> execute_from_command_line(sys.argv) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line utility.execute() File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/__init__.py", line 436, in execute self.fetch_command(subcommand).run_from_argv(self.argv) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/base.py", line 412, in run_from_argv self.execute(*args, **cmd_options) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/base.py", line 458, in execute output = self.handle(*args, **options) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/base.py", line 106, in wrapper res = handle_func(*args, **kwargs) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/core/management/commands/migrate.py", line 356, in handle post_migrate_state = executor.migrate( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/executor.py", line 135, in migrate state = self._migrate_all_forwards( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/executor.py", line 167, in _migrate_all_forwards state = self.apply_migration( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/executor.py", line 252, in apply_migration state = migration.apply(state, schema_editor) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/migration.py", line 132, in apply operation.database_forwards( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/migrations/operations/fields.py", line 235, in database_forwards schema_editor.alter_field(from_model, from_field, to_field) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 785, in alter_field if not self._field_should_be_altered(old_field, new_field): File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 1520, in _field_should_be_altered return self.quote_name(old_field.column) != self.quote_name( File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/base/schema.py", line 204, in quote_name return self.connection.ops.quote_name(name) File "/Users/donghao/.virtualenvs/simpleui/lib/python3.9/site-packages/django/db/backends/mysql/operations.py", line 184, in quote_name if name.startswith("`") and name.endswith("`"): AttributeError: 'NoneType' object has no attribute 'startswith'
follow-up: 9 comment:8 by , 14 months ago
Resolution: | → invalid |
---|---|
Status: | new → closed |
ForeignObject
is an abstraction level, not a proper model field. You should use models.ForeignKey
. If you're having trouble understanding how Django works, see TicketClosingReasons/UseSupportChannels for ways to get help.
follow-up: 10 comment:9 by , 14 months ago
Replying to Mariusz Felisiak:
ForeignObject
is an abstraction level, not a proper model field. You should usemodels.ForeignKey
. If you're having trouble understanding how Django works, see TicketClosingReasons/UseSupportChannels for ways to get help.
We do not need to create a foreign key, we have used ForeignObject and currently only need to perform queries. Should this field not be generated in the migration record for the first time?
comment:10 by , 14 months ago
Replying to puc_dong:
Replying to Mariusz Felisiak:
ForeignObject
is an abstraction level, not a proper model field. You should usemodels.ForeignKey
. If you're having trouble understanding how Django works, see TicketClosingReasons/UseSupportChannels for ways to get help.
We do not need to create a foreign key, we have used ForeignObject and currently only need to perform queries. Should this field not be generated in the migration record for the first time?
It seems that you have a niche use case. Trac is not a support channel, you can try to discuss this with folks on the Django Forum.
comment:11 by , 14 months ago
Needs tests: | set |
---|---|
Patch needs improvement: | set |
Resolution: | invalid |
Status: | closed → new |
Summary: | Change the properties of ForeignObject object, such as blank, null, and execute migrate to report an error → Migrations crashes when changing ForeignObject properties. |
Triage Stage: | Unreviewed → Accepted |
After reconsideration I think we should fix this (check out similar report #25128).
comment:12 by , 14 months ago
Has patch: | unset |
---|---|
Needs tests: | unset |
Patch needs improvement: | unset |
follow-up: 14 comment:13 by , 14 months ago
A regression test:
-
tests/migrations/test_operations.py
diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index d4fc3e855a..6366ccd667 100644
a b class OperationTests(OperationTestBase): 2306 2306 operation.database_forwards(app_label, editor, new_state, project_state) 2307 2307 self.assertColumnExists(rider_table, "pony_id") 2308 2308 2309 def test_alter_field_foreignobject_noop(self): 2310 app_label = "test_alflfo_noop" 2311 project_state = self.set_up_test_model(app_label) 2312 project_state = self.apply_operations( 2313 app_label, 2314 project_state, 2315 [ 2316 migrations.CreateModel( 2317 "Rider", 2318 fields=[ 2319 ("pony_id", models.IntegerField()), 2320 ( 2321 "pony", 2322 models.ForeignObject( 2323 f"{app_label}.Pony", 2324 models.CASCADE, 2325 from_fields=("pony_id",), 2326 to_fields=("id",), 2327 ), 2328 ), 2329 ], 2330 ), 2331 ], 2332 ) 2333 operation = migrations.AlterField( 2334 "Rider", 2335 "pony", 2336 models.ForeignObject( 2337 f"{app_label}.Pony", 2338 models.CASCADE, 2339 from_fields=("pony_id",), 2340 to_fields=("id",), 2341 null=True, 2342 ), 2343 ) 2344 new_state = project_state.clone() 2345 operation.state_forwards(app_label, new_state) 2346 with self.assertNumQueries(0), connection.schema_editor() as editor: 2347 operation.database_forwards(app_label, editor, project_state, new_state) 2348 2309 2349 @skipUnlessDBFeature("supports_comments") 2310 2350 def test_alter_model_table_comment(self): 2311 2351 app_label = "test_almotaco"
comment:14 by , 14 months ago
Replying to Mariusz Felisiak:
A regression test:
tests/migrations/test_operations.py
diff --git a/tests/migrations/test_operations.py b/tests/migrations/test_operations.py index d4fc3e855a..6366ccd667 100644
a b class OperationTests(OperationTestBase): 2306 2306 operation.database_forwards(app_label, editor, new_state, project_state) 2307 2307 self.assertColumnExists(rider_table, "pony_id") 2308 2308 2309 def test_alter_field_foreignobject_noop(self): 2310 app_label = "test_alflfo_noop" 2311 project_state = self.set_up_test_model(app_label) 2312 project_state = self.apply_operations( 2313 app_label, 2314 project_state, 2315 [ 2316 migrations.CreateModel( 2317 "Rider", 2318 fields=[ 2319 ("pony_id", models.IntegerField()), 2320 ( 2321 "pony", 2322 models.ForeignObject( 2323 f"{app_label}.Pony", 2324 models.CASCADE, 2325 from_fields=("pony_id",), 2326 to_fields=("id",), 2327 ), 2328 ), 2329 ], 2330 ), 2331 ], 2332 ) 2333 operation = migrations.AlterField( 2334 "Rider", 2335 "pony", 2336 models.ForeignObject( 2337 f"{app_label}.Pony", 2338 models.CASCADE, 2339 from_fields=("pony_id",), 2340 to_fields=("id",), 2341 null=True, 2342 ), 2343 ) 2344 new_state = project_state.clone() 2345 operation.state_forwards(app_label, new_state) 2346 with self.assertNumQueries(0), connection.schema_editor() as editor: 2347 operation.database_forwards(app_label, editor, project_state, new_state) 2348 2309 2349 @skipUnlessDBFeature("supports_comments") 2310 2350 def test_alter_model_table_comment(self): 2311 2351 app_label = "test_almotaco"
I realized that there will be a default exclude behavior, and there are 6 queries that will be executed in database setup, so I modified the use case, and the 6 queries were not written directly.
This is my modification:https://github.com/django/django/pull/17240
comment:15 by , 14 months ago
Has patch: | set |
---|
comment:16 by , 14 months ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
Triage Stage: | Accepted → Ready for checkin |
I'm not sure how to you reach a field without a
column
in migrations. I don't think you've explained the issue in enough detail to confirm a bug in Django, can you provide a sample project or test case that reproduces the issue?