[2.2.x] Fixed #30351 -- Handled pre-existing permissions in proxy model permissions data migration.
Regression in181fb60159
. Backport of98296f86b3
from master
This commit is contained in:
parent
e45763193f
commit
4f8ebdd098
|
@ -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):
|
||||||
|
|
|
@ -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`).
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in New Issue