|
9 | 9 | from django.contrib.auth import get_permission_codename
|
10 | 10 | from django.contrib.contenttypes.management import create_contenttypes
|
11 | 11 | from django.core import exceptions
|
12 |
| -from django.db import DEFAULT_DB_ALIAS, router |
| 12 | +from django.db import DEFAULT_DB_ALIAS, migrations, router, transaction |
| 13 | +from django.db.utils import IntegrityError |
| 14 | +from django.utils.text import camel_case_to_spaces |
13 | 15 |
|
14 | 16 |
|
15 | 17 | def _get_all_permissions(opts):
|
@@ -108,6 +110,98 @@ def create_permissions(
|
108 | 110 | print("Adding permission '%s'" % perm)
|
109 | 111 |
|
110 | 112 |
|
| 113 | +class RenamePermission(migrations.RunPython): |
| 114 | + def __init__(self, app_label, old_model, new_model): |
| 115 | + self.app_label = app_label |
| 116 | + self.old_model = old_model |
| 117 | + self.new_model = new_model |
| 118 | + super(RenamePermission, self).__init__( |
| 119 | + self.rename_forward, self.rename_backward |
| 120 | + ) |
| 121 | + |
| 122 | + def _rename(self, apps, schema_editor, old_model, new_model): |
| 123 | + ContentType = apps.get_model("contenttypes", "ContentType") |
| 124 | + # live model import since with frozen model we have no fk's |
| 125 | + # and permission model has default ordering based on fk's |
| 126 | + from django.contrib.auth.models import Permission |
| 127 | + |
| 128 | + db = schema_editor.connection.alias |
| 129 | + |
| 130 | + ctypes = ContentType.objects.filter( |
| 131 | + app_label=self.app_label, model__iexact=old_model.lower() |
| 132 | + ) |
| 133 | + |
| 134 | + permissions = Permission.objects.filter( |
| 135 | + content_type_id__in=(ctype.id for ctype in ctypes) |
| 136 | + ) |
| 137 | + |
| 138 | + for permission in permissions: |
| 139 | + prefix = permission.codename.split("_")[0] |
| 140 | + default_verbose_name = camel_case_to_spaces(new_model) |
| 141 | + |
| 142 | + new_codename = f"{prefix}_{new_model.lower()}" |
| 143 | + new_name = f"Can {prefix} {default_verbose_name}" |
| 144 | + |
| 145 | + # Only update if changes are needed |
| 146 | + if permission.codename != new_codename or permission.name != new_name: |
| 147 | + # Save original values in case of error |
| 148 | + original_codename = permission.codename |
| 149 | + original_name = permission.name |
| 150 | + |
| 151 | + permission.codename = new_codename |
| 152 | + permission.name = new_name |
| 153 | + |
| 154 | + try: |
| 155 | + with transaction.atomic(using=db): |
| 156 | + permission.save(update_fields={"name", "codename"}) |
| 157 | + except IntegrityError: |
| 158 | + # Skip conflicting permissions - leave them unchanged |
| 159 | + permission.codename = original_codename |
| 160 | + permission.name = original_name |
| 161 | + |
| 162 | + def rename_forward(self, apps, schema_editor): |
| 163 | + self._rename(apps, schema_editor, self.old_model, self.new_model) |
| 164 | + |
| 165 | + def rename_backward(self, apps, schema_editor): |
| 166 | + self._rename(apps, schema_editor, self.new_model, self.old_model) |
| 167 | + |
| 168 | + |
| 169 | +def rename_permissions( |
| 170 | + plan, |
| 171 | + verbosity=2, |
| 172 | + interactive=True, |
| 173 | + using=DEFAULT_DB_ALIAS, |
| 174 | + apps=global_apps, |
| 175 | + **kwargs, |
| 176 | +): |
| 177 | + """ |
| 178 | + Insert a `RenamePermissionType` operation after every planned `RenameModel` |
| 179 | + operation. |
| 180 | + """ |
| 181 | + try: |
| 182 | + Permission = apps.get_model("auth", "Permission") |
| 183 | + except LookupError: |
| 184 | + return |
| 185 | + else: |
| 186 | + if not router.allow_migrate_model(using, Permission): |
| 187 | + return |
| 188 | + |
| 189 | + for migration, backward in plan: |
| 190 | + |
| 191 | + inserts = [] |
| 192 | + for index, operation in enumerate(migration.operations): |
| 193 | + if isinstance(operation, migrations.RenameModel): |
| 194 | + operation = RenamePermission( |
| 195 | + migration.app_label, |
| 196 | + operation.old_name, |
| 197 | + operation.new_name, |
| 198 | + ) |
| 199 | + |
| 200 | + inserts.append((index + 1, operation)) |
| 201 | + for inserted, (index, operation) in enumerate(inserts): |
| 202 | + migration.operations.insert(inserted + index, operation) |
| 203 | + |
| 204 | + |
111 | 205 | def get_system_username():
|
112 | 206 | """
|
113 | 207 | Return the current system user's username, or an empty string if the
|
|
0 commit comments