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()]
Related
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
I am trying to set up an API endpoint that returns a singular object.
Right now I have:
class ShoppingCartViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = ShoppingCartSerializer
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_paginated_response(self, data):
return Response(data)
def get_queryset(self):
return ShoppingCart.objects.filter(user=self.request.user)
Which uses the ListModelMixin and a filter to return one item, becasue each user has 1 shopping cart.
The issue is the filter function returns a queryset, but I only want a single item.
I attempted to use the RetrieveModelMixin but that doesn't run on the endpoint that I want. Retrieve runs on .../api/shopping-cart/id but I want to retrieve on .../api/shopping-cart because the filtering is done via the person who is logged in.
Any solutions?
I'm not sure if you have tried this but it should be worthwhile (although I'm not sure if it'll work)
def get_queryset(self):
return ShoppingCart.objects.filter(user=self.request.user)[:1]
Why not use simple api view instead of viewset class?
from django.http import Http404
from rest_framework import status, views
class ShoppingCartAPIView(views.APIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
def get_object(self):
# use .get if you are really sure it can only be one shopping cart per user
try:
return ShoppingCart.objects.get(user=self.request.user)
except ShoppingCart.DoesNotExist:
raise Http404()
def get(self, request):
obj = self.get_object()
data = ShoppingCartSerializer().to_representation(obj)
return Response(data, status=status.HTTP_200_OK)
You can even create logic for other http methods like post, delete.
Then add the url pattern api/shopping-cart as normal url (in urls.py), not router ones.
The way I ended up solving it was still using the ListModelMixin, as I needed to benefits of the viewset.
I overwrote the list() method with:
class ShoppingCartViewSet(viewsets.GenericViewSet, mixins.ListModelMixin):
"""
API endpoint that allows users to be viewed or edited.
"""
serializer_class = ShoppingCartSerializer
# authentication_classes = (TokenAuthentication,)
# permission_classes = (IsAuthenticated,)
def get_paginated_response(self, data):
return Response(data)
def list(self, request, *args, **kwargs):
instance = ShoppingCart.objects.get(user=self.request.user)
serializer = self.get_serializer(instance)
return Response(serializer.data)
which returns me a singular item on the root url .../api/shopping-cart without having to pass parameters because it filters based on user.
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__'
I have a django project I am working on that needs a users and account model. I am using and integrating Django Rest Framework. I was initially using individual API generic views from DRF. I am thinking of converting the individual generic views into a view set. I was able to do it for the user model. I wan to convert the Account model views to a view set.
My issue is that I have two versions of the same ListAPIView for the profile model. The top View lists all of the accounts in the database and the second one list all the accounts for an idividual user based on the User__Username foreignkey in the Account model.
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
queryset = User.objects.all()
lookup_field = 'username'
class AccountListView(ListAPIView):
queryset = Account.objects.all()
serializer_class = AccountSerializer
class AccountUserListView(ListAPIView):
queryset = Account.objects.all()
serializer_class = AccountSerializer
filter_backends = (filters.DjangoFilterBackend,)
filterset_fields = ('user', '_id', '_class')
def get_queryset(self):
return self.queryset.filter(user_username=self.kwargs.get('username'))
It says that I can specifically define the properties of a view within the viewset but I want to define two versions of the ListAPIView for the single model. Is there a way to double define the same view in a single viewset.
I basically want to define both of my Account ListAPIViews in the same viewset. How should i go about doing that if it is possible??
you can use the #action decorator to define.
class UserViewSet( mixins.ListModelMixin, viewsets.GenericViewSet):
"""
A simple ViewSet for listing or retrieving users.
"""
def list(self, request):
queryset = User.objects.all()
serializer = UserSerializer(queryset, many=True)
return Response(serializer.data)
#acttion(method="get", details=False, serializer_class=AccountSerializer)
def account_list(self, request, *args, **kwargs):
queryset = User.objects.all()
data = self.get_serializer_class().(instance=queryset, many=True).data
return response.Response(data)
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