mirror of
				https://github.com/django/django.git
				synced 2025-10-28 16:16:12 +00:00 
			
		
		
		
	[1.7.x] Fixed #22783: Make sure swappable models come first in creation
This commit is contained in:
		| @@ -5,6 +5,7 @@ import datetime | |||||||
|  |  | ||||||
| from django.utils import six | from django.utils import six | ||||||
| from django.db import models | from django.db import models | ||||||
|  | from django.conf import settings | ||||||
| from django.db.migrations import operations | from django.db.migrations import operations | ||||||
| from django.db.migrations.migration import Migration | from django.db.migrations.migration import Migration | ||||||
| from django.db.migrations.questioner import MigrationQuestioner | from django.db.migrations.questioner import MigrationQuestioner | ||||||
| @@ -345,6 +346,27 @@ class MigrationAutodetector(object): | |||||||
|         operation._auto_deps = dependencies or [] |         operation._auto_deps = dependencies or [] | ||||||
|         self.generated_operations.setdefault(app_label, []).append(operation) |         self.generated_operations.setdefault(app_label, []).append(operation) | ||||||
|  |  | ||||||
|  |     def swappable_first_key(self, item): | ||||||
|  |         """ | ||||||
|  |         Sorting key function that places potential swappable models first in | ||||||
|  |         lists of created models (only real way to solve #22783) | ||||||
|  |         """ | ||||||
|  |         try: | ||||||
|  |             model = self.new_apps.get_model(item[0], item[1]) | ||||||
|  |             base_names = [base.__name__ for base in model.__bases__] | ||||||
|  |             string_version = "%s.%s" % (item[0], item[1]) | ||||||
|  |             if ( | ||||||
|  |                 model._meta.swappable or | ||||||
|  |                 "AbstractUser" in base_names or | ||||||
|  |                 "AbstractBaseUser" in base_names or | ||||||
|  |                 settings.AUTH_USER_MODEL.lower() == string_version.lower() | ||||||
|  |             ): | ||||||
|  |                 return ("___" + item[0], "___" + item[1]) | ||||||
|  |         except LookupError: | ||||||
|  |             pass | ||||||
|  |         return item | ||||||
|  |  | ||||||
|  |  | ||||||
|     def generate_renamed_models(self): |     def generate_renamed_models(self): | ||||||
|         """ |         """ | ||||||
|         Finds any renamed models, and generates the operations for them, |         Finds any renamed models, and generates the operations for them, | ||||||
| @@ -388,7 +410,7 @@ class MigrationAutodetector(object): | |||||||
|         that might be deferred (e.g. unique_together, index_together) |         that might be deferred (e.g. unique_together, index_together) | ||||||
|         """ |         """ | ||||||
|         added_models = set(self.new_model_keys) - set(self.old_model_keys) |         added_models = set(self.new_model_keys) - set(self.old_model_keys) | ||||||
|         for app_label, model_name in sorted(added_models): |         for app_label, model_name in sorted(added_models, key=self.swappable_first_key): | ||||||
|             model_state = self.to_state.models[app_label, model_name] |             model_state = self.to_state.models[app_label, model_name] | ||||||
|             # Gather related fields |             # Gather related fields | ||||||
|             related_fields = {} |             related_fields = {} | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ from django.db.migrations.questioner import MigrationQuestioner | |||||||
| from django.db.migrations.state import ProjectState, ModelState | from django.db.migrations.state import ProjectState, ModelState | ||||||
| from django.db.migrations.graph import MigrationGraph | from django.db.migrations.graph import MigrationGraph | ||||||
| from django.db import models | from django.db import models | ||||||
|  | from django.contrib.auth.models import AbstractBaseUser | ||||||
|  |  | ||||||
|  |  | ||||||
| class DeconstructableObject(object): | class DeconstructableObject(object): | ||||||
| @@ -65,7 +66,9 @@ class AutodetectorTests(TestCase): | |||||||
|     book_unique_3 = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("newfield", models.IntegerField()), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))], {"unique_together": [("title", "newfield")]}) |     book_unique_3 = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("newfield", models.IntegerField()), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))], {"unique_together": [("title", "newfield")]}) | ||||||
|     attribution = ModelState("otherapp", "Attribution", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("book", models.ForeignKey("otherapp.Book"))]) |     attribution = ModelState("otherapp", "Attribution", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("book", models.ForeignKey("otherapp.Book"))]) | ||||||
|     edition = ModelState("thirdapp", "Edition", [("id", models.AutoField(primary_key=True)), ("book", models.ForeignKey("otherapp.Book"))]) |     edition = ModelState("thirdapp", "Edition", [("id", models.AutoField(primary_key=True)), ("book", models.ForeignKey("otherapp.Book"))]) | ||||||
|     custom_user = ModelState("thirdapp", "CustomUser", [("id", models.AutoField(primary_key=True)), ("username", models.CharField(max_length=255))]) |     custom_user = ModelState("thirdapp", "CustomUser", [("id", models.AutoField(primary_key=True)), ("username", models.CharField(max_length=255))], bases=(AbstractBaseUser, )) | ||||||
|  |     custom_user_no_inherit = ModelState("thirdapp", "CustomUser", [("id", models.AutoField(primary_key=True)), ("username", models.CharField(max_length=255))]) | ||||||
|  |     aardvark = ModelState("thirdapp", "Aardvark", [("id", models.AutoField(primary_key=True))]) | ||||||
|     knight = ModelState("eggs", "Knight", [("id", models.AutoField(primary_key=True))]) |     knight = ModelState("eggs", "Knight", [("id", models.AutoField(primary_key=True))]) | ||||||
|     rabbit = ModelState("eggs", "Rabbit", [("id", models.AutoField(primary_key=True)), ("knight", models.ForeignKey("eggs.Knight")), ("parent", models.ForeignKey("eggs.Rabbit"))], {"unique_together": [("parent", "knight")]}) |     rabbit = ModelState("eggs", "Rabbit", [("id", models.AutoField(primary_key=True)), ("knight", models.ForeignKey("eggs.Knight")), ("parent", models.ForeignKey("eggs.Rabbit"))], {"unique_together": [("parent", "knight")]}) | ||||||
|  |  | ||||||
| @@ -901,3 +904,34 @@ class AutodetectorTests(TestCase): | |||||||
|         self.assertOperationAttributes(changes, 'testapp', 0, 1, name="author", order_with_respect_to="book") |         self.assertOperationAttributes(changes, 'testapp', 0, 1, name="author", order_with_respect_to="book") | ||||||
|         # Make sure the _order field is not in the CreateModel fields |         # Make sure the _order field is not in the CreateModel fields | ||||||
|         self.assertNotIn("_order", [name for name, field in changes['testapp'][0].operations[0].fields]) |         self.assertNotIn("_order", [name for name, field in changes['testapp'][0].operations[0].fields]) | ||||||
|  |  | ||||||
|  |     def test_swappable_first_inheritance(self): | ||||||
|  |         """ | ||||||
|  |         Tests that swappable models get their CreateModel first. | ||||||
|  |         """ | ||||||
|  |         # Make state | ||||||
|  |         before = self.make_project_state([]) | ||||||
|  |         after = self.make_project_state([self.custom_user, self.aardvark]) | ||||||
|  |         autodetector = MigrationAutodetector(before, after) | ||||||
|  |         changes = autodetector._detect_changes() | ||||||
|  |         # Right number of migrations? | ||||||
|  |         self.assertNumberMigrations(changes, 'thirdapp', 1) | ||||||
|  |         self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel", "CreateModel"]) | ||||||
|  |         self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="CustomUser") | ||||||
|  |         self.assertOperationAttributes(changes, 'thirdapp', 0, 1, name="Aardvark") | ||||||
|  |  | ||||||
|  |     @override_settings(AUTH_USER_MODEL="thirdapp.CustomUser") | ||||||
|  |     def test_swappable_first_setting(self): | ||||||
|  |         """ | ||||||
|  |         Tests that swappable models get their CreateModel first. | ||||||
|  |         """ | ||||||
|  |         # Make state | ||||||
|  |         before = self.make_project_state([]) | ||||||
|  |         after = self.make_project_state([self.custom_user_no_inherit, self.aardvark]) | ||||||
|  |         autodetector = MigrationAutodetector(before, after) | ||||||
|  |         changes = autodetector._detect_changes() | ||||||
|  |         # Right number of migrations? | ||||||
|  |         self.assertNumberMigrations(changes, 'thirdapp', 1) | ||||||
|  |         self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel", "CreateModel"]) | ||||||
|  |         self.assertOperationAttributes(changes, 'thirdapp', 0, 0, name="CustomUser") | ||||||
|  |         self.assertOperationAttributes(changes, 'thirdapp', 0, 1, name="Aardvark") | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user