Reputation: 1828
I'm building a webapp, where a user can create a team and add members and his/her projects. Everything is working fine, but now comes the permission part. One model will be Team
and another Project
. Right now i have written custom permission for both the models extending BasePermission
.
The operation/permission would be :
User1 created a team Team1
, can add any members and add his projects (no permission to add others project)
members of Team1
can add their own projects and edit (CRU) projects added by others. No permission for the members to delete Team1, only creator can delete the team.
A project can only be edited by the members of the team to which it is added. Others cannot. Only creator of the project can delete it.
Permissions:
from rest_framework import permissions
from .models import Team,Project
from rest_framework import serializers
class ProjectPermission(permissions.BasePermission):
message = "You do not have permission to perform this action with Project that doesn't belong to you or you are not a member of the team for this Project"
def has_object_permission(self, request,view, obj):
if not request.method in permissions.SAFE_METHODS:
if request.method != "DELETE":
if obj.team: #Team can be null when creating a project
return obj.created_by == request.user or request.user in obj.team.members.all()
return obj.created_by == request.user
return obj.created_by == request.user
return request.user.is_authenticated
def has_permission(self, request, view):
return request.user.is_authenticated
class TeamPermission(permissions.BasePermission):
message = "You do not have permission to perform this action with Team that is not created by you or you are not a member with full permission"
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return request.user.is_authenticated
else:
if request.method != "DELETE":
if obj.created_by == request.user or request.user in obj.members.all():
return True
return obj.created_by == request.user
def has_permission(self, request, view):
if not request.method in permissions.SAFE_METHODS:
if request.method =="DELETE":
return True
Projects = request.data.get('Projects')
if request.method =="PUT" or request.method == "PATCH":
if len(Projects)==0:return True # for removing a list projects an empty array is passed, in the serializer set function "remove set(current projects - empty list)" is used to identify the projects to remove
if Projects:
perm = []
for Project in Projects:
try:
#this is the issue, if there are 100 projects to be added, it does 100 queries to fetch the object
perm.append(ProjectPermission().has_object_permission(request, view,Project.objects.get(id=Project)))
except Exception as e:
raise serializers.ValidationError(e)
if False in perm:
raise serializers.ValidationError(
{"detail":"You do not have permission to perform this action with Project that doesn't belong to you or you are not a member of the team for this Project"})
return True
else:
return request.user.is_authenticated
This line at the end causes 100 queries to db for 100 projects. i can do filter in by project list. but is there any alternatives ?
perm.append(ProjectPermission().has_object_permission(request, view,Project.objects.get(id=Project))
Views:
class ProjectView(viewsets.ModelViewSet):
'''
Returns a list of all the Projects. created by user and others
Supports CRUD
'''
queryset=Project.objects.select_related('team','created_by').all()
serializer_class=ProjectSerializer
permission_classes=[ProjectPermission]
class TeamView(viewsets.ModelViewSet):
"""
Returns a list of all the teams. created by user and others
Supports CRUD
"""
queryset=Team.objects.prefetch_related('members','projects').select_related('created_by').all()
serializer_class=TeamSerializer
permission_classes=[TeamPermission]
models:
class Team(models.Model):
team_name=models.CharField(max_length=50,blank=False,null=False,unique=True)
created_by=models.ForeignKey(User,on_delete=models.SET_NULL,null=True)
created_at=models.DateTimeField(auto_now_add=True)
members=models.ManyToManyField(User,related_name='members')
def __str__(self) -> str:
return self.team_name
class Meta:
ordering=['-created_at']
class Project(models.Model):
team=models.ForeignKey(Team,related_name='projects',blank=True,null=True,on_delete=models.SET_NULL)
project_name=models.CharField(max_length=50,blank=False,null=False,unique=True)
description=models.CharField(max_length=1000,null=True,blank=True)
created_by=models.ForeignKey(User,on_delete=models.SET_NULL,null=True)
created_at=models.DateTimeField(auto_now_add=True)
file_name=models.CharField(max_length=100,null=True,blank=True)
def __str__(self) -> str:
return self.project_name
class Meta:
ordering = ['-created_at']
Is there an efficient way to achieve what i need ? I read about adding permission in the models, but i have no idea how it can be done in this case. Any suggestion would be a great help
Upvotes: 0
Views: 253
Reputation: 985
Let's break it down, any User
can create a Team
so there is one permission IsAuthenticated
Any Team
member can CRU a Project so there are three permissions here:
IsAuthenticated
& ( IsTeamOwner
| IsTeamMember
)
for deleting a Project, there are two permission (IsAuthenticated
& IsProjectOwner
) and so on so forth
so, the permissions could be something like that:
class IsTeamOwner(BasePermission):
message = "You do not have permission to perform this action"
def has_object_permission(self, request, view, obj):
return obj.created_by == request.user
class IsProjectOwner(BasePermission):
message = "You do not have permission to perform this action"
def has_object_permission(self, request, view, obj):
return obj.created_by == request.user
class IsTeamMember(BasePermission):
message = "You do not have permission to perform this action"
def has_object_permission(self, request, view, obj):
return request.user in obj.members.all()
Update
To check if the project id
exists and is created by the current user or not:
try:
project = Project.objects.only('team').get(id=recieved_id, created_by=request.user)
except Project.DoesNotExist:
raise ProjectNotExist() # Create this exception
To check if the project id
exists and belongs to the team that the current user is in or not:
try:
project = Project.objects.get(id=recieved_id).select_related('team')# try to use prefetch_related('team__members') also, I don't know it would work or not
if request.user not in project.team.members.all():
raise ProjectNotExist()
except Project.DoesNotExist:
raise ProjectNotExist()
and for more customization, after you checked if the project exists or not you could use bulk_update
to update the team
field in every project with only one query.
Upvotes: 1