""" Creates permissions for all installed apps that need permissions. """ import getpass import unicodedata from django.apps import apps as global_apps from django.contrib.auth import get_permission_codename from django.contrib.contenttypes.management import create_contenttypes from django.core import exceptions from django.db import DEFAULT_DB_ALIAS, migrations, router, transaction from django.db.utils import IntegrityError from django.utils.text import camel_case_to_spaces def _get_all_permissions(opts): """ Return (codename, name) for all permissions in the given opts. """ return [*_get_builtin_permissions(opts), *opts.permissions] def _get_builtin_permissions(opts): """ Return (codename, name) for all autogenerated permissions. By default, this is ('add', 'change', 'delete', 'view') """ perms = [] for action in opts.default_permissions: perms.append( ( get_permission_codename(action, opts), "Can %s %s" % (action, opts.verbose_name_raw), ) ) return perms def create_permissions( app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, apps=global_apps, **kwargs, ): if not app_config.models_module: return try: Permission = apps.get_model("auth", "Permission") except LookupError: return if not router.allow_migrate_model(using, Permission): return # Ensure that contenttypes are created for this app. Needed if # 'django.contrib.auth' is in INSTALLED_APPS before # 'django.contrib.contenttypes'. create_contenttypes( app_config, verbosity=verbosity, interactive=interactive, using=using, apps=apps, **kwargs, ) app_label = app_config.label try: app_config = apps.get_app_config(app_label) ContentType = apps.get_model("contenttypes", "ContentType") except LookupError: return models = list(app_config.get_models()) # Grab all the ContentTypes. ctypes = ContentType.objects.db_manager(using).get_for_models( *models, for_concrete_models=False ) # Find all the Permissions that have a content_type for a model we're # looking for. We don't need to check for codenames since we already have # a list of the ones we're going to create. all_perms = set( Permission.objects.using(using) .filter( content_type__in=set(ctypes.values()), ) .values_list("content_type", "codename") ) perms = [] for model in models: ctype = ctypes[model] for codename, name in _get_all_permissions(model._meta): if (ctype.pk, codename) not in all_perms: permission = Permission() permission._state.db = using permission.codename = codename permission.name = name permission.content_type = ctype perms.append(permission) Permission.objects.using(using).bulk_create(perms) if verbosity >= 2: for perm in perms: print("Adding permission '%s'" % perm) class RenamePermission(migrations.RunPython): def __init__(self, app_label, old_model, new_model): self.app_label = app_label self.old_model = old_model self.new_model = new_model super(RenamePermission, self).__init__( self.rename_forward, self.rename_backward ) def _rename(self, apps, schema_editor, old_model, new_model): ContentType = apps.get_model("contenttypes", "ContentType") # Use the live Permission model instead of the frozen one, since frozen # models do not retain foreign key constraints. from django.contrib.auth.models import Permission db = schema_editor.connection.alias ctypes = ContentType.objects.using(db).filter( app_label=self.app_label, model__icontains=old_model.lower() ) for permission in Permission.objects.using(db).filter( content_type_id__in=ctypes.values("id") ): prefix = permission.codename.split("_")[0] default_verbose_name = camel_case_to_spaces(new_model) new_codename = f"{prefix}_{new_model.lower()}" new_name = f"Can {prefix} {default_verbose_name}" if permission.codename != new_codename or permission.name != new_name: permission.codename = new_codename permission.name = new_name try: with transaction.atomic(using=db): permission.save(update_fields={"name", "codename"}) except IntegrityError: pass def rename_forward(self, apps, schema_editor): self._rename(apps, schema_editor, self.old_model, self.new_model) def rename_backward(self, apps, schema_editor): self._rename(apps, schema_editor, self.new_model, self.old_model) def rename_permissions( plan, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, apps=global_apps, **kwargs, ): """ Insert a `RenamePermissionType` operation after every planned `RenameModel` operation. """ try: Permission = apps.get_model("auth", "Permission") except LookupError: return else: if not router.allow_migrate_model(using, Permission): return for migration, backward in plan: inserts = [] for index, operation in enumerate(migration.operations): if isinstance(operation, migrations.RenameModel): operation = RenamePermission( migration.app_label, operation.old_name, operation.new_name, ) inserts.append((index + 1, operation)) for inserted, (index, operation) in enumerate(inserts): migration.operations.insert(inserted + index, operation) def get_system_username(): """ Return the current system user's username, or an empty string if the username could not be determined. """ try: result = getpass.getuser() except (ImportError, KeyError, OSError): # TODO: Drop ImportError and KeyError when dropping support for PY312. # KeyError (Python <3.13) or OSError (Python 3.13+) will be raised by # os.getpwuid() (called by getuser()) if there is no corresponding # entry in the /etc/passwd file (for example, in a very restricted # chroot environment). return "" return result def get_default_username(check_db=True, database=DEFAULT_DB_ALIAS): """ Try to determine the current system user's username to use as a default. :param check_db: If ``True``, requires that the username does not match an existing ``auth.User`` (otherwise returns an empty string). :param database: The database where the unique check will be performed. :returns: The username, or an empty string if no username can be determined or the suggested username is already taken. """ # This file is used in apps.py, it should not trigger models import. from django.contrib.auth import models as auth_app # If the User model has been swapped out, we can't make any assumptions # about the default user name. if auth_app.User._meta.swapped: return "" default_username = get_system_username() try: default_username = ( unicodedata.normalize("NFKD", default_username) .encode("ascii", "ignore") .decode("ascii") .replace(" ", "") .lower() ) except UnicodeDecodeError: return "" # Run the username validator try: auth_app.User._meta.get_field("username").run_validators(default_username) except exceptions.ValidationError: return "" # Don't return the default username if it is already taken. if check_db and default_username: try: auth_app.User._default_manager.db_manager(database).get( username=default_username, ) except auth_app.User.DoesNotExist: pass else: return "" return default_username