[Solved]-How to create groups and assign permission during project setup in django?

16👍

You can define a post_migrate signal to create required User and Group model instances if they don’t exist already.

When you create an application in using python manage.py startapp <app_name>, it creates an AppConfig class in apps.py file.

You can specify which signal to call in AppConfig class definition. Say the signal is called populate_models. In that case, modify AppConfig to look like following:

from django.apps import AppConfig
from django.db.models.signals import post_migrate

class AppConfig(AppConfig):
    name = 'app'

    def ready(self):
        from .signals import populate_models
        post_migrate.connect(populate_models, sender=self)

And in signals.py define the populate_models function.

def populate_models(sender, **kwargs):
    from django.contrib.auth.models import User
    from django.contrib.auth.models import group
    # create groups
    # assign permissions to groups
    # create users

5👍

According to @narenda-choudhary and @Rolando Cruz answer,

Here is the code for adding all existing permissions of an app under a Group.
For exemple if you have App1 and App2, and you want to automatically create 2 groups named app1 and app2 containing permissions to the models of each app (respectively), try this :

For App1 on apps.py :

from django.apps import AppConfig
from django.db.models.signals import post_migrate

class App1Config(AppConfig):
    name = 'app1'

    def ready(self):
        from .signals import populate_models
        post_migrate.connect(populate_models, sender=self)

Create a signals.py file that contains :

def populate_models(sender, **kwargs):
    from django.apps import apps
    from .apps import App1Config
    from django.contrib.auth.models import Group, Permission
    from django.contrib.contenttypes.models import ContentType

    group_app, created = Group.objects.get_or_create(name=App1Config.name)

    models = apps.all_models[App1Config.name]
    for model in models:
        content_type = ContentType.objects.get(
            app_label=App1Config.name,
            model=model
        )
        permissions = Permission.objects.filter(content_type=content_type)
        group_app.permissions.add(*permissions)

Do the same for App2

Then assign users to their groups.

For usage :

Create a file called permissions.py on each app and add :

from .apps import App1Config

def is_in_group_app1(user):
    return user.groups.filter(name=App1Config.name).exists() 

In views.py use :

from django.contrib.auth.decorators import login_required, user_passes_test
from .permissions import is_in_group_app1

@login_required(login_url='where_to_redirect')
@user_passes_test(is_in_group_app1) 
def myview(request):
    # Do your processing

For CBV :

@method_decorator(user_passes_test(is_in_group_app1), name='dispatch')
class LogListView(ListView):
    """ Displays all logs saved on App1 """
    model= Logger.objects.order_by('-date_created')

Create a folder named templatestag and subfolder app1 and a has_perms.py file :

from django import template
from app1.permissions import is_in_group_app1
register = template.Library()

@register.filter
def has_perms(user):
    return is_in_group_app1(user)

In your template :

{% load has_perms %}

{% if request.user|has_perms %}
    <li class="nav-item">
        <a href="{% url 'app1:log' %}" class="nav-link">
            <i class="icon-history"></i>
            <span data-i18n="nav.dash.main">App1 Log</span>
        </a>
    </li>
{% endif %}

It took me a while to find all this process, so if it can help others :

Enjoy 😉 !

1👍

Note: scroll down to see my code implementation.

Django automatically populates the database at least on the following:

Looking at the source code (django.contrib.contenttypes, django.contrib.auth, django.contrib.sites), I found the following pattern:

  • Connect the receiver function to post_migrate signal inside the AppConfig.

    Example from django.contrib.sites.apps:

    class SitesConfig(AppConfig):
        ...
    
        def ready(self):
            post_migrate.connect(create_default_site, sender=self)
            ...
    
  • Define the receiver function in the management module (instead of inside the signal module, which is usually used for defining the signal itself), and get the model class using apps.get_model() (instead of directly import the model class which could be inconsistent with the current state of migration).

    Example from django.contrib.sites.management:

    def create_default_site(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, apps=global_apps, **kwargs):
        try:
            Site = apps.get_model('sites', 'Site')
        except LookupError:
            return
    
        if not router.allow_migrate_model(using, Site):
            return
    
        if not Site.objects.using(using).exists():
            ...
    

    The most important parameter here is apps for getting a model in the current migration state. If you use multiple databases, you’ll also need the using parameter to check if the model is allowed to be migrated and to be used in your query’s .using(). For other parameter explanations, check the post_migrate signal docs.

Talk is cheap. Show me the code!

  • I created an assign_group_permissions() function, which returns a receiver function that populates some permissions for a given group.

    someproject/accounts/management.py:

    from django import apps as global_apps
    from django.db.models import Q
    
    
    def assign_group_permissions(group_name, permissions):
        def receiver(*args, apps=global_apps, **kwargs):
            try:
                Group = apps.get_model('auth', 'Group')
                Permission = apps.get_model('auth', 'Permission')
            except LookupError:
                return
    
            perm_q = Q()
            for perm in permissions:
                app_label, codename = perm.split('.')
                perm_q |= Q(content_type__app_label=app_label) & Q(codename=codename)
    
            group, _ = Group.objects.get_or_create(name=group_name)
            group.permissions.add(
                *Permission.objects.filter(perm_q)
            )
    
        return receiver
    
  • Call the function inside each of the corresponding AppConfig. The example below shows how to assign view, add, change, and delete permissions of the Announcement model, which lives in someproject.announcements app to event_admin group.

    someproject/announcements/apps.py:

    from django.apps import AppConfig
    from django.db.models.signals import post_migrate
    
    from someproject.accounts.management import assign_group_permissions
    
    
    class AnnouncementConfig(AppConfig):
        name = 'someproject.announcements'
    
        def ready(self):
            post_migrate.connect(assign_group_permissions(
                'event_admin',
                [
                    'announcements.add_announcement',
                    'announcements.change_announcement',
                    'announcements.delete_announcement',
                    'announcements.view_announcement',
                ],
            ))
    

Leave a comment