首页 > 解决方案 > 为什么我不能在 Django 的同一迁移中为组分配新权限

问题描述

我正在尝试按照本教程Meta添加新的迁移,我在permissions字段中 添加了新权限。然后我创建了迁移并尝试修改此迁移以更新组权限。却开工DoesNotExistRunPython

from django.db import migrations


def assign_new_permission(apps, *args):
    Permission = apps.get_model('auth.Permission')
    Group = apps.get_model('auth.Group')
    # __fake__.DoesNotExist: Permission matching query does not exist.
    new_permission = Permission.objects.get(
        codename='my_new_permissoin_code')

    admins = Group.objects.get(name='Group name')
    admins.permissions.add(new_permission)


class Migration(migrations.Migration):
    dependencies = [
        ('my_app', '0066_some_migratoin'),
    ]

    operations = [
        migrations.AlterModelOptions(
            name='my_model',
            options={'permissions': (('my_new_permissoin_code',
                                      'Permission name'),)},
        ),
        migrations.RunPython(assign_new_permission)
    ]

标签: djangodjango-modelsdjango-migrationsdjango-permissions

解决方案


I think that your problem occurs because permissions are not actually created during or after an individual migration, but are triggered by a post-migrate signal which is sent after the python manage.py migrate command completes successfully (see comments on the accepted answer here.)

There are a few ways around it:

  1. Split it into two separate migrations (create permissions and then assign them) and run them with two separate python manage.py migrate commands:

    python manage.py migrate my_app 0066_create_permissions
    python manage.py migrate my_app 0067_assign_permissions
    

    This allows the post-migrate signal to be emitted after 0066 is run to create the permissions.

  2. Split it up into two steps inside one migration, but you'll have to manually create the permissions with a Python function and then assign them in another. You'll have to modify your operations to account for that. One benefit of this is that if want to, you can create a Python function or functions to reverse the migration as well, which isn't really possible for #3 or #4.

  3. Emit the post-migrate signal yourself during the migration. This is a good solution for cases where you need permissions from 3rd party apps (like django-guardian, as in my case) so that you can apply them in data migrations.

    from django.apps import apps as django_apps
    def guardian_post_migrate_signal(apps, schema_editor):
        guardian_config = django_apps.get_app_config('guardian')
        models.signals.post_migrate.send(
            sender=guardian_config,
            app_config=guardian_config,
            verbosity=2,
            interactive=False,
            using=schema_editor.connection.alias,
        )
    
  4. This is similar to #3, but a little easier. There are probably subtle ways in which it is better or worse, but I'm not sure of what they are. You can create a function which calls django.contrib.auth.management.create_permissions (credit to this post) and use it directly in your migration:

    from django.contrib.auth.management import create_permissions
    
    def create_perms(apps, schema_editor):
        for app_config in apps.get_app_configs():
            app_config.models_module = True
            create_permissions(app_config, apps=apps, verbosity=0)
            app_config.models_module = None
    

    Then your operations would look like:

        operations = [
            migrations.AlterModelOptions(
                name='my_model',
                options={'permissions': (('my_new_permission_code', 'Permission name'),)},
            ),
            migrations.RunPython(create_perms),
            migrations.RunPython(assign_perms)
        ]
    

Anyway, I hope that helps. Sorry for the information overload - I've had the same issue as you in the past and it's set me back a few days.


推荐阅读