Search code examples
djangodjango-migrationsdjango-permissions

Django : how to assign permissions automatically after initial migration


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 :)


Solution

  • 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.