DjangoのマイグレーションでPermissionにアクセス

Djangoマイグレーション時にPermissionにアクセスする方法をまとめます。

実行環境

マイグレーションファイルの作成

空のマイグレーションファイルを作成します。

python manage.py makemigrations --empty yourappname
from django.db import migrations


class Migration(migrations.Migration):

    dependencies = [
        ('yourappname', '0001_initial'),
    ]

    operations = [
    ]

RunPythonでPermissionにアクセス

RunPythonの第1引数に与えた access_perm でPermissionにアクセスする状況を想定します。
ここではデフォルトで作成される auth.view_user の取得を試みます。

from django.db import migrations


def access_perm(apps, schema_editor):
    Permission = apps.get_model('auth', 'Permission')
    Permission.objects.get(
        codename='view_user',
        content_type__app_label='auth'
    )


class Migration(migrations.Migration):

    dependencies = [
        ...
    ]

    operations = [
        migrations.RunPython(access_perm),
    ]

しかし、このコードは以下のエラーを出力します。

Permission.DoesNotExist: Permission matching query does not exist.

これはmigrateの最後に送信されるpost_migrateシグナルがDefault permissionsを作成するためです。

...it will create default permissions for new models each time you run manage.py migrate (the function that creates permissions is connected to the post_migrate signal).

post_migrateシグナルの明示的な送信

ということで、django.core.management.sql.emit_post_migrate_signal(verbosity, interactive, db)post_migrate シグナルを送信することでPermissionを作成します。
第3引数のデータベース名はSchemaEditor.connection.aliasから取得してますが、1つしかデータベースがないなら 'default' 固定でも良いです。

from django.core.management.sql import emit_post_migrate_signal

def access_perm(apps, schema_editor):
    db_alias = schema_editor.connection.alias
    emit_post_migrate_signal(2, False, db_alias)

    Permission = apps.get_model('auth', 'Permission')
    Permission.objects.get(
        codename='view_user',
        content_type__app_label='auth'
    )

これでマイグレーションの途中でPermissionが作成され、参照できるようになりました。

$ ./manage.py migrate
...
Running migrations:
...
  Applying yourappname.yourmigrationfile...Running post-migrate handlers for application admin
Adding content type 'admin | logentry'
Adding permission 'admin | log entry | Can add log entry'
Adding permission 'admin | log entry | Can change log entry'
Adding permission 'admin | log entry | Can delete log entry'
Adding permission 'admin | log entry | Can view log entry'
Running post-migrate handlers for application auth
...
Adding permission 'auth | user | Can view user'
...

ただし、他のアプリの post_migrate を実行してしまうため、 emit_post_migrate_signal を実行した段階で存在しないテーブルを参照しようとしてしまう場合があり、完全に安全な方法ではなさそうです。このあたりのチケットでもう少しシンプルに扱えるようになると嬉しいものです。
(そもそもMigration Operationで頑張らずにpost_migrateシグナルで実装すれば良い気がする。)