0

I'm using Django for my backend and everything's fine. But I now have to enable permissions and so on. I'm trying to assign all the permissions of an app to a group during the migration process but here is the problem :

During the initial migration, the Permissions are not created yet. The reason is that in Django, they are created in a post_migrate signal.

See :

def ready(self):
   post_migrate.connect(
       create_permissions,
       dispatch_uid="django.contrib.auth.management.create_permissions"
   )
   # ...

in django.contrib.auth.apps in the ready() method

The default flow is :

  • call migrate command
  • it does migration things
  • post_migrate signal is sent and Permissions records are created

So I could write a post_migrate function too but, how could I be sure that it will be run after the default one that creates Permissions ?

Other question : is there a better way to assign permissions automatically when an app is first migrated ?

Thanks in advance :)

lbris
  • 1,068
  • 11
  • 34

1 Answers1

3

It seems it is currently impossible to act on Permission creation since they are created in a post_migrate signal (here) with the method bulk_create().

See here.

  1. A way to solve this would be to use create() method instead. Which I tried to do by introducing a ticket on Django ticketing system (see here) and its PR.
  2. The second way (which I'll use as long as bulk_create() is maintained for creating Permissions) is to run a method in the ready() method (...) to assign them.

For the 2) solution, I ended up with this :

def distribute_base_permissions():
    """ This method is used to automatically grant permissions of 'base' application
        to the 'Administrator' Group.
    """
    from django.contrib.auth.models import Group, Permission
    from django.contrib.contenttypes.models import ContentType
    group_content_type = ContentType.objects.get_for_model(Group)
    group, created = Group.objects.get_or_create(name="Administrator")
    for model in ContentType.objects.filter(app_label="base"):
        for perm in Permission.objects.filter(content_type__in=[model, group_content_type]):
            if (not group.has_permission(perm.codename) and
                perm.codename not in model.model_class().UNUSED_PERMISSIONS):
                group.add_permissions([perm])


class BaseConfig(AppConfig):
    name = 'backend.base'

    def ready(self):
        distribute_base_permissions()

There are some flourish things in that sample that are used for my specific use case, where I'm able to install/uninstall applications during runtime according to User necessities.

My base application is installed by default so distributing its permissions can be done like this.

For my installable applications, it's pretty much the same except that it isn't done in the ready() method but at the end of my custom installation process :

class Application(models.Model):

    class Meta:
        db_table = "base_application"
        verbose_name = "Application"

    # ...
    def migrate_post_install(self):
        # ...
        self.distribute_permissions()

    def distribute_permissions(self):
        """ This method is used to automatically grant permissions of the installed
            application to the 'Administrator' Group.
        """
        group, created = Group.objects.get_or_create(name="Administrator")
        for model in ContentType.objects.filter(app_label=self.name):
            for perm in Permission.objects.filter(content_type=model):
                if (not group.has_permission(perm.codename) and
                    perm.codename not in model.model_class().UNUSED_PERMISSIONS):
                    group.add_permissions([perm])

EDIT:

The solution 1) has been refused as it can be seen here. A solution that is discussed would be to add a post_migrate handler but since Permission creation is already done in a post_migrate, I don't know how to be sure that my signal handler will be run AFTER the one that creates Permissions...

Otherwise, it seems there is in progress work to change the Permission creation process as can be seen here.

lbris
  • 1,068
  • 11
  • 34
  • This doesn't work for me. I get `django.db.utils.OperationalError: no such table: auth_group` from line `Group.objects.get_or_create(...)` when migrations are applied. – Michael Herrmann Dec 21 '21 at 16:54
  • @MichaelHerrmann This is weird. This would mean that the auth_group table is not present. Did you do the initial migrations ? I guess if you do the `migrate` command it should do them first but who knows..?! – lbris Dec 23 '21 at 07:54
  • I got this when I ran tests, which I suppose does all migrations in one go. – Michael Herrmann Dec 23 '21 at 11:02