[3.0.x] Fixed #31021 -- Fixed proxy model permissions data migration crash with a multiple databases setup.

Regression in 98296f86b3.

Backport of e8fcdaad5c from master
This commit is contained in:
Mariusz Felisiak 2019-11-28 11:46:08 +01:00
parent 81ddf4b164
commit ca9144a4a8
3 changed files with 66 additions and 20 deletions

View File

@ -23,6 +23,7 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False):
style = color_style()
Permission = apps.get_model('auth', 'Permission')
ContentType = apps.get_model('contenttypes', 'ContentType')
alias = schema_editor.connection.alias
for Model in apps.get_models():
opts = Model._meta
if not opts.proxy:
@ -34,13 +35,14 @@ def update_proxy_model_permissions(apps, schema_editor, reverse=False):
permissions_query = Q(codename__in=proxy_default_permissions_codenames)
for codename, name in opts.permissions:
permissions_query = permissions_query | Q(codename=codename, name=name)
concrete_content_type = ContentType.objects.get_for_model(Model, for_concrete_model=True)
proxy_content_type = ContentType.objects.get_for_model(Model, for_concrete_model=False)
content_type_manager = ContentType.objects.db_manager(alias)
concrete_content_type = content_type_manager.get_for_model(Model, for_concrete_model=True)
proxy_content_type = content_type_manager.get_for_model(Model, for_concrete_model=False)
old_content_type = proxy_content_type if reverse else concrete_content_type
new_content_type = concrete_content_type if reverse else proxy_content_type
try:
with transaction.atomic():
Permission.objects.filter(
with transaction.atomic(using=alias):
Permission.objects.using(alias).filter(
permissions_query,
content_type=old_content_type,
).update(content_type=new_content_type)

View File

@ -13,3 +13,7 @@ Bugfixes
* Fixed a data loss possibility in the admin changelist view when a custom
:ref:`formset's prefix <formset-prefix>` contains regular expression special
characters, e.g. `'$'` (:ticket:`31031`).
* Fixed a regression in Django 2.2.1 that caused a crash when migrating
permissions for proxy models with a multiple database setup if the
``default`` entry was empty (:ticket:`31021`).

View File

@ -3,7 +3,8 @@ from importlib import import_module
from django.apps import apps
from django.contrib.auth.models import Permission, User
from django.contrib.contenttypes.models import ContentType
from django.test import TestCase
from django.db import connection, connections
from django.test import TransactionTestCase
from django.test.utils import captured_stdout
from .models import Proxy, UserProxy
@ -11,7 +12,7 @@ from .models import Proxy, UserProxy
update_proxy_permissions = import_module('django.contrib.auth.migrations.0011_update_proxy_permissions')
class ProxyModelWithDifferentAppLabelTests(TestCase):
class ProxyModelWithDifferentAppLabelTests(TransactionTestCase):
available_apps = [
'auth_tests',
'django.contrib.auth',
@ -41,7 +42,8 @@ class ProxyModelWithDifferentAppLabelTests(TestCase):
proxy_model_content_type = ContentType.objects.get_for_model(UserProxy, for_concrete_model=False)
self.assertEqual(self.default_permission.content_type, self.concrete_content_type)
self.assertEqual(self.custom_permission.content_type, self.concrete_content_type)
update_proxy_permissions.update_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
self.default_permission.refresh_from_db()
self.assertEqual(self.default_permission.content_type, proxy_model_content_type)
self.custom_permission.refresh_from_db()
@ -54,7 +56,8 @@ class ProxyModelWithDifferentAppLabelTests(TestCase):
for permission in [self.default_permission, self.custom_permission]:
self.assertTrue(user.has_perm('auth.' + permission.codename))
self.assertFalse(user.has_perm('auth_tests.' + permission.codename))
update_proxy_permissions.update_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
# Reload user to purge the _perm_cache.
user = User._default_manager.get(pk=user.pk)
for permission in [self.default_permission, self.custom_permission]:
@ -62,8 +65,9 @@ class ProxyModelWithDifferentAppLabelTests(TestCase):
self.assertTrue(user.has_perm('auth_tests.' + permission.codename))
def test_migrate_backwards(self):
update_proxy_permissions.update_proxy_model_permissions(apps, None)
update_proxy_permissions.revert_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
update_proxy_permissions.revert_proxy_model_permissions(apps, editor)
self.default_permission.refresh_from_db()
self.assertEqual(self.default_permission.content_type, self.concrete_content_type)
self.custom_permission.refresh_from_db()
@ -76,8 +80,9 @@ class ProxyModelWithDifferentAppLabelTests(TestCase):
for permission in [self.default_permission, self.custom_permission]:
self.assertTrue(user.has_perm('auth.' + permission.codename))
self.assertFalse(user.has_perm('auth_tests.' + permission.codename))
update_proxy_permissions.update_proxy_model_permissions(apps, None)
update_proxy_permissions.revert_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
update_proxy_permissions.revert_proxy_model_permissions(apps, editor)
# Reload user to purge the _perm_cache.
user = User._default_manager.get(pk=user.pk)
for permission in [self.default_permission, self.custom_permission]:
@ -85,7 +90,7 @@ class ProxyModelWithDifferentAppLabelTests(TestCase):
self.assertFalse(user.has_perm('auth_tests.' + permission.codename))
class ProxyModelWithSameAppLabelTests(TestCase):
class ProxyModelWithSameAppLabelTests(TransactionTestCase):
available_apps = [
'auth_tests',
'django.contrib.auth',
@ -115,7 +120,8 @@ class ProxyModelWithSameAppLabelTests(TestCase):
proxy_model_content_type = ContentType.objects.get_for_model(Proxy, for_concrete_model=False)
self.assertEqual(self.default_permission.content_type, self.concrete_content_type)
self.assertEqual(self.custom_permission.content_type, self.concrete_content_type)
update_proxy_permissions.update_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
self.default_permission.refresh_from_db()
self.custom_permission.refresh_from_db()
self.assertEqual(self.default_permission.content_type, proxy_model_content_type)
@ -127,15 +133,17 @@ class ProxyModelWithSameAppLabelTests(TestCase):
user.user_permissions.add(self.custom_permission)
for permission in [self.default_permission, self.custom_permission]:
self.assertTrue(user.has_perm('auth_tests.' + permission.codename))
update_proxy_permissions.update_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
# Reload user to purge the _perm_cache.
user = User._default_manager.get(pk=user.pk)
for permission in [self.default_permission, self.custom_permission]:
self.assertTrue(user.has_perm('auth_tests.' + permission.codename))
def test_migrate_backwards(self):
update_proxy_permissions.update_proxy_model_permissions(apps, None)
update_proxy_permissions.revert_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
update_proxy_permissions.revert_proxy_model_permissions(apps, editor)
self.default_permission.refresh_from_db()
self.assertEqual(self.default_permission.content_type, self.concrete_content_type)
self.custom_permission.refresh_from_db()
@ -147,8 +155,9 @@ class ProxyModelWithSameAppLabelTests(TestCase):
user.user_permissions.add(self.custom_permission)
for permission in [self.default_permission, self.custom_permission]:
self.assertTrue(user.has_perm('auth_tests.' + permission.codename))
update_proxy_permissions.update_proxy_model_permissions(apps, None)
update_proxy_permissions.revert_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
update_proxy_permissions.revert_proxy_model_permissions(apps, editor)
# Reload user to purge the _perm_cache.
user = User._default_manager.get(pk=user.pk)
for permission in [self.default_permission, self.custom_permission]:
@ -175,5 +184,36 @@ class ProxyModelWithSameAppLabelTests(TestCase):
name='May display proxys information',
)
with captured_stdout() as stdout:
update_proxy_permissions.update_proxy_model_permissions(apps, None)
with connection.schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
self.assertIn('A problem arose migrating proxy model permissions', stdout.getvalue())
class MultiDBProxyModelAppLabelTests(TransactionTestCase):
databases = {'default', 'other'}
available_apps = [
'auth_tests',
'django.contrib.auth',
'django.contrib.contenttypes',
]
def setUp(self):
ContentType.objects.all().delete()
Permission.objects.using('other').delete()
concrete_content_type = ContentType.objects.db_manager(
'other'
).get_for_model(Proxy)
self.permission = Permission.objects.using('other').create(
content_type=concrete_content_type,
codename='add_proxy',
name='Can add proxy',
)
def test_migrate_other_database(self):
proxy_model_content_type = ContentType.objects.db_manager(
'other'
).get_for_model(Proxy, for_concrete_model=False)
with connections['other'].schema_editor() as editor:
update_proxy_permissions.update_proxy_model_permissions(apps, editor)
self.permission.refresh_from_db()
self.assertEqual(self.permission.content_type, proxy_model_content_type)