Django per-model authorization permissions

Question:

Im facing a problem in Django with authorization permissions (a bit new to Django).
I have a teacher, student and manager models.
When a teacher sends a request to my API they should get different permissions than a student (ie, a student will see all of his own test grades, while a teacher can see all of its own class’s students, and a manager can see everything).

My questions are as follows:

  1. How do I make all of my models valid system users? I’ve tried adding
models.OneToOneField(User, on_delete=models.CASCADE)

But this requires creating a user, and then assigning it to the teacher. What I want is for the actual teacher "instance" to be the used user.

  1. How do I check which "type" is my user ? if they are a teacher, student or manager? do I need to go over all 3 tables every time a user sends a request, and figure out which they belong to ? doesnt sound right.
    I thought about creating a global ‘user’ table with a "type" column, but then I wont be able to add specific columns to my models (ie a student should have an avg grade while a teacher shouldn’t) .

Would appreciate any pointers in the right direction.

Asked By: AvitanD

||

Answers:

When you need multiple user types, for example, in your case multiple roles are needed like a student, teacher, manager, etc… then you need a different role for all the persons to categorize.
To have these roles you need to extend AbstractUser(for simple case) in your models.py for your User model also You can specify permissions in your models. Attaching permissions is done on the model’s class Meta using the permissions field. You will be able to specify as many permissions as you need, but it must be in a tuple like below:

from django.db import models
from django.contrib.auth.models import AbstractUser
from django.db.models.fields.related import ForeignKey
from django.utils.translation import gettext as _


class Role(models.Model):
  STUDENT = 1
  TEACHER = 2
  MANAGER = 3
  ROLE_CHOICES = (
      (STUDENT, 'student'),
      (TEACHER, 'teacher'),
      (MANAGER, 'manager'),
  )

  id = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, primary_key=True)

  def __str__(self):
      return self.get_id_display()

class User(AbstractUser):
    roles = models.ManyToManyField(Role)
    username = models.CharField(max_length = 50, blank = True, null = True, unique = True)
    email = models.EmailField(_('email address'), unique = True)
    native_name = models.CharField(max_length = 5)
    phone_no = models.CharField(max_length = 10)
    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['username', 'first_name', 'last_name']
    def __str__(self):
        return "{}".format(self.email)

class Student(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='students')
    sample_field_name = models.CharField(max_length = 50, blank = True, null = True)

    class Meta:
        permissions = (("sample_permission", "can change sth of sth"),)


class Teacher(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='teachers')
    sample_field_name = models.CharField(max_length = 50, blank = True, null = True)

    class Meta:
        permissions = (("sample_permission", "can change sth in sth"),)

class Manager(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='managers')
    sample_field_name = models.CharField(max_length = 50, blank = True, null = True)

    class Meta:
        permissions = (("sample_permission", "can change sth in sth"),)

After that you should have your permissions for your views and Adding permissions to restrict a function to only users that have that particular permission can be done by using a Django built-in decorator, permission_required for function-based views::

from django.contrib.auth.decorators import permission_required

@permission_required('students.sample_permission')
def student_sample_view(request):
    """Raise permission denied exception or redirect user"""

And if you are using a class-based view, you just need to use a mixin, PermissionRequiredMixin:

from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import ListView

class SampleListView(PermissionRequiredMixin, ListView):
    permission_required = 'students.sample_permission'
    # Or multiple permissions
    permission_required = ('students.sample_permission', 'teachers.other_sample_permission')

This was one way you can manage multiple roles in your Django project,
you can also find more ways in below blogs and references:

Answered By: Javad
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.