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 /tests/auth_tests | |
| parent | 4187da258fe212d494cb578a0bc2b52c4979ab95 (diff) | |
Fixed #27489 -- Renamed permissions upon model renaming in migrations.
Diffstat (limited to 'tests/auth_tests')
4 files changed, 204 insertions, 2 deletions
diff --git a/tests/auth_tests/operations_migrations/0001_initial.py b/tests/auth_tests/operations_migrations/0001_initial.py new file mode 100644 index 0000000000..49a475653c --- /dev/null +++ b/tests/auth_tests/operations_migrations/0001_initial.py @@ -0,0 +1,14 @@ +from django.db import migrations, models + + +class Migration(migrations.Migration): + initial = True + + operations = [ + migrations.CreateModel( + name="OldModel", + fields=[ + ("id", models.AutoField(primary_key=True)), + ], + ), + ] diff --git a/tests/auth_tests/operations_migrations/0002_rename_oldmodel_to_newmodel.py b/tests/auth_tests/operations_migrations/0002_rename_oldmodel_to_newmodel.py new file mode 100644 index 0000000000..a23761e8fd --- /dev/null +++ b/tests/auth_tests/operations_migrations/0002_rename_oldmodel_to_newmodel.py @@ -0,0 +1,14 @@ +from django.db import migrations + + +class Migration(migrations.Migration): + dependencies = [ + ("auth_tests", "0001_initial"), + ] + + operations = [ + migrations.RenameModel( + old_name="OldModel", + new_name="NewModel", + ), + ] diff --git a/tests/auth_tests/operations_migrations/__init__.py b/tests/auth_tests/operations_migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/auth_tests/operations_migrations/__init__.py diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py index 0701ac2d68..7e8d01cbce 100644 --- a/tests/auth_tests/test_management.py +++ b/tests/auth_tests/test_management.py @@ -7,15 +7,21 @@ from io import StringIO from unittest import mock from django.apps import apps +from django.conf import settings from django.contrib.auth import get_permission_codename, management -from django.contrib.auth.management import create_permissions, get_default_username +from django.contrib.auth.management import ( + RenamePermission, + create_permissions, + get_default_username, +) from django.contrib.auth.management.commands import changepassword, createsuperuser from django.contrib.auth.models import Group, Permission, User from django.contrib.contenttypes.models import ContentType from django.core.management import call_command from django.core.management.base import CommandError -from django.db import migrations +from django.db import migrations, models from django.test import TestCase, override_settings +from django.test.testcases import TransactionTestCase from django.utils.translation import gettext_lazy as _ from .models import ( @@ -1528,6 +1534,174 @@ class CreatePermissionsTests(TestCase): ) +@override_settings( + MIGRATION_MODULES=dict( + settings.MIGRATION_MODULES, + auth_tests="auth_tests.operations_migrations", + ), +) +class PermissionRenameOperationsTests(TransactionTestCase): + available_apps = [ + "django.contrib.contenttypes", + "django.contrib.auth", + "auth_tests", + ] + + def setUp(self): + app_config = apps.get_app_config("auth_tests") + models.signals.post_migrate.connect( + self.assertOperationsInjected, sender=app_config + ) + self.addCleanup( + models.signals.post_migrate.disconnect, + self.assertOperationsInjected, + sender=app_config, + ) + + def assertOperationsInjected(self, plan, **kwargs): + for migration, _backward in plan: + operations = iter(migration.operations) + for operation in operations: + if isinstance(operation, migrations.RenameModel): + next_operation = next(operations) + self.assertIsInstance(next_operation, RenamePermission) + self.assertEqual(next_operation.app_label, migration.app_label) + self.assertEqual(next_operation.old_model, operation.old_name) + self.assertEqual(next_operation.new_model, operation.new_name) + + def test_permission_rename(self): + ct = ContentType.objects.create(app_label="auth_tests", model="oldmodel") + actions = ["add", "change", "delete", "view"] + for action in actions: + Permission.objects.create( + codename=f"{action}_oldmodel", + name=f"Can {action} old model", + content_type=ct, + ) + + call_command("migrate", "auth_tests", verbosity=0) + for action in actions: + self.assertFalse( + Permission.objects.filter(codename=f"{action}_oldmodel").exists() + ) + self.assertTrue( + Permission.objects.filter(codename=f"{action}_newmodel").exists() + ) + + call_command( + "migrate", + "auth_tests", + "zero", + database="default", + interactive=False, + verbosity=0, + ) + + for action in actions: + self.assertTrue( + Permission.objects.filter(codename=f"{action}_oldmodel").exists() + ) + self.assertFalse( + Permission.objects.filter(codename=f"{action}_newmodel").exists() + ) + + @mock.patch( + "django.db.router.allow_migrate_model", + return_value=False, + ) + def test_rename_skipped_if_router_disallows(self, _): + ct = ContentType.objects.create(app_label="auth_tests", model="oldmodel") + Permission.objects.create( + codename="change_oldmodel", + name="Can change old model", + content_type=ct, + ) + # The rename operation should not be there when disallowed by router. + app_config = apps.get_app_config("auth_tests") + models.signals.post_migrate.disconnect( + self.assertOperationsInjected, sender=app_config + ) + + call_command( + "migrate", + "auth_tests", + database="default", + interactive=False, + verbosity=0, + ) + self.assertTrue(Permission.objects.filter(codename="change_oldmodel").exists()) + self.assertFalse(Permission.objects.filter(codename="change_newmodel").exists()) + + call_command( + "migrate", + "auth_tests", + "zero", + database="default", + interactive=False, + verbosity=0, + ) + + def test_rename_backward_does_nothing_if_no_permissions(self): + Permission.objects.filter(content_type__app_label="auth_tests").delete() + + call_command( + "migrate", + "auth_tests", + "zero", + database="default", + interactive=False, + verbosity=0, + ) + self.assertFalse( + Permission.objects.filter( + codename__in=["change_oldmodel", "change_newmodel"] + ).exists() + ) + + def test_rename_permission_conflict(self): + ct = ContentType.objects.create(app_label="auth_tests", model="oldmodel") + Permission.objects.create( + codename="change_newmodel", + name="Can change new model", + content_type=ct, + ) + Permission.objects.create( + codename="change_oldmodel", + name="Can change old model", + content_type=ct, + ) + + call_command( + "migrate", + "auth_tests", + database="default", + interactive=False, + verbosity=0, + ) + self.assertTrue( + Permission.objects.filter( + codename="change_oldmodel", + name="Can change old model", + ).exists() + ) + self.assertEqual( + Permission.objects.filter( + codename="change_newmodel", + name="Can change new model", + ).count(), + 1, + ) + + call_command( + "migrate", + "auth_tests", + "zero", + database="default", + interactive=False, + verbosity=0, + ) + + class DefaultDBRouter: """Route all writes to default.""" |
