Until recently I had never worked with Django permissions, so I was surprised to find how hard it is to create a static set of groups with permissions.

I had expected I could just use a migration to create the groups, but it turns out that permissions are created using a post_migrate signal is run after an apps migrations have completed.

There are two scenarios where this is problematic:

Migrating a clean DB

This happens during test runs and could also happen if you’re setting up a new system. In this case the groups will be created before the permissions (or at least before ALL the permissions) have been created.

Adding a new model

If you want to add a new model and assign permissions at the same time, you’ll run into the same problem since the permissions for the model won’t get created until after the apps migrations have run.

There have been a few proposals to solve this problem1, but none of them seem to have been accepted into Django core.

against a clean DB e.g. in tests, or if you want to assign permissions to a group for new model that’s being added at the same time as the permissions update

from django.db.models.signals import post_migrate

_all_apps_with_models = set()
_groups_created = []

@post_migrate.connect
def create_default_groups(sender, **kwargs):
    """
    This signal is called after each app's migrations have completed (regardless of whether there
    were migrations run for that app).

    Since permissions are also created using a `post_migrate` signal we have to wait until all the
    permissions are created before we create the groups.
    """
    if not _all_apps_with_models:
        apps = kwargs.get("apps")
        _all_apps_with_models.update({c.label for c in apps.get_app_configs() if c.models})

    try:
        _all_apps_with_models.remove(sender.label)
    except KeyError:
        pass
    
    if not _groups_created and not _all_apps_with_models:
        # all the apps with models have been migrated
        print("Creating groups")
        create_default_groups()

    _groups_created.append(1)