Programmatically create a django group with permissions
Asked Answered
U

4

71

In the Admin console, I can add a group and add a bunch of permissions that relate to my models, e.g.

api | project | Can add project
api | project | Can change project
api | project | Can delete project

How can I do this programmatically. I can't find any information out there on how to do this.

I have:

from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType

from api.models import Project


new_group, created = Group.objects.get_or_create(name='new_group')
# Code to add permission to group ???
ct = ContentType.objects.get_for_model(Project)
# Now what - Say I want to add 'Can add project' permission to new_group?

UPDATE: Thanks for the answer you provided. I was able to use that to work out what I needed. In my case, I can do the following:

new_group, created = Group.objects.get_or_create(name='new_group')
proj_add_perm = Permission.objects.get(name='Can add project')
new_group.permissions.add(proj_add_perm)
Unideaed answered 7/3, 2014 at 12:39 Comment(2)
for the UPDATE you mean new_group.permissions.add(proj_add_perm)Onaonager
Sorry to ask, but where did you place your permission script? In the same directory of models.py?Crusado
I
69

Use below code

from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from api.models import Project
new_group, created = Group.objects.get_or_create(name='new_group')
# Code to add permission to group ???
ct = ContentType.objects.get_for_model(Project)

# Now what - Say I want to add 'Can add project' permission to new_group?
permission = Permission.objects.create(codename='can_add_project',
                                   name='Can add project',
                                   content_type=ct)
new_group.permissions.add(permission)
Icao answered 7/3, 2014 at 12:48 Comment(4)
/yourapp/management/commands/anything.py, make sure to include management/__init__.py and commands/__init__.py so that it loads.Denticulate
Thank you JREAM, since i struggled with management/commands a lot i'd like to mention reading up on Custom Django Management Commands might be helpful. i found this link to be very useful.Selfsame
another way of doing it is to create a data migration docs.djangoproject.com/en/3.1/topics/migrations/…Paphos
Why? Why in the init? Why not data migration?Profiterole
G
44

I needed to create a default set of groups and permission (view only) for those groups. I came up with a manage.py command that may be useful to others (create_groups.py). You can add it to your <app>/management/commands dir, and then run via manage.py create_groups:

"""
Create permission groups
Create permissions (read only) to models for a set of groups
"""
import logging

from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group
from django.contrib.auth.models import Permission

GROUPS = ['developers', 'devops', 'qa', 'operators', 'product']
MODELS = ['video', 'article', 'license', 'list', 'page', 'client']
PERMISSIONS = ['view', ]  # For now only view permission by default for all, others include add, delete, change


class Command(BaseCommand):
    help = 'Creates read only default permission groups for users'

    def handle(self, *args, **options):
        for group in GROUPS:
            new_group, created = Group.objects.get_or_create(name=group)
            for model in MODELS:
                for permission in PERMISSIONS:
                    name = 'Can {} {}'.format(permission, model)
                    print("Creating {}".format(name))

                    try:
                        model_add_perm = Permission.objects.get(name=name)
                    except Permission.DoesNotExist:
                        logging.warning("Permission not found with name '{}'.".format(name))
                        continue

                    new_group.permissions.add(model_add_perm)

        print("Created default group and permissions.")

UPDATE: A bit more sophisticated now:

from django.contrib.auth.models import Group
from django.contrib.auth.models import User
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType


READ_PERMISSIONS = ['view', ]  # For now only view permission by default for all, others include add, delete, change
WRITE_PERMISSIONS = ['add', 'change', 'delete']
EMAIL_USER_DOMAIN = 'your-domain.com'

# Add your groups here, app and model code
GROUP_MODEL = ('auth', 'group')
USER_MODEL = ('auth', 'user')
PERMISSION_MODEL = ('auth', 'permission')
LOG_ENTRY_MODEL = ('admin', 'logentry')


def add_group_permissions(group_names, model_natural_keys, permissions):
    """
    Add permissions to the provided groups for the listed models.
    Error raised if permission or `ContentType` can't be found.

    :param group_names: iterable of group names
    :param model_natural_keys: iterable of 2-tuples containing natural keys for ContentType
    :param permissions: iterable of str (permission names i.e. add, view)
    """
    for group_name in group_names:
        group, created = Group.objects.get_or_create(name=group_name)

        for model_natural_key in model_natural_keys:
            perm_to_add = []
            for permission in permissions:
                # using the 2nd element of `model_natural_key` which is the
                #  model name to derive the permission `codename`
                permission_codename = f"{permission}_{model_natural_key[1]}"
                try:
                    perm_to_add.append(
                        Permission.objects.get_by_natural_key(
                            permission_codename, *model_natural_key
                        )
                    )
                except Permission.DoesNotExist:
                    # trying to add a permission that doesn't exist; log and continue
                    logging.error(
                        f"permissions.add_group_permissions Permission not found with name {permission_codename!r}."
                    )
                    raise
                except ContentType.DoesNotExist:
                    # trying to add a permission that doesn't exist; log and continue
                    logging.error(
                        "permissions.add_group_permissions ContentType not found with "
                        f"natural name {model_natural_key!r}."
                    )
                    raise

            group.permissions.add(*perm_to_add)


def set_users_group(users, group):
    """
    Adds users to specific permission group.
    If user or group does not exist, they are created.
    Intended for use with special users for api key auth.
    :param users: list of str, usernames
    :param group: str, group for which users should be added to
    :return: list, user objects added to group
    """
    users = users or []
    user_objs = []
    for user_name in users:
        try:
            user = User.objects.get(username=user_name)
        except User.DoesNotExist:
            user = User.objects.create_user(username=user_name,
                                            email=f'{user_name}@{EMAIL_USER_DOMAIN}',
                                            password='')
        user_objs.append(user)
        group, created = Group.objects.get_or_create(name=group)
        group.user_set.add(user)

    return user_objs


API_READ_GROUP = 'api-read-users'
API_WRITE_GROUP = 'api-write-users'
READ_GROUPS = [API_READ_GROUP, ]
WRITE_GROUPS = [API_WRITE_GROUP, ]  # Can be used in same way as read users below

# Adding users to a group
set_users_group(READ_USERS, API_READ_GROUP)

# Setting up the group permissions i.e. read for a group of models
add_group_permissions(READ_GROUPS, [GROUP_MODEL, USER_MODEL, LOG_ENTRY_MODEL], READ_PERMISSIONS)

I also found that using manage.py update_permissions is useful to sort out/clean up stale permissions if models have changed etc.. Its part of django-extensions commands.

Gobelin answered 11/12, 2018 at 23:6 Comment(6)
Note model names should be lower case, space separated, eg, for UserInfo pass user info. Or find the permission on codename instead, which is something like view_userinfoAtheism
Note that model names will change, based on the verbose_name if specified on your model.Gobelin
Nice. You might want to use "codename" instead of name, and also filter on content_type in case the same model_name is used in different apps: MODELS = ['my_app.video', .... app_label, model_name = MODELS[n].split('.'') ... content_type = ContentType.objects.get(app_label=app_label, model=model_name) ... model_add_perm = Permission.objects.get(codename=codename, content_type=content_type)Snippet
Where would be the best place to add this script other than management/commands?Crusado
what are the advantages of having a management command for creating and adding group and permissions instead of adding the code to models.py?Wyrick
@EliasPrado manage.py searches the management/commands directory and as such management commands should lie thereWyrick
H
24

Inspired by radtek's answer I created a bit better version (in my opinion). It allows specifying model as object (instead of string) and specifying all configuration in one dictionary (instead of several lists)

# backend/management/commands/initgroups.py
from django.core.management import BaseCommand
from django.contrib.auth.models import Group, Permission

from backend import models

GROUPS_PERMISSIONS = {
    'ConnectionAdmins': {
        models.StaticCredentials: ['add', 'change', 'delete', 'view'],
        models.NamedCredentials: ['add', 'change', 'delete', 'view'],
        models.Folder: ['add', 'change', 'delete', 'view'],
        models.AppSettings: ['view'],
    },
}

class Command(BaseCommand):
    def __init__(self, *args, **kwargs):
        super(Command, self).__init__(*args, **kwargs)

    help = "Create default groups"

    def handle(self, *args, **options):
        # Loop groups
        for group_name in GROUPS_PERMISSIONS:

            # Get or create group
            group, created = Group.objects.get_or_create(name=group_name)

            # Loop models in group
            for model_cls in GROUPS_PERMISSIONS[group_name]:

                # Loop permissions in group/model
                for perm_index, perm_name in \
                        enumerate(GROUPS_PERMISSIONS[group_name][model_cls]):

                    # Generate permission name as Django would generate it
                    codename = perm_name + "_" + model_cls._meta.model_name

                    try:
                        # Find permission object and add to group
                        perm = Permission.objects.get(codename=codename)
                        group.permissions.add(perm)
                        self.stdout.write("Adding "
                                          + codename
                                          + " to group "
                                          + group.__str__())
                    except Permission.DoesNotExist:
                        self.stdout.write(codename + " not found")
Hoekstra answered 4/10, 2019 at 7:23 Comment(8)
Much better indeed as you can easily specify permissions for each group instead a complete many to many as in previous answer (still great answer btw). What I'd suggest is to create these permissions from within a [empty / data] migration, helping also to track business decisions about the point in which new groups/permissions were added.Rugger
Thank you. You should also filter on content_type in case the same model_name is used by different apps. So: perm = Permission.objects.get(codename=codename, content_type=content_type), where: content_type = ContentType.objects.get(app_label=app_label, model=model_name)Snippet
Thanks for this. The only change I would make would be to remove the __init__ method definition. It doesn't seem to serve any purpose here.Behah
Thanks. The idea is nice but the code itself could be improved. Why use enumerate for example? Also when iterating over a dict, you may want to use dict.items() to get both the keys and values at the same time. Much appreciated nonetheless.Darrin
How is it that "from backend import models" doesn't work for me? Django 3.1Oread
Nvmd I just saw that was your directory structure.Oread
I have since updated the script, let me see if I can add an updateGobelin
what is the structure of the models script?Manhandle
G
13

Taking ideas from the answers of @radtek and @Pavel I created my own version of create_groups.py which I call using python manage.py create_groups. This file is stored on app_name/management/commands/create_groups.py. I created a __init__.py inside each the management and commands folders.

I have created the possibility to control each model permissions separately because I had a group of users called Member that must have different permissions on different models. I also added the possibility to create users with emails and a default password that would have to be changed afterwards and associate them with a certain group.

from django.core.management import BaseCommand
from django.contrib.auth.models import User, Group , Permission
import logging

GROUPS = {
    "Administration": {
        #general permissions
        "log entry" : ["add","delete","change","view"],
        "group" : ["add","delete","change","view"],
        "permission" : ["add","delete","change","view"],
        "user" : ["add","delete","change","view"],
        "content type" : ["add","delete","change","view"],
        "session" : ["add","delete","change","view"],

        #django app model specific permissions
        "project" : ["add","delete","change","view"],
        "order" : ["add","delete","change","view"],
        "staff time sheet" : ["add","delete","change","view"],
        "staff" : ["add","delete","change","view"],
        "client" : ["add","delete","change","view"],       
    },

    "Member": {
        #django app model specific permissions
        "project" : ["view"],
        "order" : ["view"],
        "staff time sheet" : ["add","delete","change","view"],
    },
}


USERS = {
    "my_member_user" : ["Member","[email protected]","1234*"],
    "my_admin_user" :  ["Administration","[email protected]","1234"],
    "Admin" : ["Administration","[email protected]","1234"],
}

class Command(BaseCommand):

    help = "Creates read only default permission groups for users"

    def handle(self, *args, **options):

        for group_name in GROUPS:

            new_group, created = Group.objects.get_or_create(name=group_name)

            # Loop models in group
            for app_model in GROUPS[group_name]:

                # Loop permissions in group/model
                for permission_name in GROUPS[group_name][app_model]:

                    # Generate permission name as Django would generate it
                    name = "Can {} {}".format(permission_name, app_model)
                    print("Creating {}".format(name))

                    try:
                        model_add_perm = Permission.objects.get(name=name)
                    except Permission.DoesNotExist:
                        logging.warning("Permission not found with name '{}'.".format(name))
                        continue

                    new_group.permissions.add(model_add_perm)


            for user_name in USERS:

                new_user = None
                if user_name == "Admin":
                    new_user, created = User.objects.get_or_create(username=user_name,is_staff = True,is_superuser = True, email = USERS[user_name][1])
                else:
                    new_user, created = User.objects.get_or_create(username=user_name,is_staff = True, email = USERS[user_name][1])

                new_user.set_password(USERS[user_name][2])
                new_user.save()

                if USERS[user_name][0] == str(new_group):

                    new_group.user_set.add(new_user)

                    print("Adding {} to {}".format(user_name,new_group))
Grope answered 14/11, 2020 at 23:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.