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.
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"
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?
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
In short, you will always have one User model to handle authentication. Do not spread username and passwords across multiple models. Usually extending the default User model and adding boolean flags such as is_student and is_staff work for most cases. Permissions can be managed at a higher level using view decorators.
You can have in your models two user classes that extend from the USER model.
You need to create a group for each user role, and add needed permissions for each group. (Django has a default model permission, created automatically, look at the docs on the given links) or create the needed permission manually in the model definition.
1. No matter what strategy you pick, or what is your business model, always use one, and only one Django model to handle the authentication. You can still have multiple user types, but generally speaking it’s a bad idea to store authentication information across multiple models/tables.
For that case, you can use the built-in is_staff flag to differentiate normal users from admin users. Actually the built-in User model has two similar fields, is_staff and is_superuser. Those flags are used in the Django Admin app, the is_staff flag designates if the user can log in the Django Admin pages.
This is an example project to illustrate an implementation of multiple user types. In this Django app, teachers can create quizzes and students can sign up and take quizzes related to their interests.
If your application handle many user types, and users can assume multiple roles, an option is to create an extra table and create a many to many relationship: class Role(models.Model): ''' The Role entries are managed by the system, automatically created via a Django data migration.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With