From 5732424beed38f81870f2dd9d913310bca1eaf44 Mon Sep 17 00:00:00 2001 From: Tianyi Wang Date: Tue, 14 Oct 2014 15:20:24 +0100 Subject: [PATCH] Fixed #23629 -- Allowed autodetector to detect changes in Meta.db_table. Thanks Naddiseo for reporting. --- django/db/migrations/autodetector.py | 18 ++++++ docs/releases/1.7.1.txt | 3 + tests/migrations/test_autodetector.py | 91 ++++++++++++++++++++++++++- 3 files changed, 111 insertions(+), 1 deletion(-) diff --git a/django/db/migrations/autodetector.py b/django/db/migrations/autodetector.py index 9674222aee..972c978c6f 100644 --- a/django/db/migrations/autodetector.py +++ b/django/db/migrations/autodetector.py @@ -183,6 +183,7 @@ class MigrationAutodetector(object): self.generate_altered_fields() self.generate_altered_unique_together() self.generate_altered_index_together() + self.generate_altered_db_table() self.generate_altered_order_with_respect_to() # Now, reordering to make things possible. The order we have already @@ -910,6 +911,23 @@ class MigrationAutodetector(object): def generate_altered_index_together(self): self._generate_altered_foo_together(operations.AlterIndexTogether) + def generate_altered_db_table(self): + models_to_check = self.kept_model_keys.union(self.kept_proxy_keys).union(self.kept_unmanaged_keys) + for app_label, model_name in sorted(models_to_check): + 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] + new_model_state = self.to_state.models[app_label, model_name] + old_db_table_name = old_model_state.options.get('db_table') + new_db_table_name = new_model_state.options.get('db_table') + if old_db_table_name != new_db_table_name: + self.add_operation( + app_label, + operations.AlterModelTable( + name=model_name, + table=new_db_table_name, + ) + ) + def generate_altered_options(self): """ Works out if any non-schema-affecting options have changed and diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt index b7cea71cb7..f356d9d8d5 100644 --- a/docs/releases/1.7.1.txt +++ b/docs/releases/1.7.1.txt @@ -126,3 +126,6 @@ Bugfixes * Made the Oracle test database creation drop the test user in the event of an unclean exit of a previous test run (:ticket:`23649`). + +* Fixed :djadmin:`makemigrations` to detect changes to + :attr:`Meta.db_table ` (:ticket:`23629`). diff --git a/tests/migrations/test_autodetector.py b/tests/migrations/test_autodetector.py index 25b11e4f08..89f5eee430 100644 --- a/tests/migrations/test_autodetector.py +++ b/tests/migrations/test_autodetector.py @@ -58,6 +58,15 @@ class AutodetectorTests(TestCase): ]) author_with_m2m_through = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("publishers", models.ManyToManyField("testapp.Publisher", through="testapp.Contract"))]) author_with_options = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True))], {"verbose_name": "Authi", "permissions": [('can_hire', 'Can hire')]}) + author_with_db_table_options = ModelState("testapp", "Author", [ + ("id", models.AutoField(primary_key=True)) + ], {"db_table": "author_one"}) + author_with_new_db_table_options = ModelState("testapp", "Author", [ + ("id", models.AutoField(primary_key=True)) + ], {"db_table": "author_two"}) + author_renamed_with_db_table_options = ModelState("testapp", "NewAuthor", [ + ("id", models.AutoField(primary_key=True)) + ], {"db_table": "author_one"}) contract = ModelState("testapp", "Contract", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("publisher", models.ForeignKey("testapp.Publisher"))]) publisher = ModelState("testapp", "Publisher", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=100))]) publisher_with_author = ModelState("testapp", "Publisher", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("name", models.CharField(max_length=100))]) @@ -397,7 +406,6 @@ class AutodetectorTests(TestCase): after = self.make_project_state([self.author_renamed_with_book, self.book_with_author_renamed]) autodetector = MigrationAutodetector(before, after, MigrationQuestioner({"ask_rename_model": True})) changes = autodetector._detect_changes() - # Right number of migrations for model rename? self.assertNumberMigrations(changes, 'testapp', 1) # Right number of actions? @@ -609,6 +617,87 @@ class AutodetectorTests(TestCase): # Right number of migrations? self.assertEqual(len(changes), 0) + def test_alter_db_table_add(self): + """Tests detection for adding db_table in model's options""" + # Make state + before = self.make_project_state([self.author_empty]) + after = self.make_project_state([self.author_with_db_table_options]) + autodetector = MigrationAutodetector(before, after) + changes = autodetector._detect_changes() + # Right number of migrations? + self.assertEqual(len(changes), 1) + # Right number of actions? + migration = changes['testapp'][0] + self.assertEqual(len(migration.operations), 1) + action = migration.operations[0] + self.assertEqual(action.__class__.__name__, "AlterModelTable") + self.assertEqual(action.name, "author") + self.assertEqual(action.table, "author_one") + + def test_alter_db_table_change(self): + "Tests detection for changing db_table in model's options'" + # Make state + before = self.make_project_state([self.author_with_db_table_options]) + after = self.make_project_state([self.author_with_new_db_table_options]) + autodetector = MigrationAutodetector(before, after) + changes = autodetector._detect_changes() + # Right number of migrations? + self.assertEqual(len(changes), 1) + # Right number of actions? + migration = changes['testapp'][0] + self.assertEqual(len(migration.operations), 1) + action = migration.operations[0] + self.assertEqual(action.__class__.__name__, "AlterModelTable") + self.assertEqual(action.name, "author") + self.assertEqual(action.table, "author_two") + + def test_alter_db_table_remove(self): + """Tests detection for removing db_table in model's options""" + # Make state + before = self.make_project_state([self.author_with_db_table_options]) + after = self.make_project_state([self.author_empty]) + autodetector = MigrationAutodetector(before, after) + changes = autodetector._detect_changes() + # Right number of migrations? + self.assertEqual(len(changes), 1) + # Right number of actions? + migration = changes['testapp'][0] + self.assertEqual(len(migration.operations), 1) + action = migration.operations[0] + self.assertEqual(action.__class__.__name__, "AlterModelTable") + self.assertEqual(action.name, "author") + self.assertEqual(action.table, None) + + def test_alter_db_table_no_changes(self): + """ + Tests that alter_db_table doesn't generate a migration if no changes + have been made. + """ + # Make state + before = self.make_project_state([self.author_with_db_table_options]) + after = self.make_project_state([self.author_with_db_table_options]) + autodetector = MigrationAutodetector(before, after) + changes = autodetector._detect_changes() + # Right number of migrations? + self.assertEqual(len(changes), 0) + + def test_alter_db_table_with_model_change(self): + """ + Tests when model changes, autodetector does not create more than one + operation. + """ + # Make state + before = self.make_project_state([self.author_with_db_table_options]) + after = self.make_project_state([self.author_renamed_with_db_table_options]) + autodetector = MigrationAutodetector( + before, after, MigrationQuestioner({"ask_rename_model": True}) + ) + changes = autodetector._detect_changes() + # Right number of migrations? + self.assertEqual(len(changes), 1) + migration = changes['testapp'][0] + self.assertEqual(len(migration.operations), 1) + def test_empty_foo_together(self): "#23452 - Empty unique/index_togther shouldn't generate a migration." # Explicitly testing for not specified, since this is the case after