70

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)
whirish
  • 450
  • 4
  • 18
Steve Walsh
  • 6,363
  • 12
  • 42
  • 54

4 Answers4

68

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)
anuragal
  • 3,024
  • 19
  • 27
  • 4
    `/yourapp/management/commands/anything.py`, make sure to include `management/__init__.py` and `commands/__init__.py` so that it loads. – JREAM Aug 15 '16 at 05:23
  • 1
    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](http://janetriley.net/2014/11/quick-how-to-custom-django-management-commands.html) to be very useful. – atb00ker Mar 26 '18 at 13:58
  • 3
    another way of doing it is to create a data migration https://docs.djangoproject.com/en/3.1/topics/migrations/#data-migrations – kamayd Nov 17 '20 at 15:40
43

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.

radtek
  • 34,210
  • 11
  • 144
  • 111
  • 1
    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_userinfo` – Chris Jun 08 '19 at 18:15
  • 2
    Note that model names will change, based on the `verbose_name` if specified on your model. – radtek Jun 13 '19 at 18:28
  • 1
    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) – Mario Orlandi Dec 04 '19 at 08:15
  • Where would be the best place to add this script other than `management/commands`? – Elias Prado May 22 '21 at 19:35
  • what are the advantages of having a management command for creating and adding group and permissions instead of adding the code to ```models.py```? – Nnaobi Mar 28 '22 at 23:22
  • @EliasPrado [manage.py searches](https://docs.djangoproject.com/en/4.0/howto/custom-management-commands/#django.core.management.BaseCommand.add_arguments) the ```management/commands``` directory and as such management commands should lie there – Nnaobi Mar 28 '22 at 23:58
23

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")
Pavel
  • 383
  • 3
  • 6
  • 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. – pmourelle Oct 16 '19 at 03:51
  • 2
    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) – Mario Orlandi Dec 04 '19 at 08:17
  • 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. – Thismatters Feb 16 '21 at 13:39
  • 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. – Jacques Gaudin Mar 17 '21 at 23:28
  • How is it that "from backend import models" doesn't work for me? Django 3.1 – logicOnAbstractions Mar 31 '21 at 16:03
  • 1
    Nvmd I just saw that was your directory structure. – logicOnAbstractions Mar 31 '21 at 16:07
  • I have since updated the script, let me see if I can add an update – radtek Mar 30 '22 at 14:28
  • what is the structure of the models script? – Kjobber Mar 16 '23 at 01:26
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","member@domain.cu","1234*"],
    "my_admin_user" :  ["Administration","admin@domain.ca","1234"],
    "Admin" : ["Administration","superuser@domain.cu","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))
VMMF
  • 906
  • 1
  • 17
  • 28