Permissions

(Note: this page is about authorization, not authentication.)

Link: https://docs.djangoproject.com/en/stable/topics/auth/default/#topic-authorization

User objects have two many-to-many fields: groups and user_permissions. User objects can access their related objects in the same way as any other Django model:

myuser.groups = [group_list]
myuser.groups.add(group, group, ...)
myuser.groups.remove(group, group, ...)
myuser.groups.clear()
myuser.user_permissions = [permission_list]
myuser.user_permissions.add(permission, permission, ...)
myuser.user_permissions.remove(permission, permission, ...)
myuser.user_permissions.clear()

Assuming you have an application with an app_label foo and a model named Bar, to test for basic permissions you should use:

  • add: user.has_perm(‘foo.add_bar’)

  • change: user.has_perm(‘foo.change_bar’)

  • delete: user.has_perm(‘foo.delete_bar’)

Note

There is no default permission for read access.

The Permission model is rarely accessed directly, but here it is:

permission = Permission.objects.create(codename='can_publish',
                                       name='Can Publish Posts',  # verbose human-readable name
                                       content_type=content_type)

Permissions are more commonly referred to by a string, of the form “app_label.codename”. E.g., if user.has_perm('myapp.codename'):  do something.

Confusingly, the Permission model has no app_label field. It uses content_type__app_label for that.

Warning

This means all permissions need a content type, whether it makes sense for that permission to be applied to a particular model or not.

To create a new permission programmatically:

content_type = ContentType.objects.get_for_model(BlogPost)
permission = Permission.objects.create(codename='can_publish',
                                       name='Can Publish Posts',
                                       content_type=content_type)

Default permissions

https://docs.djangoproject.com/en/stable/topics/auth/default/#default-permissions

For every model in an installed app, Django automatically creates three permissions: applabel.add_modelname, applabel.change_modelname, and applabel.delete_modelname, where the modelname is lowercased.

Adding model permissions

https://docs.djangoproject.com/en/stable/ref/models/options/#permissions

You can ask Django to create more permissions for a model:

class Meta:
    permissions = [
        ('codename', 'verbose name'),
    ]

When the table is created during syncdb, Django will create the additional Permission objects too.

In Django 1.11 (and probably earlier, but definitely not before 1.7), if you edit these permissions and makemigrations, Django will create a migration for you that when run, will add any missing migrations. (I don’t know whether it’ll update verbose names of existing permissions.)

You can programmatically force Django to create additional Permissions with code like:

from django.db.models import get_models, get_app
from django.contrib.auth.management import create_permissions

apps = set([get_app(model._meta.app_label) for model in get_models()])
for app in apps:
    create_permissions(app, None, 2)

Best practices

  • Plan not to give users specific permissions, except when you have to make an exception to your usual policies.

  • Design groups with useful sets of permissions.

  • Plan to add users to the appropriate groups depending on their roles.

  • Provide a way to ensure the groups continue to have the permissions you want.

Fixtures aren’t a bad way to provide initial data, but setting them up for automatic loading is deprecated with Django 1.7 and will go away with Django 2.0. Instead, load them from a data migration. This is better in some ways anyway, because the migration will use the same version of the models that the fixtures were written for at the time. (Though, this doesn’t matter so much for Permissions and Groups, which we don’t really expect to change their schemas…)

Add utility methods like this, maybe in accounts/utils.py or equivalent:

def permission_names_to_objects(names):
    """
    Given an iterable of permission names (e.g. 'app_label.add_model'),
    return an iterable of Permission objects for them.  The permission
    must already exist, because a permission name is not enough information
    to create a new permission.
    """
    result = []
    for name in names:
        app_label, codename = name.split(".", 1)
        # Is that enough to be unique? Hope so
        try:
            result.append(Permission.objects.get(content_type__app_label=app_label,
                                                 codename=codename))
        except Permission.DoesNotExist:
            logger.exception("NO SUCH PERMISSION: %s, %s" % (app_label, codename))
            raise
    return result


def get_all_perm_names_for_group(group):
    # Return the set of permission names that the group should contain


def create__or_update_groups():
    for group_name, perm_names in GROUP_PERMISSIONS.iteritems():
        group, created = Group.objects.get_or_create(name=group_name)
        perms_to_add = permission_names_to_objects(get_all_perm_names_for_group(group))
        group.permissions.add(*perms_to_add)
        if not created:
            # Group already existed - make sure it doesn't have any perms we didn't want
            to_remove = set(group.permissions.all()) - set(perms_to_add)
            if to_remove:
                group.permissions.remove(*to_remove)

Checking permissions in templates

https://docs.djangoproject.com/en/stable/topics/auth/default/#authentication-data-in-templates

{% if user.is_authenticated %} {% if perms.applabel %} {# user has any permissions in app applabel #} {% if ‘applabel’ in perms %} {# same as above %} {% if perms.applabel.change_thing %} {# user has ‘change_thing’ permission in app applabel #} {% if ‘applabel.change_thing’ in perms %} {# same as above #}