diff --git a/django/contrib/auth/management/__init__.py b/django/contrib/auth/management/__init__.py index 2ada789caed..b5fd29a1c21 100644 --- a/django/contrib/auth/management/__init__.py +++ b/django/contrib/auth/management/__init__.py @@ -9,6 +9,7 @@ import unicodedata from django.contrib.auth import models as auth_app, get_user_model from django.core import exceptions +from django.core.management.base import CommandError from django.db.models import get_models, signals from django.utils import six from django.utils.six.moves import input @@ -18,13 +19,43 @@ def _get_permission_codename(action, opts): return '%s_%s' % (action, opts.object_name.lower()) -def _get_all_permissions(opts): - "Returns (codename, name) for all permissions in the given opts." +def _get_all_permissions(opts, ctype): + """ + Returns (codename, name) for all permissions in the given opts. + """ + builtin = _get_builtin_permissions(opts) + custom = list(opts.permissions) + _check_permission_clashing(custom, builtin, ctype) + return builtin + custom + +def _get_builtin_permissions(opts): + """ + Returns (codename, name) for all autogenerated permissions. + """ perms = [] for action in ('add', 'change', 'delete'): - perms.append((_get_permission_codename(action, opts), 'Can %s %s' % (action, opts.verbose_name_raw))) - return perms + list(opts.permissions) + perms.append((_get_permission_codename(action, opts), + 'Can %s %s' % (action, opts.verbose_name_raw))) + return perms +def _check_permission_clashing(custom, builtin, ctype): + """ + Check that permissions for a model do not clash. Raises CommandError if + there are duplicate permissions. + """ + pool = set() + builtin_codenames = set(p[0] for p in builtin) + for codename, _name in custom: + if codename in pool: + raise CommandError( + "The permission codename '%s' is duplicated for model '%s.%s'." % + (codename, ctype.app_label, ctype.model_class().__name__)) + elif codename in builtin_codenames: + raise CommandError( + "The permission codename '%s' clashes with a builtin permission " + "for model '%s.%s'." % + (codename, ctype.app_label, ctype.model_class().__name__)) + pool.add(codename) def create_permissions(app, created_models, verbosity, **kwargs): from django.contrib.contenttypes.models import ContentType @@ -39,7 +70,7 @@ def create_permissions(app, created_models, verbosity, **kwargs): for klass in app_models: ctype = ContentType.objects.get_for_model(klass) ctypes.add(ctype) - for perm in _get_all_permissions(klass._meta): + for perm in _get_all_permissions(klass._meta, ctype): searched_perms.append((ctype, perm)) # Find all the Permissions that have a context_type for a model we're diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py index 18b499ef00d..cab7b20f20f 100644 --- a/django/contrib/auth/tests/management.py +++ b/django/contrib/auth/tests/management.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from datetime import date from django.contrib.auth import models, management +from django.contrib.auth.management import create_permissions from django.contrib.auth.management.commands import changepassword from django.contrib.auth.models import User from django.contrib.auth.tests import CustomUser @@ -167,3 +168,43 @@ class CreatesuperuserManagementCommandTestCase(TestCase): ) self.assertEqual(CustomUser.objects.count(), 0) + + +class PermissionDuplicationTestCase(TestCase): + + def setUp(self): + self._original_user_permission = models.User._meta.permissions + + def tearUp(self): + models.User._meta.permissions = self._original_user_permissions + + def test_duplicated_permissions(self): + """ + Test that we show proper error message if we are trying to create + duplicate permissions. + """ + # check duplicated default permission + models.Permission._meta.permissions = [ + ('change_permission', 'Can edit permission (duplicate)')] + self.assertRaisesRegexp(CommandError, + "The permission codename 'change_permission' clashes with a " + "builtin permission for model 'auth.Permission'.", + create_permissions, models, [], verbosity=0) + + # check duplicated custom permissions + models.Permission._meta.permissions = [ + ('my_custom_permission', 'Some permission'), + ('other_one', 'Some other permission'), + ('my_custom_permission', 'Some permission with duplicate permission code'), + ] + self.assertRaisesRegexp(CommandError, + "The permission codename 'my_custom_permission' is duplicated for model " + "'auth.Permission'.", + create_permissions, models, [], verbosity=0) + + # should not raise anything + models.Permission._meta.permissions = [ + ('my_custom_permission', 'Some permission'), + ('other_one', 'Some other permission'), + ] + create_permissions(models, [], verbosity=0)