My problem is very simple : I'm trying to create some custom permissions for my django rest API. This is my code (permission.py) :
class UserPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return obj == request.user
I just want that the users can only get, delete and update their own account.
The problem is that I think my code is not read by Django. I have try to always return false (without any condition) and it does nothing. I have also try to print some debug message at the beginning of the file and it's does nothing.
(My file permissions.py is at the root of my application)$
This is my user view (UserView.py) :
class UserViewSet(viewsets.ModelViewSet):
"""
API endpoint that allows users to be viewed or edited.
"""
queryset = User.objects.all().order_by("-date_joined")
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated]
swagger_tag = ["User"]
class LoginView(KnoxLoginView):
"""
API endpoint allowing the user to login and receive a token
"""
permission_classes = [
permissions.AllowAny,
]
#swagger_auto_schema(request_body=AuthTokenSerializer)
def post(self, request, format=None):
serializer = AuthTokenSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data["user"]
login(request, user)
return super(LoginView, self).post(request, format=None)
As #UtkucanBıyıklı says in their comment, you should specify the permission in the ViewSet:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.order_by('-date_joined')
serializer_class = UserSerializer
permission_classes = [permissions.IsAuthenticated, UserPermissions]
swagger_tag = ['User']
Related
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 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
How to add custom permission in viewset in django rest framework other than the default permission while creating a module?
I have a permission "fix_an_appointment". In the below viewset, how to include this permission? Those who have this permission has only able to create.
My views.py file:
class settingsViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Setting.objects.all()
Can anyone help?
I can't use a decorator like: #permission_classes(IsAuthenticated, ) in extra actions within ViewSet
To use different permissions in actions, instead, put it into the #action() as a parameter.
#action(detail=True, methods=['post'], permission_classes=[IsAdminOrIsSelf])
def set_password(self, request, pk=None):
...
drf doc
simply create a custom permission class
class FixAnAppointmentPermssion(permissions.BasePermission):
def has_permission(self, request, view):
return True or False
then the in your view set class use your custom permission
class settingsViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Setting.objects.all()
permission_classes = (FixAnAppointmentPermssion,)
by docs custom-permissions, list of view actions actions
my_permissions.py
from rest_framework import permissions
class FixPermission(permissions.BasePermission):
"""
fix_an_appointment
"""
def has_permission(self, request, view):
if request.user.is_authenticated :
if view.action == 'retrieve':
return request.user.has_perms('fix_list_perm')
if view.action == 'retrieve':
return request.user.has_perms('fix_an_appointment')
return False
in views.py
from my_permissions import FixPermission
class settingsViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Setting.objects.all()
permission_classes = (FixPermission,)
We can set permission for each functions like create, retrive, update, delete(add,edit,delete and update)
from my_permissions import FixPermission
class FixAnAppointmentPermssion(permissions.BasePermission):
def has_permission(self, request, view):
return True or False
class YourViewSet(viewsets.ModelViewSet):
serializer_class = SettingsSerializer
queryset = Your.objects.all()
#permission_classes(FixAnAppointmentPermssion,)
def create(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
#permission_classes(FixAnAppointmentPermssion,)
def retrive(request, format=None):
content = {
'status': 'request was permitted'
}
return Response(content)
I have a django model and and i want that model to be accessed only by its owner(user who created the model). So i created a permission class as follows
class IsOwnerOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
and applied this permission on modelviewset
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
permission_classes = (IsOwnerOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
While accessing a single item it works, But even then every authenticated user can access the list of items. So how could i limit the item access to only its owner?
I have included Tokenauthentication in settings page as shown
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
}
and the item looks like
class Item(models.Model):
name=models.CharField(max_length=30)
address=models.TextField()
owner = models.ForeignKey('auth.User', related_name='items', on_delete=models.CASCADE)
def __str__(self):
return self.name
You can't control who can access item list by owner,if you what,you need to override has_permission to class IsOwnerOnly, like:
class IsAuthenticatedOwner(permissions.BasePermission):
def has_permission(self, request, view):
# work when your access /item/
if request.user and is_authenticated(request.user):
if request.user.id in [1, 2, 3]:
return True
else:
return False
else:
return False
def has_object_permission(self, request, view, obj):
# work when your access /item/item_id/
# Instance must have an attribute named `owner`.
return obj.owner == request.user
Notice:has_permission work when your access /item/(list),has_object_permission work when your access /item/item_id/(retrieve and update).
If you want to let user see the items only he created,simple as:
class ItemsViewSet(ModelViewSet):
queryset = Items.objects.all()
serializer_class = ItemsSerializer
permission_classes = (IsAuthenticated)
def get_queryset(self):
queryset = self.get_queryset().filter(owner=self.request.user)
return queryset
You can override the method get_queryset on your View
class ItemViewSet(viewsets.ModelViewSet):
queryset = Item.objects.all()
serializer_class = ItemSerializer
permission_classes = (IsOwnerOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
def get_queryset(self):
return self.queryset.filter(owner=self.request.user)
This way the method list (from ModelViewSet) will call your "get_queryset" to build the pagination with the data.
I built a Follow model to record the social networking behaviour and would like to simulate the following action. Every authenticated user can follow the others.
class Follow(models.Model):
user = models.ForeignKey(User)
follower = models.ForeignKey(User, related_name="followers")
follow_time = models.DateTimeField(auto_now_add=True, blank=True)
class Meta:
unique_together = ('user', 'follower')
def __unicode__(self):
return u'%s, %s' % (self.user.username, self.follower)
And the FollowSerializer is:
class FollowSerializer(serializers.ModelSerializer):
class Meta:
model = Follow
field = ('user', 'follower', 'follow_time')
The view that I am using is:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
I am registering it in the urls as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
Every authenticated user can view the following relation, no restricts. But I would like to just allow the authenticated user to add the following relation by himself/herself, which means there should be request.user == follower. How can I do this?
I would like to add the FollowingDelete view to just allow the user to add a following relation by himself/herself.
So I updated the url.py as:
url(r'^api/users/(?P<pk>[0-9]+)/following/$', views.FollowingEnumByUserID.as_view()),
url(r'^api/users/(?P<pk>[0-9]+)/following/(?P<following_id>[0-9]+)/$', views.FollowingDelete.as_view()),
The permission that I am using is:
class IsFollowerOrReadOnly(permissions.BasePermission):
"""
View-level permission to allow the follower to edit the following relation
"""
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
#Reject any request for an invalid user
return False
return follower == request.user
And the views are:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_class = (IsFollowerOrReadOnly)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_class = (IsAuthenticated, IsFollowerOrReadOnly)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
Now the questions are:
The permission class doesn't work totally.
How to rewrite the DestroyAPIView, should I override the get_queryset function?
Django REST framework provides custom permission handling that allows you to handle complex permissions on the view and object level. In order to do what you are looking for, you are going to have to create a custom permission, but it's surprisingly easy.
Every authenticated user can follow the others.
DRF provides an IsAuthenticated permission that allows you to do this very easily. All you have to do is add it to the permission_classes on the view, or globally through the settings.
from rest_framework import permissions
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (permissions.IsAuthenticated, )
def get_queryset(self):
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
There is another restriction, which is the one that requires the custom permission class.
But I would like to just allow the authenticated user to add the following relation by himself/herself
This requires checking the request method (which I'm assuming is POST) and also the user who is being followed.
Lets start off with the easy check, the request method. Django REST framework provides permission classes that check the request method, such as IsAuthenticatedOrReadOnly, so we can look at the code to see how it is being done. From there it's just a matter of having a check against the request type.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
return False
This code will reject all requests that come in using the POST method, while allowing all others. The second step in creating this permission is doing the user check, so only the follower can add new people that they are following. This requires getting the follower and checking that against request.user.
class PostIfFollower(BasePermission):
"""
The request is not POST or the request user is the follower.
"""
def has_permission(self, request, view):
if request.method != "POST":
return True
try:
follower = User.objects.get(id=view.kwargs["pk"])
except User.DoesNotExist:
# Reject any requests for an invalid user
return False
return follower == request.user
This builds upon the last permission class by getting the user from the url (not allowing it if the user doesn't exist) and checking if they are the current user.
After I modified the typos, the permission class works now:
class FollowingEnumByUserID(generics.ListCreateAPIView):
serializer_class = FollowSerializer
permission_classes = (IsFollowerOrReadOnly,)
def get_queryset(self):
"""
List all the people the input user is following
"""
follower_id = self.kwargs['pk']
return Follow.objects.filter(follower=follower_id)
class FollowingDelete(generics.DestroyAPIView):
serializer_class = FollowSerializer
permission_classes = (IsAuthenticated, IsFollowerOrReadOnly,)
def get_queryset(self):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
return Follow.objects.filter(user=user_id, follower=follower_id)
And I make the FollowingDelete view work successfully by overriding the get_object() function.
def get_object(self, *args, **kwargs):
user_id = self.kwargs['following_id']
follower_id = self.kwargs['pk']
try:
return Follow.objects.get(user=user_id, follower=follower_id)
except Follow.DoesNotExist:
raise Http404("No such following relation")