diff options
| author | Artyom Kotovskiy <mrartem1927@gmail.com> | 2025-04-20 18:17:47 -0400 |
|---|---|---|
| committer | Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> | 2025-08-19 16:36:52 +0200 |
| commit | f02b49d2f3bf84f5225de920ca510149f1f9f1da (patch) | |
| tree | 46b6edab00945a8f93ba72d3c000b761e19023a8 /django/contrib | |
| parent | 4187da258fe212d494cb578a0bc2b52c4979ab95 (diff) | |
Fixed #27489 -- Renamed permissions upon model renaming in migrations.
Diffstat (limited to 'django/contrib')
| -rw-r--r-- | django/contrib/auth/apps.py | 9 | ||||
| -rw-r--r-- | django/contrib/auth/management/__init__.py | 82 |
2 files changed, 88 insertions, 3 deletions
diff --git a/django/contrib/auth/apps.py b/django/contrib/auth/apps.py index ad6f816809..eeba5b7e13 100644 --- a/django/contrib/auth/apps.py +++ b/django/contrib/auth/apps.py @@ -1,12 +1,12 @@ from django.apps import AppConfig from django.core import checks from django.db.models.query_utils import DeferredAttribute -from django.db.models.signals import post_migrate +from django.db.models.signals import post_migrate, pre_migrate from django.utils.translation import gettext_lazy as _ from . import get_user_model from .checks import check_middleware, check_models_permissions, check_user_model -from .management import create_permissions +from .management import create_permissions, rename_permissions from .signals import user_logged_in @@ -20,6 +20,11 @@ class AuthConfig(AppConfig): create_permissions, dispatch_uid="django.contrib.auth.management.create_permissions", ) + pre_migrate.connect( + rename_permissions, + dispatch_uid="django.contrib.auth.management.rename_permissions", + ) + last_login_field = getattr(get_user_model(), "last_login", None) # Register the handler only if UserModel.last_login is a field. if isinstance(last_login_field, DeferredAttribute): diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index e816357b2b..40ada52e29 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -9,7 +9,9 @@ from django.apps import apps as global_apps from django.contrib.auth import get_permission_codename from django.contrib.contenttypes.management import create_contenttypes from django.core import exceptions -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.text import camel_case_to_spaces def _get_all_permissions(opts): @@ -108,6 +110,84 @@ def create_permissions( print("Adding permission '%s'" % perm) +class RenamePermission(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(RenamePermission, self).__init__( + self.rename_forward, self.rename_backward + ) + + def _rename(self, apps, schema_editor, old_model, new_model): + ContentType = apps.get_model("contenttypes", "ContentType") + # Use the live Permission model instead of the frozen one, since frozen + # models do not retain foreign key constraints. + from django.contrib.auth.models import Permission + + db = schema_editor.connection.alias + ctypes = ContentType.objects.filter( + app_label=self.app_label, model__icontains=old_model.lower() + ) + for permission in Permission.objects.filter( + content_type_id__in=ctypes.values("id") + ): + prefix = permission.codename.split("_")[0] + default_verbose_name = camel_case_to_spaces(new_model) + + new_codename = f"{prefix}_{new_model.lower()}" + new_name = f"Can {prefix} {default_verbose_name}" + + if permission.codename != new_codename or permission.name != new_name: + permission.codename = new_codename + permission.name = new_name + try: + with transaction.atomic(using=db): + permission.save(update_fields={"name", "codename"}) + except IntegrityError: + pass + + 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 rename_permissions( + plan, + verbosity=2, + interactive=True, + using=DEFAULT_DB_ALIAS, + apps=global_apps, + **kwargs, +): + """ + Insert a `RenamePermissionType` operation after every planned `RenameModel` + operation. + """ + try: + Permission = apps.get_model("auth", "Permission") + except LookupError: + return + else: + if not router.allow_migrate_model(using, Permission): + return + + for migration, backward in plan: + inserts = [] + for index, operation in enumerate(migration.operations): + if isinstance(operation, migrations.RenameModel): + operation = RenamePermission( + migration.app_label, + operation.old_name, + operation.new_name, + ) + inserts.append((index + 1, operation)) + for inserted, (index, operation) in enumerate(inserts): + migration.operations.insert(inserted + index, operation) + + def get_system_username(): """ Return the current system user's username, or an empty string if the |
