How to implement Django multiple user types, while one user can have different role according to the project he/she is working on?
Asked Answered
F

2

9

I couldn't find a solution to my problem and would appreciate comments/help on this.

I would like to develop a multiple user type model in Django, along the lines of this video where the author is using Django Proxy Models.

Situation

I have a list of XX projects (proj01, proj02 , projXX, ...). All these projects have their specific page that can be accessed through a specific url mysite/projXX/

I have multiple users: Adam, Bob, Caroline, Dany, Ed, ...

Each user can have several roles according to the project they are working on (e.g. manager, developer, documentarist, reviewer, editor, ...)

A user can have a different role according to the project. E.g. Adam can be reviewer on proj01 but editor on proj02 while Bob can be editor on proj01 but reviewer on proj02, etc..

I started defining multiple user types in the models.py file below (only reviewer and editor roles):

# accounts/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
from django.urls import reverse
from django.utils.translation import gettext_lazy as _


class User(AbstractUser):
    class Types(models.TextChoices):
        EDITOR= "EDITOR", "Editor"
        REVIEWER = "REVIEWER", "Reviewer"

    base_type = Types.EDITOR

    type = models.CharField(
        _("Type"), max_length=50, choices=Types.choices, default=base_type
    )

    name = models.CharField(_("Name of User"), blank=True, max_length=255)

    def get_absolute_url(self):
        return reverse("users:detail", kwargs={"username": self.username})

    def save(self, *args, **kwargs):
        if not self.id:
            self.type = self.base_type
        return super().save(*args, **kwargs)


class EditorManager(models.Manager):
    def get_queryset(self, *args, **kwargs):
        return super().get_queryset(*args, **kwargs).filter(type=User.Types.EDITOR)


class ReviewerManager(models.Manager):
    def get_queryset(self, *args, **kwargs):
        return super().get_queryset(*args, **kwargs).filter(type=User.Types.REVIEWER)


class EditorMore(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    gadgets = models.TextField()


class Editor(User):
    base_type = User.Types.EDITOR
    objects = EditorManager()

    class Meta:
        proxy = True

    def edit(self):
        return "Edition in progress"


class ReviewerMore(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    model = models.CharField(max_length=255)
    make = models.CharField(max_length=255)
    year = models.IntegerField()


class Reviewer(User):
    base_type = User.Types.REVIEWER
    objects = ReviewerManager()

    @property
    def more(self):
        return self.reviewermore

    class Meta:
        proxy = True

    def review(self):
        return "In Review" 

Question:

What is the best way to handle the fact that the role of the user can change according to the project page he/she is visiting?

Example: If Adam is logged in and visits the page mysite/proj01/ I would like him to access only the content allowed for a reviewer while if Adam visit mysite/proj02/, I would like the user to see only the content allowed to the editor.

Ideally, I would like each user to have its unique entry in the user database. I was thinking that the project-dependent role level could be stored as a dictionary? For example:

{'proj01':'reviewer', 'proj02':'editor', 'projxx': 'roleY', ... } 

How would combine this user model and the list of project-dependent permissions?

Edit 02/07/21

Add example files for a project app, models.py and views.py :

# projects/models.py
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
from django.urls import reverse


class Project(models.Model):
    title = models.CharField(max_length=255)
    body = models.TextField()
    date = models.DateTimeField(auto_now_add=True)
    author = models.ForeignKey(
        get_user_model(),
        on_delete=models.CASCADE,
    )

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("project_detail", args=[str(self.id)])

# projects/views.py
from django.contrib.auth.mixins import (
    LoginRequiredMixin,
    UserPassesTestMixin,
)
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, DeleteView, CreateView
from django.urls import reverse_lazy
from .models import Project


class ProjectListView(LoginRequiredMixin, ListView):
    model = Project
    template_name = "project_list.html"


class ProjectDetailView(LoginRequiredMixin, DetailView):
    model = Article
    template_name = "project_detail.html"


class ProjectUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
    model = Project
    fields = (
        "title",
        "body",
    )
    template_name = "project_edit.html"

    def test_func(self):
        obj = self.get_object()
        return obj.author == self.request.user

Fanion answered 28/6, 2021 at 17:55 Comment(4)
This is already implemented through Django's groups. A group has a collection of members (user objects) and a collection of permissions.Indomitability
Thanks @WillemVanOnsem, do you have an example I could refer too? I don't see how I could implement the group permissions in my case.Fanion
Can you provide an example view that one of these projects use?Narcotic
@LordElrond Thx, I edited my initial question. As you can see project update is only allowed for the author but I would like that any reviewer or editor can change this.Fanion
G
3

If you have projects as a model. You can add custom permissions to the model. Then assign those permissions to your users appropriately for each project (actually easily add/remove permissions too).

Then use either user_passes_test or permissions_required in your views/template to restrict what users can see/access/edit.

    class Project(models.Model):
       project_name = ...

    class RoleType(models.Model):
       role_name = models.CharField
       # Permission boolean flags
       can_edit = models.Boolean
       can_view = models.Boolean
       
    class ProjectRole(models.Model):
        project = models.ForeignKey('Project', ...)
        role = models.ForeignKey('RoleType', ...)
        user = models.ForeignKey('User', ...)

Now you can reverse lookup based on project or user

    # To show all assigned users and their roles for a project
    foo_project = Project.objects.get(project_name='foo')
    project_roles = ProjectRole.objects.filter(project=foo_project)

You can also restrict your views and templates by roles and their permissions boolean flags.

Given answered 1/7, 2021 at 10:17 Comment(4)
Thx! I don't really how I can test project-varying permissions to the logged user. Where would you record the list of project dependent permissions (e.g for user1: {'proj01':'reviewer', 'proj02':'editor', 'projxx': 'roleY', ... }?Fanion
Yeah Actually, with varying projects assigning permissions wouldn't work with this explanation, this would be a higher level permission system where someone has the role of editor on all projects. @Arjun has an answer that is almost there. If you subclass the user model of Django, you could add a many to one filed for project permissions and use his answer with just a few minor tweaks.Given
I've also updated my answer to reflect a useable layout.Given
Updated last code section to make it usable code.Given
T
0
  • Create groups that defines your Roles, for example : Group1: Editor, Group2:Manager and so on
  • Assign each user to the specified group ( you can do it in python manage.py shell or in admin panel )
  • Add restrictions on the view, for example: /mysite/projx/ view is restricted to groupA, you can check the following question that helps you with this point: https://mcmap.net/q/127995/-in-django-how-do-i-check-if-a-user-is-in-a-certain-group

For example: GroupA : GlobalEditor ( Bob can edit in projx and projy , and can only view projz )

GroupB : viewonly ( Adam can only view the content of the projs ) and so on

Toothless answered 3/7, 2021 at 9:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.