How to set permissions for CRUD operations in ModelViewSet Django - django

I have a viewset for model News.
I want to do next permissions:
All people can see news.
Only authorized users and admin can create news.
Only owner and admin can update news.
Only admin can delete news.
How can I set different permissions for each operation? For create I want to use: IsAuthenticated and IsAdminUser. For update I want to use IsAdminUser and I create my own permission for owner. For delete I want to use also IsAdminUser.
view:
class NewsViewSet(viewsets.ModelViewSet):
queryset = News.objects.all()
serializer_class = NewsSerializer
permission:
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.author == request.user

class Viewset(BaseModelViewSet):enter code here
queryset = Model.objects.all()
serializer_class = ModelSerializer
permission_classes_by_action = {
'create': (permissions.IsAdminUser,),
'list': (permissions.IsAuthenticatedOrReadOnly,),
'retrieve': (permissions.AllowAny,),
'update': (permissions.AllowAny,),
'destroy': (permissions.IsAdminUser,),
'search': (permissions.IsAuthenticated,)
Like this you can use the pre-built permission or create custom permission class

Related

Django Rest Framework Custom Permission does not recognize user

I am trying to enforce a custom permission in DRF. Specifically, I am trying to see whether a user is authorized to access an object. I adapted this solution from another post here but it does not quite work for me. The permission class always assumes that request.user is an Anonymous user. What am I missing?
permissions.py
class CanSeeWishlist(permissions.BasePermission):
def has_permission(self, request, view):
try:
wishlist = Wishlist.objects.get(
pk=view.kwargs['pk'])
except:
return False
if wishlist.private:
print(request.user) # Prints Anonymous User
if request.user.id == wishlist.owner.id or request.user.id in wishlist.members.all():
return True
return False
return True
api.py
class WishlistViewset(viewsets.ModelViewSet):
serializer_class = WishlistSerializer
queryset = Wishlist.objects.all()
authentication_classes = (TokenAuthentication,)
permission_classes = [
permissions.IsAuthenticatedOrReadOnly
]
def get_permissions(self):
if self.request.method == 'GET':
self.permission_classes = (CanSeeWishlist,)
return super(WishlistViewset, self).get_permissions()
I suspect that doing super(WishlistViewset, self).get_permissions() didn't pass request to the class so it resulted in Anonymous User or as you dropped IsAuthenticatedOrReadOnly something went wrong.
Anyway, I think it's better to override get_permissions() with the same way it was implemented, it will fix the problem I hope:
What get_permissions() actually do:
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
So It should be:
def get_permissions(self):
permissions_list = [permissions.IsAuthenticatedOrReadOnly] #the base one that should be there for all request actions
if self.request.method == 'GET': # this will add the permission for all GET requests, If you want it to only apply on retrive you can do self.action == 'retrive':
permissions_list.append(CanSeeWishlist)
return [permission() for permission in permissions_list]

Django separate permissions for get and post method in generic view

I have created generic views in django rest framework which allows listing and creation of objects to admin user. But, what I am really trying to achieve is any user staff status should be able to get the objects (use get method) but only super user should be able to create objects (use post method). Here are my generic views.
class StateList(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = [IsAdminUser]
you can use get_permission for that.
class StateList(generics.ListCreateAPIView):
queryset = State.objects.all()
serializer_class = StateSerializer
permission_classes = [IsAdminUser]
def get_permissions(self):
if self.request.method == 'POST':
return [permission() for permission in self.permission_classes]
return [AllowAny()]

Django Rest Framework check_object_permissions not being called

I am trying to make sure the user has permission to view the object they are calling. Here is my permissions class:
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to do actions.
"""
message = 'You must be the owner of this object.'
def has_object_permission(self, request, view, obj):
print("CHECK THAT I GOT HERE")
return obj.user == request.user
And here is my ViewSet:
class TopLevelJobViewSet(ModelViewSet):
permission_classes = (IsOwner,)
serializer_class = TopLevelJobSerializer
queryset = TopLevelJob.objects.all()
filter_backends = [DjangoFilterBackend, RelatedOrderingFilter]
filter_class = TopLevelJobFilter
ordering_fields = '__all__'
Thehas_object_permissions is not being called, anyone visiting the endpoint is able to access all the objects.
Why is this? How do I get has_object_permissions to get called?
This post: Django rest framework ignores has_object_permission talks about it being an issue with not having GenericAPIView. But ModelViewSet has GenericViewSet which has generics.GenericAPIView. Is something overriding this somewhere?
EDIT: My issue was that I was calling list instead of get. How can I only returns objects in list that belong to a user?
This link: https://www.django-rest-framework.org/api-guide/filtering/#filtering-against-the-current-user shows I could implement something like this:
def get_queryset(self):
username = self.kwargs['username']
return Purchase.objects.filter(purchaser__username=username)
This seems to violate DRY if I have to add this to every viewset. Is there a way to turn this into a permissions class that I could always call?
You can implement custom generic filtering [drf-doc]. For example:
class IsOwnerFilter(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view):
return queryset.objects.filter(user=request.user)
Then you can add this to your ModelViewSet:
class TopLevelJobViewSet(ModelViewSet):
permission_classes = (IsOwner,)
serializer_class = TopLevelJobSerializer
queryset = TopLevelJob.objects.all()
filter_backends = [IsOwnerFilter, DjangoFilterBackend, RelatedOrderingFilter]
filter_class = TopLevelJobFilter
ordering_fields = '__all__'

Django DRF restrict profile model creation to authenticated user

I have a many User model and a Customer model with a OneToOne relation to the User.
I have established authentication for User model where a user can edit/update/delete only his profile. But I want the customer model also to be accessible (list/create/update etc) by the authenticated user.
My permissions class for Customer:
class UpdateCustomerProfile(permissions.BasePermission):
"""Allow customers to edit their own profile """
def has_permission(self, request, view):
"""Check if user is authenticated and has permisson to access customer model """
if view.action == 'list':
return request.user.is_authenticated and request.user.is_superuser
elif view.action == 'create':
return request.user.is_authenticated
elif view.action in ['retrieve', 'update', 'partial_update', 'destroy']:
return request.user.is_authenticated
else:
return False
My customer view set:
class CustomerViewSet(viewsets.ModelViewSet):
"""Handle creating reading and updating Users in system"""
serializer_class = serializers.CustomerSerializer
queryset = models.Customer.objects.filter()
permission_classes = (permissions.UpdateCustomerProfile,)
But I get an error saying:
"detail": "Authentication credentials were not provided."
even If I add the token in Authorisation field of Header.
UPDATE:
If I add authentication_classes = (TokenAuthentication,) to my CustomerViewSet I get an error:
"detail": "You do not have permission to perform this action."
I'm confused, I want to leverage the current authorisation of an User to authorise creation of a customer. i.e Only An authenticated user should be able to create his Customer profile
How can I fix this?
You should add authentication_classes attribute to the view
from rest_framework.authentication import TokenAuthentication
class CustomerViewSet(viewsets.ModelViewSet):
"""Handle creating reading and updating Users in system"""
serializer_class = serializers.CustomerSerializer
queryset = models.Customer.objects.filter()
permission_classes = (permissions.UpdateCustomerProfile,)
authentication_classes = (TokenAuthentication,)

DRF Viewset remove permission for detail route

I have a basic Viewset:
class UsersViewSet(viewsets.ModelViewSet):
permission_classes = (OnlyStaff,)
queryset = User.objects.all()
serializer_class = UserSerializer
It is bind to the /api/users/ endpoint. I want to create a user profile page, so I need only a particular user, so I can retrieve it from /api/users/<id>/, but the problem is that I want /api/users/<id>/ to be allowed to anyone, but /api/users/ to keep its permission OnlyStaff, so no one can have access to the full list of users.
Note: Perhaps it's not such a good implementation, since anyone could brute force the data incremeting the id, but I'm willing to change it from <id> to <slug>.
How can I delete the permission from detail route?
Thanks in advance.
Override the get_permissions() method as below
from rest_framework.permissions import AllowAny
class UsersViewSet(viewsets.ModelViewSet):
permission_classes = (OnlyStaff,)
queryset = User.objects.all()
serializer_class = UserSerializer
def get_permissions(self):
if self.action == 'retrieve':
return [AllowAny(), ]
return super(UsersViewSet, self).get_permissions()
It would help if you posted the permission class.
But going off what you posted, it appears that only staff users can have access to the endpoints bound to that viewset. Meaning no other user type/role can access those endpoints.
Going off your question, it seems like you want to setup a IsOwnerOrStaffOrReadOnly permission and over ride the list route function of the ModelViewSet and replace permission_classes and then call super
class UsersViewSet(viewsets.ModelViewSet):
permission_classes = (IsOwnerOrStaffOrReadOnly,)
queryset = User.objects.all()
serializer_class = UserSerializer
def list(self, request, *arg, **kwargs):
self.permission_classes = (OnlyStaffCanReadList,)
super(UsersViewSet, self).list(request, *args, **kwargs) // python3 super().list(request, *args, **kwargs)
is Owner object permission class
class IsOwnerOrStaffOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
if request.user.role == 'staff':
return True
# Instance must have an attribute named `owner`.
return obj.owner == request.user
only staff can read permission class
class OnlyStaffCanReadList(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.user.role == 'Staff':
return True
else:
return False
as provided in the comments, your user model must have the owner role. if you are using the django user model you can just do a obj.id == request.user.id comparison