Django Object Level Permission Template

A template to guide you how to setup a Django Project with Object Level Permission.

1. Authentication

1.1. Packages

  • Djoser provide commonly use authentication functions such as: Sign Up, Login, Change Password, Forgot Password, User Account Activation, User Profile

  • Simple JWT provide Json Web Token authentication method.

  • Django Guardian provide object level permission management.

1.2. Configuration

  • JWT authentication is configured in

core/settings.py


# Application definition

INSTALLED_APPS = [
(...),

'rest_framework',
'djoser',
'guardian',
]

AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend', # default
'guardian.backends.ObjectPermissionBackend',
)

# Configuration for Django Rest Framework
# https://www.django-rest-framework.org/

REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (

# Simple JWT Authentication
# https://django-rest-framework-simplejwt.readthedocs.io/
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}

core/urls.py

urlpatterns = [
(...),

path('auth/', include('djoser.urls')),
path('auth/', include('djoser.urls.jwt')),
]

1.3. Data Model

1.3.1. Using a custom model

If you’re starting a new project, it’s highly recommended to set up a custom user model, even if the default User model is sufficient for you. This model behaves identically to the default user model, but you’ll be able to customize it in the future if the need arises.

core/user/models.py

from django.contrib.auth.models import AbstractUser
from django.db import models


class User(AbstractUser):
pass

core/settings.py

AUTH_USER_MODEL = 'user.User'

INSTALLED_APPS = [
(...),
'core.user',
]

1.3.2. Reusable apps and AUTH_USER_MODEL

Reusable apps shouldn’t implement a custom user model. A project may use many apps, and two reusable apps that implemented a custom user model couldn’t be used together. If you need to store per user information in your app, use a ForeignKey or OneToOneField to settings.AUTH_USER_MODEL as described below.

Referencing the User model

If you reference User directly (for example, by referring to it in a foreign key), your code will not work in projects where the AUTH_USER_MODEL setting has been changed to a different user model.

Instead of referring to User directly, you should reference the user model using django.contrib.auth.get_user_model(). This method will return the currently active user model – the custom user model if one is specified, or User otherwise.

When you define a foreign key or many-to-many relations to the user model, you should specify the custom model using the AUTH_USER_MODEL setting. For example:

from django.conf import settings
from django.db import models

class Article(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)

1.4. Declare model permissions

1.4.1 Django builtin permission

Django comes with a built-in permissions system. It provides a way to assign permissions to specific users and groups of users.

When django.contrib.auth is listed in your INSTALLED_APPS setting, it will ensure that four default permissions – add, change, delete, and view – are created for each Django model defined in one of your installed applications.

1.4.2 Declare new permission

Define directly inside model

class Job(models.Model):
(...)
class Meta:
permissions = [('review_job', 'Can review job (approve or deny)')]

Define programmatically
from myapp.models import Job
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType

content_type = ContentType.objects.get_for_model(Job)
permission = Permission.objects.create(
codename='review_job',
name='Can review job (approve or deny)',
content_type=content_type,
)

1.4.3 Assign permission to user/group

Global Permission

>> from django.contrib.auth import get_user_model
>> from django.contrib.auth.models import Group
>> User = get_user_model()

>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack')
>> jack.has_perm('auth.change_group')
False

>> from guardian.shortcuts import assign_perm
>> assign_perm('auth.change_group', jack)
>> jack.has_perm('auth.change_group')
True

Object Permission

>> from django.contrib.auth import get_user_model
>> from django.contrib.auth.models import Group
>> User = get_user_model()

>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack')
>> admins = Group.objects.create(name='admins')
>> jack.has_perm('auth.change_group', admins)
False

>> from guardian.shortcuts import assign_perm
>> assign_perm('auth.change_group', jack, admins)
>> jack.has_perm('auth.change_group', admins)
True

1.4.4 Retrieve user permitted objects

>> from django.contrib.auth import get_user_model
>> from django.contrib.auth.models import Group
>> User = get_user_model()

>> jack = User.objects.create_user('jack', 'jack@example.com', 'topsecretagentjack')
>> admins = Group.objects.create(name='admins')
>> leaders = Group.objects.create(name='leaders')

>> from guardian.shortcuts import assign_perm, get_objects_for_user
>> get_objects_for_user(jack, perms=['auth.change_group'], accept_global_perms=True)
<QuerySet []>

>> assign_perm('auth.change_group', jack, admins)
>> get_objects_for_user(jack, perms=['auth.change_group'], accept_global_perms=True)
<QuerySet [<Group: admins>]>

# Let try with global permission
>> assign_perm('auth.change_group', jack)
>> get_objects_for_user(jack, perms=['auth.change_group'], accept_global_perms=True)
<QuerySet [<Group: admins>, <Group: level1>]>

2. Database

2.1. Packages

  • dj-database-url database connection string parser that is helpful to parse database configuration in a connection string style.

2.2. Supported URL connection schema

Engine Django Backend URL
SQLite django.db.backends.sqlite3 sqlite:///PATH
PostgreSQL django.db.backends.postgresql postgres://USER:PASSWORD@HOST:PORT/NAME
PostGIS django.contrib.gis.db.backends.postgis postgis://USER:PASSWORD@HOST:PORT/NAME
MSSQL sql_server.pyodbc mssql://USER:PASSWORD@HOST:PORT/NAME
MySQL django.db.backends.mysql mysql://USER:PASSWORD@HOST:PORT/NAME
MySQL (GIS) django.contrib.gis.db.backends.mysql mysqlgis://USER:PASSWORD@HOST:PORT/NAME
Oracle django.db.backends.oracle oracle://USER:PASSWORD@HOST:PORT/NAME
Oracle (GIS) django.contrib.gis.db.backends.oracle oraclegis://USER:PASSWORD@HOST:PORT/NAME
Redshift django_redshift_backend redshift://USER:PASSWORD@HOST:PORT/NAME
SpatiaLite django.contrib.gis.db.backends.spatialite spatialite:///PATH

2.3. Configuration

This is configuration for the database, the default database will use sqlite3 for simple to quick to run in development mode. This may conflict with database you run on production mode because some of the funtion sqlite may not support. It is recommended to config the database in development mode have the same database system you run on production. To change the database system, config the environment variable DATABASE_URL

core/settings.py

import dj_database_url

# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases

DATABASE_URL = os.environ.get('DATABASE_URL', 'sqlite:///{}'.format(os.path.join(BASE_DIR, 'db.sqlite3')))
DATABASES = {
'default': dj_database_url.config(default=DATABASE_URL)
}

3. API Development

3.1. Packages

3.2. Serializers

TODO What is serializers, how to code one?

3.3. Views

TODO What is views, how to code one?