[2.2.x] Fixed #30351 -- Handled pre-existing permissions in proxy model permissions data migration.

Regression in 181fb60159.

Backport of 98296f86b3 from master
This commit is contained in:
Carlton Gibson 2019-04-26 08:51:58 +02:00 committed by Mariusz Felisiak
parent e45763193f
commit 4f8ebdd098
3 changed files with 55 additions and 5 deletions

View File

@ -1,5 +1,18 @@
from django.db import migrations import sys
from django.core.management.color import color_style
from django.db import migrations, transaction
from django.db.models import Q from django.db.models import Q
from django.db.utils import IntegrityError
WARNING = """
A problem arose migrating proxy model permissions for {old} to {new}.
Permission(s) for {new} already existed.
Codenames Q: {query}
Ensure to audit ALL permissions for {old} and {new}.
"""
def update_proxy_model_permissions(apps, schema_editor, reverse=False): def update_proxy_model_permissions(apps, schema_editor, reverse=False):
@ -7,6 +20,7 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False):
Update the content_type of proxy model permissions to use the ContentType Update the content_type of proxy model permissions to use the ContentType
of the proxy model. of the proxy model.
""" """
style = color_style()
Permission = apps.get_model('auth', 'Permission') Permission = apps.get_model('auth', 'Permission')
ContentType = apps.get_model('contenttypes', 'ContentType') ContentType = apps.get_model('contenttypes', 'ContentType')
for Model in apps.get_models(): for Model in apps.get_models():
@ -24,10 +38,16 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False):
proxy_content_type = ContentType.objects.get_for_model(Model, for_concrete_model=False) proxy_content_type = ContentType.objects.get_for_model(Model, for_concrete_model=False)
old_content_type = proxy_content_type if reverse else concrete_content_type old_content_type = proxy_content_type if reverse else concrete_content_type
new_content_type = concrete_content_type if reverse else proxy_content_type new_content_type = concrete_content_type if reverse else proxy_content_type
Permission.objects.filter( try:
permissions_query, with transaction.atomic():
content_type=old_content_type, Permission.objects.filter(
).update(content_type=new_content_type) permissions_query,
content_type=old_content_type,
).update(content_type=new_content_type)
except IntegrityError:
old = '{}_{}'.format(old_content_type.app_label, old_content_type.model)
new = '{}_{}'.format(new_content_type.app_label, new_content_type.model)
sys.stdout.write(style.WARNING(WARNING.format(old=old, new=new, query=permissions_query)))
def revert_proxy_model_permissions(apps, schema_editor): def revert_proxy_model_permissions(apps, schema_editor):

View File

@ -59,3 +59,8 @@ Bugfixes
* Increased the default timeout when using ``Watchman`` to 5 seconds to prevent * Increased the default timeout when using ``Watchman`` to 5 seconds to prevent
falling back to ``StatReloader`` on larger projects and made it customizable falling back to ``StatReloader`` on larger projects and made it customizable
via the ``DJANGO_WATCHMAN_TIMEOUT`` environment variable (:ticket:`30361`). via the ``DJANGO_WATCHMAN_TIMEOUT`` environment variable (:ticket:`30361`).
* Fixed a regression in Django 2.2 that caused a crash when migrating
permissions for proxy models if the target permissions already existed. For
example, when a permission had been created manually or a model had been
migrated from concrete to proxy (:ticket:`30351`).

View File

@ -4,6 +4,7 @@ from django.apps import apps
from django.contrib.auth.models import Permission, User from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.test import TestCase from django.test import TestCase
from django.test.utils import captured_stdout
from .models import Proxy, UserProxy from .models import Proxy, UserProxy
@ -152,3 +153,27 @@ class ProxyModelWithSameAppLabelTests(TestCase):
user = User._default_manager.get(pk=user.pk) user = User._default_manager.get(pk=user.pk)
for permission in [self.default_permission, self.custom_permission]: for permission in [self.default_permission, self.custom_permission]:
self.assertTrue(user.has_perm('auth_tests.' + permission.codename)) self.assertTrue(user.has_perm('auth_tests.' + permission.codename))
def test_migrate_with_existing_target_permission(self):
"""
Permissions may already exist:
- Old workaround was to manually create permissions for proxy models.
- Model may have been concrete and then converted to proxy.
Output a reminder to audit relevant permissions.
"""
proxy_model_content_type = ContentType.objects.get_for_model(Proxy, for_concrete_model=False)
Permission.objects.create(
content_type=proxy_model_content_type,
codename='add_proxy',
name='Can add proxy',
)
Permission.objects.create(
content_type=proxy_model_content_type,
codename='display_proxys',
name='May display proxys information',
)
with captured_stdout() as stdout:
update_proxy_permissions.update_proxy_model_permissions(apps, None)
self.assertIn('A problem arose migrating proxy model permissions', stdout.getvalue())