mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[1.7.x] Fixed #22568: Better proxy model support in migrations
This commit is contained in:
		| @@ -3,6 +3,7 @@ from __future__ import unicode_literals | |||||||
| import re | import re | ||||||
| import datetime | import datetime | ||||||
|  |  | ||||||
|  | from django.utils import six | ||||||
| from django.db import models | from django.db import models | ||||||
| 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 | ||||||
| @@ -104,23 +105,32 @@ class MigrationAutodetector(object): | |||||||
|         # into migrations to resolve dependencies caused by M2Ms and FKs. |         # into migrations to resolve dependencies caused by M2Ms and FKs. | ||||||
|         self.generated_operations = {} |         self.generated_operations = {} | ||||||
|  |  | ||||||
|         # Prepare some old/new state and model lists, ignoring |         # Prepare some old/new state and model lists, separating | ||||||
|         # proxy models and unmigrated apps. |         # proxy models and ignoring unmigrated apps. | ||||||
|         self.old_apps = self.from_state.render(ignore_swappable=True) |         self.old_apps = self.from_state.render(ignore_swappable=True) | ||||||
|         self.new_apps = self.to_state.render() |         self.new_apps = self.to_state.render() | ||||||
|         self.old_model_keys = [] |         self.old_model_keys = [] | ||||||
|  |         self.old_proxy_keys = [] | ||||||
|  |         self.new_model_keys = [] | ||||||
|  |         self.new_proxy_keys = [] | ||||||
|         for al, mn in sorted(self.from_state.models.keys()): |         for al, mn in sorted(self.from_state.models.keys()): | ||||||
|             model = self.old_apps.get_model(al, mn) |             model = self.old_apps.get_model(al, mn) | ||||||
|             if not model._meta.proxy and model._meta.managed and al not in self.from_state.real_apps: |             if model._meta.managed and al not in self.from_state.real_apps: | ||||||
|                 self.old_model_keys.append((al, mn)) |                 if model._meta.proxy: | ||||||
|         self.new_model_keys = [] |                     self.old_proxy_keys.append((al, mn)) | ||||||
|  |                 else: | ||||||
|  |                     self.old_model_keys.append((al, mn)) | ||||||
|  |  | ||||||
|         for al, mn in sorted(self.to_state.models.keys()): |         for al, mn in sorted(self.to_state.models.keys()): | ||||||
|             model = self.new_apps.get_model(al, mn) |             model = self.new_apps.get_model(al, mn) | ||||||
|             if not model._meta.proxy and model._meta.managed and ( |             if model._meta.managed and ( | ||||||
|                 al not in self.from_state.real_apps or |                 al not in self.from_state.real_apps or | ||||||
|                 (convert_apps and al in convert_apps) |                 (convert_apps and al in convert_apps) | ||||||
|             ): |             ): | ||||||
|                 self.new_model_keys.append((al, mn)) |                 if model._meta.proxy: | ||||||
|  |                     self.new_proxy_keys.append((al, mn)) | ||||||
|  |                 else: | ||||||
|  |                     self.new_model_keys.append((al, mn)) | ||||||
|  |  | ||||||
|         # Renames have to come first |         # Renames have to come first | ||||||
|         self.generate_renamed_models() |         self.generate_renamed_models() | ||||||
| @@ -155,6 +165,8 @@ class MigrationAutodetector(object): | |||||||
|         # Generate non-rename model operations |         # Generate non-rename model operations | ||||||
|         self.generate_created_models() |         self.generate_created_models() | ||||||
|         self.generate_deleted_models() |         self.generate_deleted_models() | ||||||
|  |         self.generate_created_proxies() | ||||||
|  |         self.generate_deleted_proxies() | ||||||
|         self.generate_altered_options() |         self.generate_altered_options() | ||||||
|  |  | ||||||
|         # Generate field operations |         # Generate field operations | ||||||
| @@ -232,7 +244,12 @@ class MigrationAutodetector(object): | |||||||
|                                 if self.migrations.get(dep[0], None): |                                 if self.migrations.get(dep[0], None): | ||||||
|                                     operation_dependencies.add((dep[0], self.migrations[dep[0]][-1].name)) |                                     operation_dependencies.add((dep[0], self.migrations[dep[0]][-1].name)) | ||||||
|                                 else: |                                 else: | ||||||
|                                     operation_dependencies.add((dep[0], "__latest__")) |                                     # If we can't find the other app, we add a __latest__ dependency, | ||||||
|  |                                     # but only if we've already been through once and checked everything | ||||||
|  |                                     if chop_mode: | ||||||
|  |                                         operation_dependencies.add((dep[0], "__latest__")) | ||||||
|  |                                     else: | ||||||
|  |                                         deps_satisfied = False | ||||||
|                     if deps_satisfied: |                     if deps_satisfied: | ||||||
|                         chopped.append(operation) |                         chopped.append(operation) | ||||||
|                         dependencies.update(operation_dependencies) |                         dependencies.update(operation_dependencies) | ||||||
| @@ -306,6 +323,12 @@ class MigrationAutodetector(object): | |||||||
|                 operation.model_name.lower() == dependency[1].lower() and |                 operation.model_name.lower() == dependency[1].lower() and | ||||||
|                 operation.name.lower() == dependency[2].lower() |                 operation.name.lower() == dependency[2].lower() | ||||||
|             ) |             ) | ||||||
|  |         # Removed model | ||||||
|  |         elif dependency[2] is None and dependency[3] is False: | ||||||
|  |             return ( | ||||||
|  |                 isinstance(operation, operations.DeleteModel) and | ||||||
|  |                 operation.name.lower() == dependency[1].lower() | ||||||
|  |             ) | ||||||
|         # order_with_respect_to being unset for a field |         # order_with_respect_to being unset for a field | ||||||
|         elif dependency[2] is not None and dependency[3] == "order_wrt_unset": |         elif dependency[2] is not None and dependency[3] == "order_wrt_unset": | ||||||
|             return ( |             return ( | ||||||
| @@ -384,7 +407,16 @@ class MigrationAutodetector(object): | |||||||
|             unique_together = model_state.options.pop('unique_together', None) |             unique_together = model_state.options.pop('unique_together', None) | ||||||
|             index_together = model_state.options.pop('index_together', None) |             index_together = model_state.options.pop('index_together', None) | ||||||
|             order_with_respect_to = model_state.options.pop('order_with_respect_to', None) |             order_with_respect_to = model_state.options.pop('order_with_respect_to', None) | ||||||
|             # Generate creation operatoin |             # Depend on the deletion of any possible proxy version of us | ||||||
|  |             dependencies = [ | ||||||
|  |                 (app_label, model_name, None, False), | ||||||
|  |             ] | ||||||
|  |             # Depend on all bases | ||||||
|  |             for base in model_state.bases: | ||||||
|  |                 if isinstance(base, six.string_types) and "." in base: | ||||||
|  |                     base_app_label, base_name = base.split(".", 1) | ||||||
|  |                     dependencies.append((base_app_label, base_name, None, False)) | ||||||
|  |             # Generate creation operation | ||||||
|             self.add_operation( |             self.add_operation( | ||||||
|                 app_label, |                 app_label, | ||||||
|                 operations.CreateModel( |                 operations.CreateModel( | ||||||
| @@ -392,7 +424,8 @@ class MigrationAutodetector(object): | |||||||
|                     fields=[d for d in model_state.fields if d[0] not in related_fields], |                     fields=[d for d in model_state.fields if d[0] not in related_fields], | ||||||
|                     options=model_state.options, |                     options=model_state.options, | ||||||
|                     bases=model_state.bases, |                     bases=model_state.bases, | ||||||
|                 ) |                 ), | ||||||
|  |                 dependencies = dependencies, | ||||||
|             ) |             ) | ||||||
|             # Generate operations for each related field |             # Generate operations for each related field | ||||||
|             for name, field in sorted(related_fields.items()): |             for name, field in sorted(related_fields.items()): | ||||||
| @@ -412,6 +445,8 @@ class MigrationAutodetector(object): | |||||||
|                         None, |                         None, | ||||||
|                         True |                         True | ||||||
|                     )) |                     )) | ||||||
|  |                 # Depend on our own model being created | ||||||
|  |                 dependencies.append((app_label, model_name, None, True)) | ||||||
|                 # Make operation |                 # Make operation | ||||||
|                 self.add_operation( |                 self.add_operation( | ||||||
|                     app_label, |                     app_label, | ||||||
| @@ -423,6 +458,11 @@ class MigrationAutodetector(object): | |||||||
|                     dependencies=list(set(dependencies)), |                     dependencies=list(set(dependencies)), | ||||||
|                 ) |                 ) | ||||||
|             # Generate other opns |             # Generate other opns | ||||||
|  |             related_dependencies = [ | ||||||
|  |                 (app_label, model_name, name, True) | ||||||
|  |                 for name, field in sorted(related_fields.items()) | ||||||
|  |             ] | ||||||
|  |             related_dependencies.append((app_label, model_name, None, True)) | ||||||
|             if unique_together: |             if unique_together: | ||||||
|                 self.add_operation( |                 self.add_operation( | ||||||
|                     app_label, |                     app_label, | ||||||
| @@ -430,10 +470,7 @@ class MigrationAutodetector(object): | |||||||
|                         name=model_name, |                         name=model_name, | ||||||
|                         unique_together=unique_together, |                         unique_together=unique_together, | ||||||
|                     ), |                     ), | ||||||
|                     dependencies=[ |                     dependencies=related_dependencies | ||||||
|                         (app_label, model_name, name, True) |  | ||||||
|                         for name, field in sorted(related_fields.items()) |  | ||||||
|                     ] |  | ||||||
|                 ) |                 ) | ||||||
|             if index_together: |             if index_together: | ||||||
|                 self.add_operation( |                 self.add_operation( | ||||||
| @@ -442,10 +479,7 @@ class MigrationAutodetector(object): | |||||||
|                         name=model_name, |                         name=model_name, | ||||||
|                         index_together=index_together, |                         index_together=index_together, | ||||||
|                     ), |                     ), | ||||||
|                     dependencies=[ |                     dependencies=related_dependencies | ||||||
|                         (app_label, model_name, name, True) |  | ||||||
|                         for name, field in sorted(related_fields.items()) |  | ||||||
|                     ] |  | ||||||
|                 ) |                 ) | ||||||
|             if order_with_respect_to: |             if order_with_respect_to: | ||||||
|                 self.add_operation( |                 self.add_operation( | ||||||
| @@ -456,9 +490,43 @@ class MigrationAutodetector(object): | |||||||
|                     ), |                     ), | ||||||
|                     dependencies=[ |                     dependencies=[ | ||||||
|                         (app_label, model_name, order_with_respect_to, True), |                         (app_label, model_name, order_with_respect_to, True), | ||||||
|  |                         (app_label, model_name, None, True), | ||||||
|                     ] |                     ] | ||||||
|                 ) |                 ) | ||||||
|  |  | ||||||
|  |     def generate_created_proxies(self): | ||||||
|  |         """ | ||||||
|  |         Makes CreateModel statements for proxy models. | ||||||
|  |         We use the same statements as that way there's less code duplication, | ||||||
|  |         but of course for proxy models we can skip all that pointless field | ||||||
|  |         stuff and just chuck out an operation. | ||||||
|  |         """ | ||||||
|  |         added_proxies = set(self.new_proxy_keys) - set(self.old_proxy_keys) | ||||||
|  |         for app_label, model_name in sorted(added_proxies): | ||||||
|  |             model_state = self.to_state.models[app_label, model_name] | ||||||
|  |             assert model_state.options.get("proxy", False) | ||||||
|  |             # Depend on the deletion of any possible non-proxy version of us | ||||||
|  |             dependencies = [ | ||||||
|  |                 (app_label, model_name, None, False), | ||||||
|  |             ] | ||||||
|  |             # Depend on all bases | ||||||
|  |             for base in model_state.bases: | ||||||
|  |                 if isinstance(base, six.string_types) and "." in base: | ||||||
|  |                     base_app_label, base_name = base.split(".", 1) | ||||||
|  |                     dependencies.append((base_app_label, base_name, None, False)) | ||||||
|  |             # Generate creation operation | ||||||
|  |             self.add_operation( | ||||||
|  |                 app_label, | ||||||
|  |                 operations.CreateModel( | ||||||
|  |                     name=model_state.name, | ||||||
|  |                     fields=[], | ||||||
|  |                     options=model_state.options, | ||||||
|  |                     bases=model_state.bases, | ||||||
|  |                 ), | ||||||
|  |                 # Depend on the deletion of any possible non-proxy version of us | ||||||
|  |                 dependencies = dependencies, | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     def generate_deleted_models(self): |     def generate_deleted_models(self): | ||||||
|         """ |         """ | ||||||
|         Find all deleted models and make creation operations for them, |         Find all deleted models and make creation operations for them, | ||||||
| @@ -547,6 +615,21 @@ class MigrationAutodetector(object): | |||||||
|                 dependencies=list(set(dependencies)), |                 dependencies=list(set(dependencies)), | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|  |     def generate_deleted_proxies(self): | ||||||
|  |         """ | ||||||
|  |         Makes DeleteModel statements for proxy models. | ||||||
|  |         """ | ||||||
|  |         deleted_proxies = set(self.old_proxy_keys) - set(self.new_proxy_keys) | ||||||
|  |         for app_label, model_name in sorted(deleted_proxies): | ||||||
|  |             model_state = self.from_state.models[app_label, model_name] | ||||||
|  |             assert model_state.options.get("proxy", False) | ||||||
|  |             self.add_operation( | ||||||
|  |                 app_label, | ||||||
|  |                 operations.DeleteModel( | ||||||
|  |                     name=model_state.name, | ||||||
|  |                 ), | ||||||
|  |             ) | ||||||
|  |  | ||||||
|     def generate_added_fields(self): |     def generate_added_fields(self): | ||||||
|         # New fields |         # New fields | ||||||
|         self.renamed_fields = {} |         self.renamed_fields = {} | ||||||
| @@ -687,7 +770,8 @@ class MigrationAutodetector(object): | |||||||
|         makes an operation to represent them in state changes (in case Python |         makes an operation to represent them in state changes (in case Python | ||||||
|         code in migrations needs them) |         code in migrations needs them) | ||||||
|         """ |         """ | ||||||
|         for app_label, model_name in sorted(self.kept_model_keys): |         models_to_check = self.kept_model_keys.union(set(self.new_proxy_keys).intersection(self.old_proxy_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_name = self.renamed_models.get((app_label, model_name), model_name) | ||||||
|             old_model_state = self.from_state.models[app_label, old_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] |             new_model_state = self.to_state.models[app_label, model_name] | ||||||
|   | |||||||
| @@ -32,17 +32,17 @@ class CreateModel(Operation): | |||||||
|     def database_forwards(self, app_label, schema_editor, from_state, to_state): |     def database_forwards(self, app_label, schema_editor, from_state, to_state): | ||||||
|         apps = to_state.render() |         apps = to_state.render() | ||||||
|         model = apps.get_model(app_label, self.name) |         model = apps.get_model(app_label, self.name) | ||||||
|         if router.allow_migrate(schema_editor.connection.alias, model): |         if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy: | ||||||
|             schema_editor.create_model(model) |             schema_editor.create_model(model) | ||||||
|  |  | ||||||
|     def database_backwards(self, app_label, schema_editor, from_state, to_state): |     def database_backwards(self, app_label, schema_editor, from_state, to_state): | ||||||
|         apps = from_state.render() |         apps = from_state.render() | ||||||
|         model = apps.get_model(app_label, self.name) |         model = apps.get_model(app_label, self.name) | ||||||
|         if router.allow_migrate(schema_editor.connection.alias, model): |         if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy: | ||||||
|             schema_editor.delete_model(model) |             schema_editor.delete_model(model) | ||||||
|  |  | ||||||
|     def describe(self): |     def describe(self): | ||||||
|         return "Create model %s" % (self.name, ) |         return "Create %smodel %s" % ("proxy " if self.options.get("proxy", False) else "", self.name) | ||||||
|  |  | ||||||
|     def references_model(self, name, app_label=None): |     def references_model(self, name, app_label=None): | ||||||
|         strings_to_check = [self.name] |         strings_to_check = [self.name] | ||||||
| @@ -85,13 +85,13 @@ class DeleteModel(Operation): | |||||||
|     def database_forwards(self, app_label, schema_editor, from_state, to_state): |     def database_forwards(self, app_label, schema_editor, from_state, to_state): | ||||||
|         apps = from_state.render() |         apps = from_state.render() | ||||||
|         model = apps.get_model(app_label, self.name) |         model = apps.get_model(app_label, self.name) | ||||||
|         if router.allow_migrate(schema_editor.connection.alias, model): |         if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy: | ||||||
|             schema_editor.delete_model(model) |             schema_editor.delete_model(model) | ||||||
|  |  | ||||||
|     def database_backwards(self, app_label, schema_editor, from_state, to_state): |     def database_backwards(self, app_label, schema_editor, from_state, to_state): | ||||||
|         apps = to_state.render() |         apps = to_state.render() | ||||||
|         model = apps.get_model(app_label, self.name) |         model = apps.get_model(app_label, self.name) | ||||||
|         if router.allow_migrate(schema_editor.connection.alias, model): |         if router.allow_migrate(schema_editor.connection.alias, model) and not model._meta.proxy: | ||||||
|             schema_editor.create_model(model) |             schema_editor.create_model(model) | ||||||
|  |  | ||||||
|     def references_model(self, name, app_label=None): |     def references_model(self, name, app_label=None): | ||||||
|   | |||||||
| @@ -163,7 +163,8 @@ class MigrationOptimizer(object): | |||||||
|         """ |         """ | ||||||
|         Folds a CreateModel and a DeleteModel into nothing. |         Folds a CreateModel and a DeleteModel into nothing. | ||||||
|         """ |         """ | ||||||
|         if operation.name.lower() == other.name.lower(): |         if operation.name.lower() == other.name.lower() and \ | ||||||
|  |             not operation.options.get("proxy", False): | ||||||
|             return [] |             return [] | ||||||
|  |  | ||||||
|     def reduce_model_alter_delete(self, operation, other, in_between): |     def reduce_model_alter_delete(self, operation, other, in_between): | ||||||
|   | |||||||
| @@ -35,7 +35,9 @@ class AutodetectorTests(TestCase): | |||||||
|     author_with_publisher = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("publisher", models.ForeignKey("testapp.Publisher"))]) |     author_with_publisher = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("publisher", models.ForeignKey("testapp.Publisher"))]) | ||||||
|     author_with_custom_user = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("user", models.ForeignKey("thirdapp.CustomUser"))]) |     author_with_custom_user = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("user", models.ForeignKey("thirdapp.CustomUser"))]) | ||||||
|     author_proxy = ModelState("testapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author", )) |     author_proxy = ModelState("testapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author", )) | ||||||
|  |     author_proxy_options = ModelState("testapp", "AuthorProxy", [], {"proxy": True, "verbose_name": "Super Author"}, ("testapp.author", )) | ||||||
|     author_proxy_notproxy = ModelState("testapp", "AuthorProxy", [], {}, ("testapp.author", )) |     author_proxy_notproxy = ModelState("testapp", "AuthorProxy", [], {}, ("testapp.author", )) | ||||||
|  |     author_proxy_third = ModelState("thirdapp", "AuthorProxy", [], {"proxy": True}, ("testapp.author", )) | ||||||
|     author_unmanaged = ModelState("testapp", "AuthorUnmanaged", [], {"managed": False}, ("testapp.author", )) |     author_unmanaged = ModelState("testapp", "AuthorUnmanaged", [], {"managed": False}, ("testapp.author", )) | ||||||
|     author_unmanaged_managed = ModelState("testapp", "AuthorUnmanaged", [], {}, ("testapp.author", )) |     author_unmanaged_managed = ModelState("testapp", "AuthorUnmanaged", [], {}, ("testapp.author", )) | ||||||
|     author_with_m2m = ModelState("testapp", "Author", [ |     author_with_m2m = ModelState("testapp", "Author", [ | ||||||
| @@ -52,6 +54,7 @@ class AutodetectorTests(TestCase): | |||||||
|     other_stable = ModelState("otherapp", "Stable", [("id", models.AutoField(primary_key=True))]) |     other_stable = ModelState("otherapp", "Stable", [("id", models.AutoField(primary_key=True))]) | ||||||
|     third_thing = ModelState("thirdapp", "Thing", [("id", models.AutoField(primary_key=True))]) |     third_thing = ModelState("thirdapp", "Thing", [("id", models.AutoField(primary_key=True))]) | ||||||
|     book = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))]) |     book = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Author")), ("title", models.CharField(max_length=200))]) | ||||||
|  |     book_proxy_fk = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("thirdapp.AuthorProxy")), ("title", models.CharField(max_length=200))]) | ||||||
|     book_with_no_author = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("title", models.CharField(max_length=200))]) |     book_with_no_author = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("title", models.CharField(max_length=200))]) | ||||||
|     book_with_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))]) |     book_with_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("author", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))]) | ||||||
|     book_with_field_and_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("writer", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))]) |     book_with_field_and_author_renamed = ModelState("otherapp", "Book", [("id", models.AutoField(primary_key=True)), ("writer", models.ForeignKey("testapp.Writer")), ("title", models.CharField(max_length=200))]) | ||||||
| @@ -361,6 +364,29 @@ class AutodetectorTests(TestCase): | |||||||
|         self.assertEqual(migration2.dependencies, [("testapp", "auto_1")]) |         self.assertEqual(migration2.dependencies, [("testapp", "auto_1")]) | ||||||
|         self.assertEqual(migration3.dependencies, [("otherapp", "auto_1")]) |         self.assertEqual(migration3.dependencies, [("otherapp", "auto_1")]) | ||||||
|  |  | ||||||
|  |     def test_proxy_fk_dependency(self): | ||||||
|  |         "Tests that FK dependencies still work on proxy models" | ||||||
|  |         # Make state | ||||||
|  |         # Note that testapp (author) has no dependencies, | ||||||
|  |         # otherapp (book) depends on testapp (authorproxy) | ||||||
|  |         before = self.make_project_state([]) | ||||||
|  |         after = self.make_project_state([self.author_empty, self.author_proxy_third, self.book_proxy_fk]) | ||||||
|  |         autodetector = MigrationAutodetector(before, after) | ||||||
|  |         changes = autodetector._detect_changes() | ||||||
|  |         # Right number of migrations? | ||||||
|  |         self.assertNumberMigrations(changes, 'testapp', 1) | ||||||
|  |         self.assertNumberMigrations(changes, 'otherapp', 1) | ||||||
|  |         self.assertNumberMigrations(changes, 'thirdapp', 1) | ||||||
|  |         # Right number of actions? | ||||||
|  |         # Right actions? | ||||||
|  |         self.assertOperationTypes(changes, 'otherapp', 0, ["CreateModel"]) | ||||||
|  |         self.assertOperationTypes(changes, 'testapp', 0, ["CreateModel"]) | ||||||
|  |         self.assertOperationTypes(changes, 'thirdapp', 0, ["CreateModel"]) | ||||||
|  |         # Right dependencies? | ||||||
|  |         self.assertEqual(changes['testapp'][0].dependencies, []) | ||||||
|  |         self.assertEqual(changes['otherapp'][0].dependencies, [("thirdapp", "auto_1")]) | ||||||
|  |         self.assertEqual(changes['thirdapp'][0].dependencies, [("testapp", "auto_1")]) | ||||||
|  |  | ||||||
|     def test_same_app_no_fk_dependency(self): |     def test_same_app_no_fk_dependency(self): | ||||||
|         """ |         """ | ||||||
|         Tests that a migration with a FK between two models of the same app |         Tests that a migration with a FK between two models of the same app | ||||||
| @@ -535,30 +561,29 @@ class AutodetectorTests(TestCase): | |||||||
|         self.assertEqual(action2.__class__.__name__, "AlterUniqueTogether") |         self.assertEqual(action2.__class__.__name__, "AlterUniqueTogether") | ||||||
|         self.assertEqual(action2.unique_together, set([("title", "newfield")])) |         self.assertEqual(action2.unique_together, set([("title", "newfield")])) | ||||||
|  |  | ||||||
|     def test_proxy_ignorance(self): |     def test_proxy(self): | ||||||
|         "Tests that the autodetector correctly ignores proxy models" |         "Tests that the autodetector correctly deals with proxy models" | ||||||
|         # First, we test adding a proxy model |         # First, we test adding a proxy model | ||||||
|         before = self.make_project_state([self.author_empty]) |         before = self.make_project_state([self.author_empty]) | ||||||
|         after = self.make_project_state([self.author_empty, self.author_proxy]) |         after = self.make_project_state([self.author_empty, self.author_proxy]) | ||||||
|         autodetector = MigrationAutodetector(before, after) |         autodetector = MigrationAutodetector(before, after) | ||||||
|         changes = autodetector._detect_changes() |         changes = autodetector._detect_changes() | ||||||
|         # Right number of migrations? |         # Right number of migrations? | ||||||
|         self.assertEqual(len(changes), 0) |         self.assertNumberMigrations(changes, "testapp", 1) | ||||||
|  |         self.assertOperationTypes(changes, "testapp", 0, ["CreateModel"]) | ||||||
|  |         self.assertOperationAttributes(changes, "testapp", 0, 0, name="AuthorProxy", options={"proxy": True}) | ||||||
|  |  | ||||||
|         # Now, we test turning a proxy model into a non-proxy model |         # Now, we test turning a proxy model into a non-proxy model | ||||||
|  |         # It should delete the proxy then make the real one | ||||||
|         before = self.make_project_state([self.author_empty, self.author_proxy]) |         before = self.make_project_state([self.author_empty, self.author_proxy]) | ||||||
|         after = self.make_project_state([self.author_empty, self.author_proxy_notproxy]) |         after = self.make_project_state([self.author_empty, self.author_proxy_notproxy]) | ||||||
|         autodetector = MigrationAutodetector(before, after) |         autodetector = MigrationAutodetector(before, after) | ||||||
|         changes = autodetector._detect_changes() |         changes = autodetector._detect_changes() | ||||||
|         # Right number of migrations? |         # Right number of migrations? | ||||||
|         self.assertEqual(len(changes['testapp']), 1) |         self.assertNumberMigrations(changes, "testapp", 1) | ||||||
|         # Right number of actions? |         self.assertOperationTypes(changes, "testapp", 0, ["DeleteModel", "CreateModel"]) | ||||||
|         migration = changes['testapp'][0] |         self.assertOperationAttributes(changes, "testapp", 0, 0, name="AuthorProxy") | ||||||
|         self.assertEqual(len(migration.operations), 1) |         self.assertOperationAttributes(changes, "testapp", 0, 1, name="AuthorProxy", options={}) | ||||||
|         # Right action? |  | ||||||
|         action = migration.operations[0] |  | ||||||
|         self.assertEqual(action.__class__.__name__, "CreateModel") |  | ||||||
|         self.assertEqual(action.name, "AuthorProxy") |  | ||||||
|  |  | ||||||
|     def test_unmanaged_ignorance(self): |     def test_unmanaged_ignorance(self): | ||||||
|         "Tests that the autodetector correctly ignores managed models" |         "Tests that the autodetector correctly ignores managed models" | ||||||
| @@ -792,8 +817,7 @@ class AutodetectorTests(TestCase): | |||||||
|  |  | ||||||
|     def test_alter_model_options(self): |     def test_alter_model_options(self): | ||||||
|         """ |         """ | ||||||
|         If two models with a ForeignKey from one to the other are removed at the same time, |         Changing a model's options should make a change | ||||||
|         the autodetector should remove them in the correct order. |  | ||||||
|         """ |         """ | ||||||
|         before = self.make_project_state([self.author_empty]) |         before = self.make_project_state([self.author_empty]) | ||||||
|         after = self.make_project_state([self.author_with_options]) |         after = self.make_project_state([self.author_with_options]) | ||||||
| @@ -804,6 +828,19 @@ class AutodetectorTests(TestCase): | |||||||
|         # Right actions in right order? |         # Right actions in right order? | ||||||
|         self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"]) |         self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"]) | ||||||
|  |  | ||||||
|  |     def test_alter_model_options_proxy(self): | ||||||
|  |         """ | ||||||
|  |         Changing a proxy model's options should also make a change | ||||||
|  |         """ | ||||||
|  |         before = self.make_project_state([self.author_proxy, self.author_empty]) | ||||||
|  |         after = self.make_project_state([self.author_proxy_options, self.author_empty]) | ||||||
|  |         autodetector = MigrationAutodetector(before, after) | ||||||
|  |         changes = autodetector._detect_changes() | ||||||
|  |         # Right number of migrations? | ||||||
|  |         self.assertNumberMigrations(changes, "testapp", 1) | ||||||
|  |         # Right actions in right order? | ||||||
|  |         self.assertOperationTypes(changes, "testapp", 0, ["AlterModelOptions"]) | ||||||
|  |  | ||||||
|     def test_set_alter_order_with_respect_to(self): |     def test_set_alter_order_with_respect_to(self): | ||||||
|         "Tests that setting order_with_respect_to adds a field" |         "Tests that setting order_with_respect_to adds a field" | ||||||
|         # Make state |         # Make state | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ class OperationTests(MigrationTestBase): | |||||||
|         with connection.schema_editor() as editor: |         with connection.schema_editor() as editor: | ||||||
|             return migration.unapply(project_state, editor) |             return migration.unapply(project_state, editor) | ||||||
|  |  | ||||||
|     def set_up_test_model(self, app_label, second_model=False, third_model=False, related_model=False, mti_model=False): |     def set_up_test_model(self, app_label, second_model=False, third_model=False, related_model=False, mti_model=False, proxy_model=False): | ||||||
|         """ |         """ | ||||||
|         Creates a test model state and database table. |         Creates a test model state and database table. | ||||||
|         """ |         """ | ||||||
| @@ -112,6 +112,13 @@ class OperationTests(MigrationTestBase): | |||||||
|                 ], |                 ], | ||||||
|                 bases=['%s.Pony' % app_label], |                 bases=['%s.Pony' % app_label], | ||||||
|             )) |             )) | ||||||
|  |         if proxy_model: | ||||||
|  |             operations.append(migrations.CreateModel( | ||||||
|  |                 "ProxyPony", | ||||||
|  |                 fields=[], | ||||||
|  |                 options={"proxy": True}, | ||||||
|  |                 bases=['%s.Pony' % app_label], | ||||||
|  |             )) | ||||||
|  |  | ||||||
|         return self.apply_operations(app_label, ProjectState(), operations) |         return self.apply_operations(app_label, ProjectState(), operations) | ||||||
|  |  | ||||||
| @@ -221,6 +228,34 @@ class OperationTests(MigrationTestBase): | |||||||
|             operation.database_backwards("test_crmoih", editor, new_state, project_state) |             operation.database_backwards("test_crmoih", editor, new_state, project_state) | ||||||
|         self.assertTableNotExists("test_crmoih_shetlandpony") |         self.assertTableNotExists("test_crmoih_shetlandpony") | ||||||
|  |  | ||||||
|  |     def test_create_proxy_model(self): | ||||||
|  |         """ | ||||||
|  |         Tests that CreateModel ignores proxy models. | ||||||
|  |         """ | ||||||
|  |         project_state = self.set_up_test_model("test_crprmo") | ||||||
|  |         # Test the state alteration | ||||||
|  |         operation = migrations.CreateModel( | ||||||
|  |             "ProxyPony", | ||||||
|  |             [], | ||||||
|  |             options = {"proxy": True}, | ||||||
|  |             bases = ("test_crprmo.Pony", ), | ||||||
|  |         ) | ||||||
|  |         new_state = project_state.clone() | ||||||
|  |         operation.state_forwards("test_crprmo", new_state) | ||||||
|  |         self.assertIn(("test_crprmo", "proxypony"), new_state.models) | ||||||
|  |         # Test the database alteration | ||||||
|  |         self.assertTableNotExists("test_crprmo_proxypony") | ||||||
|  |         self.assertTableExists("test_crprmo_pony") | ||||||
|  |         with connection.schema_editor() as editor: | ||||||
|  |             operation.database_forwards("test_crprmo", editor, project_state, new_state) | ||||||
|  |         self.assertTableNotExists("test_crprmo_proxypony") | ||||||
|  |         self.assertTableExists("test_crprmo_pony") | ||||||
|  |         # And test reversal | ||||||
|  |         with connection.schema_editor() as editor: | ||||||
|  |             operation.database_backwards("test_crprmo", editor, new_state, project_state) | ||||||
|  |         self.assertTableNotExists("test_crprmo_proxypony") | ||||||
|  |         self.assertTableExists("test_crprmo_pony") | ||||||
|  |  | ||||||
|     def test_delete_model(self): |     def test_delete_model(self): | ||||||
|         """ |         """ | ||||||
|         Tests the DeleteModel operation. |         Tests the DeleteModel operation. | ||||||
| @@ -241,6 +276,30 @@ class OperationTests(MigrationTestBase): | |||||||
|             operation.database_backwards("test_dlmo", editor, new_state, project_state) |             operation.database_backwards("test_dlmo", editor, new_state, project_state) | ||||||
|         self.assertTableExists("test_dlmo_pony") |         self.assertTableExists("test_dlmo_pony") | ||||||
|  |  | ||||||
|  |     def test_delete_proxy_model(self): | ||||||
|  |         """ | ||||||
|  |         Tests the DeleteModel operation ignores proxy models. | ||||||
|  |         """ | ||||||
|  |         project_state = self.set_up_test_model("test_dlprmo", proxy_model=True) | ||||||
|  |         # Test the state alteration | ||||||
|  |         operation = migrations.DeleteModel("ProxyPony") | ||||||
|  |         new_state = project_state.clone() | ||||||
|  |         operation.state_forwards("test_dlprmo", new_state) | ||||||
|  |         self.assertIn(("test_dlprmo", "proxypony"), project_state.models) | ||||||
|  |         self.assertNotIn(("test_dlprmo", "proxypony"), new_state.models) | ||||||
|  |         # Test the database alteration | ||||||
|  |         self.assertTableExists("test_dlprmo_pony") | ||||||
|  |         self.assertTableNotExists("test_dlprmo_proxypony") | ||||||
|  |         with connection.schema_editor() as editor: | ||||||
|  |             operation.database_forwards("test_dlprmo", editor, project_state, new_state) | ||||||
|  |         self.assertTableExists("test_dlprmo_pony") | ||||||
|  |         self.assertTableNotExists("test_dlprmo_proxypony") | ||||||
|  |         # And test reversal | ||||||
|  |         with connection.schema_editor() as editor: | ||||||
|  |             operation.database_backwards("test_dlprmo", editor, new_state, project_state) | ||||||
|  |         self.assertTableExists("test_dlprmo_pony") | ||||||
|  |         self.assertTableNotExists("test_dlprmo_proxypony") | ||||||
|  |  | ||||||
|     def test_rename_model(self): |     def test_rename_model(self): | ||||||
|         """ |         """ | ||||||
|         Tests the RenameModel operation. |         Tests the RenameModel operation. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user