diff options
| author | Simon Charette <charette.s@gmail.com> | 2015-12-17 20:12:05 -0500 |
|---|---|---|
| committer | Simon Charette <charette.s@gmail.com> | 2016-05-17 12:14:58 -0400 |
| commit | f179113e6cbc8ba0a8d4e87e1d4410fb61d63e75 (patch) | |
| tree | 3d37340aee66ff6b3f492fb81efdb547ab24061f /django | |
| parent | 354acd04af524ad82002b903df1189581c51cabe (diff) | |
Fixed #24067 -- Renamed content types upon model renaming.
Thanks to Tim for the extensive review.
Diffstat (limited to 'django')
| -rw-r--r-- | django/contrib/contenttypes/apps.py | 7 | ||||
| -rw-r--r-- | django/contrib/contenttypes/management.py | 84 |
2 files changed, 88 insertions, 3 deletions
diff --git a/django/contrib/contenttypes/apps.py b/django/contrib/contenttypes/apps.py index f6c3d430fd..e5708adc99 100644 --- a/django/contrib/contenttypes/apps.py +++ b/django/contrib/contenttypes/apps.py @@ -1,10 +1,12 @@ from django.apps import AppConfig from django.contrib.contenttypes.checks import check_generic_foreign_keys from django.core import checks -from django.db.models.signals import post_migrate +from django.db.models.signals import post_migrate, pre_migrate from django.utils.translation import ugettext_lazy as _ -from .management import update_contenttypes +from .management import ( + inject_rename_contenttypes_operations, update_contenttypes, +) class ContentTypesConfig(AppConfig): @@ -12,5 +14,6 @@ class ContentTypesConfig(AppConfig): verbose_name = _("Content Types") def ready(self): + pre_migrate.connect(inject_rename_contenttypes_operations, sender=self) post_migrate.connect(update_contenttypes) checks.register(check_generic_foreign_keys, checks.Tags.models) diff --git a/django/contrib/contenttypes/management.py b/django/contrib/contenttypes/management.py index 7af1a78e36..0bdf8ed9fa 100644 --- a/django/contrib/contenttypes/management.py +++ b/django/contrib/contenttypes/management.py @@ -1,9 +1,91 @@ from django.apps import apps as global_apps -from django.db import DEFAULT_DB_ALIAS, router +from django.db import DEFAULT_DB_ALIAS, migrations, router, transaction +from django.db.utils import IntegrityError from django.utils import six from django.utils.six.moves import input +class RenameContentType(migrations.RunPython): + def __init__(self, app_label, old_model, new_model): + self.app_label = app_label + self.old_model = old_model + self.new_model = new_model + super(RenameContentType, self).__init__(self.rename_forward, self.rename_backward) + + def _rename(self, apps, schema_editor, old_model, new_model): + ContentType = apps.get_model('contenttypes', 'ContentType') + db = schema_editor.connection.alias + if not router.allow_migrate_model(db, ContentType): + return + + try: + content_type = ContentType.objects.db_manager(db).get_by_natural_key(self.app_label, old_model) + except ContentType.DoesNotExist: + pass + else: + content_type.model = new_model + try: + with transaction.atomic(using=db): + content_type.save(update_fields={'model'}) + except IntegrityError: + # Gracefully fallback if a stale content type causes a + # conflict as update_contenttypes will take care of asking the + # user what should be done next. + content_type.model = old_model + else: + # Clear the cache as the `get_by_natual_key()` call will cache + # the renamed ContentType instance by its old model name. + ContentType.objects.clear_cache() + + def rename_forward(self, apps, schema_editor): + self._rename(apps, schema_editor, self.old_model, self.new_model) + + def rename_backward(self, apps, schema_editor): + self._rename(apps, schema_editor, self.new_model, self.old_model) + + +def inject_rename_contenttypes_operations(plan=None, apps=global_apps, using=DEFAULT_DB_ALIAS, **kwargs): + """ + Insert a `RenameContentType` operation after every planned `RenameModel` + operation. + """ + if plan is None: + return + + # Determine whether or not the ContentType model is available. + try: + ContentType = apps.get_model('contenttypes', 'ContentType') + except LookupError: + available = False + else: + if not router.allow_migrate_model(using, ContentType): + return + available = True + + for migration, backward in plan: + if ((migration.app_label, migration.name) == ('contenttypes', '0001_initial')): + # There's no point in going forward if the initial contenttypes + # migration is unapplied as the ContentType model will be + # unavailable from this point. + if backward: + break + else: + available = True + continue + # The ContentType model is not available yet. + if not available: + continue + inserts = [] + for index, operation in enumerate(migration.operations): + if isinstance(operation, migrations.RenameModel): + operation = RenameContentType( + migration.app_label, operation.old_name_lower, operation.new_name_lower + ) + inserts.append((index + 1, operation)) + for inserted, (index, operation) in enumerate(inserts): + migration.operations.insert(inserted + index, operation) + + def update_contenttypes(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, apps=global_apps, **kwargs): """ Creates content types for models in the given app, removing any model |
