Custom Permissions in Django: Example by SDH

Aug 07, 2022 0 min read 5644
Viacheslav Bukhantsov CEO
Custom Permissions in Django: Example by SDH

The Django framework has a built-in permissions engine that will suit most simple projects. I can say that this is an ideal solution for the built-in admin interface. But what if the project access rights should be implemented in more complicated way?

In this article I will share our team’s experience and will give you some examples of  the custom permission engine. Our example will not interfere with Django's built-in permissions. This article is accompanied by a complete Django project  with implementation. To simplify the presentation of the code, I deliberately removed the lines that relate to active query caching and performance optimization. 

The demo project will also be useful for novice developers to explore.  

Our Practices for Django Groups & Permissions

We started using the first version of the described approach starting from the Django 1.2 version on the project, called Billing platform for ISP. The requirement of the project in terms of security was the differentiation of user access rights between various companies. The summary below gives more details:

  • Several Companies can be created in the system   
  • There is only one entry for User (i.e. the User has one login and password in all Companies)
  • Access rights in different Companies are independent, for example, User A in Company A is a manager and can change Customer A's data (belong to Company A), but User A in Company B has ReadOnly access to Customer B (belong to Customer B).

As it can be understood from the description of the requirements above, defying the access rights must be carried out at the Object level.

For clarity, I will give a brief description of the models:

 

class Company(models.Model):
   name = models.CharField(max_length=255)

   class Meta:
       db_table = 'company' 


class Customer(models.Model):
   company = models.ForeignKey(Company, on_delete=models.PROTECT)
   name = models.CharField(max_length=255)

   class Meta:
       db_table = 'customer'

 

In the project, these models are separated into different modules for convenience. Let's accept that the description of the permission will consist of two parts: the name of the module and the description of the user action that the permission will control: 

 

Module

Action

Description

company

read

Viewing the object Company

company

create

Creation of the object Company

company

update

Alteration of the object Company

company

delete

Deleting the object Company

customer

read

Viewing the object Customer

customer

create

Creation of the object Customer

customer

update

Alteration of the object Customer

customer

delete

Deleting the object Customer

customer

verify

Verification of the object Customer

customer

activate

Activating the object Customer

 

According to the table, some of the actions are “classic” CRUD, but for display only; both real business applications, and the permission system are more complicated;  two additional functions have been added for the Customer module:  to verify and to activate. 
Thus, in building the permissions engine, we are not limited to the CRUD model, but we will be able to control user actions with the necessary granularity. 

To simplify the code, the rights will be assigned only to groups. In real projects, it is sometimes required to issue rights for individual actions at the user level. 

 

Two-level Structure of Permissions

The two-level structure of permissions will be described by the following models:

 

class AccessModule(models.Model):
   name = models.CharField(max_length=128)
   slug = models.CharField(max_length=128, unique=True)

   class Meta:
       db_table = 'access_module'


class AccessPermission(models.Model):
   module = models.ForeignKey(AccessModule, on_delete=models.PROTECT)
   name = models.CharField(max_length=128)
   slug = models.CharField(max_length=128)
   groups = models.ManyToManyField(Group)

   class Meta:
       db_table = 'access_permission'
       unique_together = (('module', 'slug'),)

The AccessPermission model has a hyperlink to a Group. In such a way a list of groups that are allowed to this permission is identifed. 

In the ERP platform project, such models can be represented by the following configuration interface: 


Custom Permissions in Django — ERP Platform Example

In the result, presentation of sections and access rights by user groups appears flexible  and user-friendly in terms of functionality. One only needs to add a model that will describe which of the groups a user belongs to in different companies:


class AccessModule(models.Model):
   name = models.CharField(max_length=128)
   slug = models.CharField(max_length=128, unique=True)

   class Meta:
       db_table = 'access_module'


class AccessPermission(models.Model):
   module = models.ForeignKey(AccessModule, on_delete=models.PROTECT)
   name = models.CharField(max_length=128)
   slug = models.CharField(max_length=128)
   groups = models.ManyToManyField(Group)

   class Meta:
       db_table = 'access_permission'
       unique_together = (('module', 'slug'),)

 

The object HttpRequest is present in many contexts: it can be found in View, it can be transferred to the template context. Information about the authorized user is also available in  request.user.

Therefore, it would be convenient to add the access to the rights check via Request. For this purpose, middleware has been developed, this is a lazy object to Acl. 

For example, it is possible to use the following construction:

def get_queryset(self):
   qs = super().get_queryset()
   qs = qs.filter(company__in=self.request.acl.get_companies('customer', 'view'))
   return qs

The example above shows the Customer list filtration only for companies where the user has rights. 

It is highly recommended to view the example of CustomerListView from the project.

In this article I have not touched upon active caching of requests by rights. As you can see from the code, adding caching is not difficult when composing a caching key in the following way: 

 <module_slug>:<permissions_slug>:<company_id>

In my next works, an example and description of use of this permission engine combined with Django Rest Framework  will be performed, as well as auto-documenting permissions in conjunction with drf-spectacular will be presented. 

Software Development Hub is a Ukraine-based software engineering company with Python/Django stack. We help tech-enabled organizations meet their growth ambitions through IT talent outsourcing. With a pool of more than 100 highly skilled specialists, SDH can build engineering units with nearly any skillset, providing maximum flexibility to deliver high-quality software development services to projects of any complexity or scale.

 

 

Categories

Django Coding ERP

Share

Need a project estimate?

Drop us a line, and we provide you with a qualified consultation.

x