I need permissions for my app in DRF.
Input:
noauthenticated users can only read publication and images
authenticated users can create publications and add images
authorized users can edit and delete publications and images for which they are authors
admin user can do all actions but edit content
Models:
class Publication(models.Model):
pub_text = models.TextField(null=True, blank=True)
pub_date = models.DateTimeField(auto_now_add=True)
pub_author = models.ForeignKey(User, on_delete=models.CASCADE)
class Image(models.Model):
image = models.ImageField(upload_to='images', null=True)
image_to_pub = models.ForeignKey(Publication, on_delete=models.CASCADE, null=True, related_name='images')
Views:
class PublicationViewSet(viewsets.ModelViewSet):
queryset = Publication.objects.all()
serializer_class = PublicationSerializer
permission_classes = [PublicPermission]
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
permission_classes = [ImagePermission]
Permissions:
class ImagePermission(BasePermission):
edit_methods = ['PUT', 'PATCH']
def has_permission(self, request, view):
if request.method in SAFE_METHODS:
return True
if request.user.is_authenticated:
return True
def has_object_permission(self, request, view, obj):
if request.user.is_superuser:
return True
if request.method in SAFE_METHODS:
return True
if request.user.id == Publication.objects.get(id=obj.image_to_pub_id).pub_author_id:
return True
if request.user.is_staff and request.method not in self.edit_methods:
return True
return False
class ImageAuthorPermission(BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.id == Publication.objects.get(id=obj.image_to_pub_id).pub_author_id:
return True
return False
Now it works as i described above. But i'm mot sure if this is good practice.
I mean class <ImagePermission>. There are two times check if method in SAFE_METHODS.
If i delete that check out from <has_permission>, unauthenticated users do not have ReadOnly rights.
If i delet that check out from <has_object_permission>, authenticated users do not have Edit and Delete rights.
I'm sure there is beter way to customise this permissions. Isn't there?
Also i tryed to check if current user has object permission to images which related to publication for which user was author. This works but is there standard practice how to check permissions to related objects? I tryed to delete check
if request.user.id == Publication.objects.get(id=obj.image_to_pub_id).pub_author_id:
out of <ImagePermission> and combine both <ImagePermission> and <ImageAuthorPermission> classes in a <permission_classes> list. Used & and | operators, but did not get success.
I saw this part and implemented it.
First of all, the SAFE_METHOD request must be read only, and the action such as create can only be made to authenticated users.
For this part, use the IsAuthenticatedOrReadOnly class that DRF Permission provides as default.
rest_framework.permission
class IsAuthenticatedOrReadOnly(BasePermission):
"""
The request is authenticated as a user, or is a read-only request.
"""
def has_permission(self, request, view):
return bool(
request.method in SAFE_METHODS or
request.user and
request.user.is_authenticated
)
To implement No. 3 and 4, only the owner can modify and delete it, and the administrator can only modify it("PUT", "PATCH").
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission
class AdminObjectPermission(BasePermission):
def has_object_permission(self, request, view, obj):
if bool(request.user and request.user.is_staff) and request.method in ["PUT", "PATCH"]:
return True
class PublicPermission(IsAuthenticatedOrReadOnly):
def has_object_permission(self, request, view, obj):
if request.user.id == obj.pub_author.id:
return True
return False
class ImagePermission(IsAuthenticatedOrReadOnly, AdminObjectPermission):
def has_object_permission(self, request, view, obj):
if request.user.id == obj.image_to_pub.pub_author.id:
return True
return False
For the Publication object, it is associated with the User with the pub_author field.
if request.user.id == obj.pub_author.id
Image objects were compared through pub_author on connected Publication objects.
if request.user.id == obj.image_to_pub.pub_author.id
views.py
class PublicationViewSet(viewsets.ModelViewSet):
queryset = Publication.objects.all()
serializer_class = PublicationSerializer
authentication_classes = [YourAuthenticationClass]
permission_classes = [PublicPermission|AdminObjectPermission]
class ImageViewSet(viewsets.ModelViewSet):
queryset = Image.objects.all()
serializer_class = ImageSerializer
authentication_classes = [YourAuthenticationClass]
permission_classes = [ImagePermission|AdminObjectPermission]
additionally
The permission check for the retrieve method failed.
I modified the source code to allow for this part.
from rest_framework.permissions import IsAuthenticatedOrReadOnly, BasePermission
class AdminObjectPermission(BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in SAFE_METHODS:
return True
if bool(request.user and request.user.is_staff) and request.method in ["PUT", "PATCH"]:
return True
class PublicPermission(IsAuthenticatedOrReadOnly):
def has_object_permission(self, request, view, obj):
if request.user.id == obj.pub_author.id:
return True
return False
class ImagePermission(IsAuthenticatedOrReadOnly, AdminObjectPermission):
def has_object_permission(self, request, view, obj):
if request.user.id == obj.image_to_pub.pub_author.id:
return True
return False